diff --git a/.gitignore b/.gitignore
index 9b035f8..1056b50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@ node_modules
dist
*.geojson
*.shp
-layers/dot-cams/*/data/states.js
+layers/dot-cams/castle-rock/data/states.js
diff --git a/layers/dot-cams/README.md b/layers/dot-cams/README.md
index 72c36bf..6fdc998 100644
--- a/layers/dot-cams/README.md
+++ b/layers/dot-cams/README.md
@@ -1,124 +1,10 @@
-# Department of Transportation Cameras by State
-
-Many states are through one of a few providers:
-
- * Castle Rock: https://www.castlerockits.com/oneweb
- * IBI Travel IQ: https://www.ibigroup.com/ibi-products/traveliq/
-
-| State | DOT Name[^name] | Online Service Name | Video? | Web provider | Link |
-| ----- | --------------- | ------------------- | ------ | ------------ | ------------------------------- |
-| AK | ALDOT | | | Travel IQ | https://511.alaska.gov/ |
-| AL | PF | | | | |
-| AR | ADOT | | | | |
-| AZ | ARDOT | | | | |
-| CA | Caltrans | | | | |
-| CO | CDOT | | | | |
-| CT | CTDOT | | | | |
-| DE | DelDOT | | | | |
-| FL | FDOT | | | | |
-| GA | GDOT | | | | |
-| HI | HDOT | | | | |
-| IA | ITD | | | | |
-| ID | IDOT | | | | |
-| IL | INDOT | | | | |
-| IN | DOT | | | | |
-| KS | KDOT | | | | |
-| KY | KYTC | | | | |
-| LA | DOTD | | | | |
-| MA | MaineDOT | | | | |
-| MD | MDOT | | | | |
-| ME | MassDOT | | | | |
-| MI | MDOT | | | | |
-| MN | MNDOT | 511MN | | Castle Rock | |
-| MO | MoDOT | | | | https://traveler.modot.org/map/ |
-| MS | MDOT | | | | |
-| MT | MDT | | | | |
-| NC | NDOT | | | | |
-| ND | NDOT | | | | |
-| NE | NHDOT TODO: fix | | | | |
-| NH | NJDOT | | | | |
-| NJ | NMDOT | | | | |
-| NM | NYSDOT | | | | |
-| NV | NCDOT | | | | |
-| NY | NDDOT | | | | |
-| OH | ODOT | | | | |
-| OK | ODOT | | | | |
-| OR | ODOT | | | | |
-| PA | PennDOT | | | | |
-| RI | RIDOT | | | | |
-| SC | SCDOT | | | | |
-| SD | SDDOT | | | | |
-| TN | TDOT | | | | |
-| TX | TxDOT | | | | |
-| UT | UDOT | | | | |
-| VA | VTrans | | | | |
-| VT | VDOT | | | | |
-| WA | WSDOT | | | | |
-| WI | WisDOT | | | | |
-| WV | WVDOT | | | | |
-| WY | WYDOT | | | | |
-
-
-[^name]: From https://en.wikipedia.org/wiki/Department_of_transportation#List_of_U.S._state_and_insular_area_departments_of_transportation
-
-
-
+https://traveler.modot.org/map/
https://map.wyoroad.info/wtimap/index.html
https://www.sd511.org/#&zoom=6.396744103345674&lon=-96.21250629888505&lat=44.0011361134118&states
https://www.travelmidwest.com/lmiga/cameraReport.jsp?location=GATEWAY.IL
-https://www.traffic-cams.com/seatonville/illinois/all
-
-https://www.wyoroad.info/
-https://map.wyoroad.info/wtimap/index.html
-
-https://www.511mt.net
-
-https://wsdot.com/Travel/Real-time/Map/
-
-oregon: https://tripcheck.com/
-
-https://www.nmroads.com/mapIndex.html?
-
-https://drivetexas.org/?r=co / https://its.txdot.gov/its/District/FTW/cameras
-
-oklahoma: https://oktraffic.org/#/map
-
-arkansas: https://www.idrivearkansas.com/
-
-tn: https://smartway.tn.gov/traffic?features=incident,traffic
-
-https://goky.ky.gov/
-https://maps.kytc.ky.gov/trafficcameras/?xmin=-9450477.801928485&xmax=-9383289.654065905&ymin=4696826.35185232&ymax=4742421.039224366
-
-https://www.511sc.org/#zoom=7.392317422778981&lon=-80.72462271068872&lat=33.54446902822535&dmsg&rest&cams&other&cong&wthr&acon&incd&trfc iteris
-
-https://drivenc.gov/#
-https://eapps.ncdot.gov/services/traffic-prod/v1/cameras/
-https://eapps.ncdot.gov/services/traffic-prod/v1/cameras/images?filename=US70_YearganRd.jpg&t=1706599344471
-
-https://www.mdottraffic.com/default.aspx?showMain=true
-
-Michigan is just...down? https://mdotjboss.state.mi.us/MiDrive/map
-
-ohio: https://www.ohgo.com/all-ohio?lt=39.949999999999996&ln=-83.05&z=7&ls=incident,construction
-
-https://511pa.com/
-
-https://www.dot.ri.gov/travel/index.php
-
-https://511nj.org/camera
-
-https://www.511virginia.org/
-
-https://wv511.org/
-
-https://chart.maryland.gov/TrafficCameras/GetTrafficCameras
-
-http://goakamai.org/cameras
-
# CA
kern maybe same as WI?
@@ -127,15 +13,3 @@ https://kern511.org/cctv?start=0&length=10&order%5Bi%5D=1&order%5Bdir%5D=asc
https://dot.ca.gov/programs/traffic-operations/traveler-information/511
"rr": "https://riverregion511.org" castlerock
-
-
-No video, only photos ??
- "ak": "https://511.alaska.gov/",
- "az": "https://az511.com/",
- "ct": "https://ctroads.org/",
- "new england": "https://newengland511.org/",
- "id": "https://511.idaho.gov/",
- "ut": "https://www.udottraffic.utah.gov/",
-
-Some kind of signature needed:
- "fl": "https://fl511.com/",
diff --git a/layers/dot-cams/al/get_data.py b/layers/dot-cams/al/get_data.py
index 7549c77..8c732dc 100755
--- a/layers/dot-cams/al/get_data.py
+++ b/layers/dot-cams/al/get_data.py
@@ -7,60 +7,18 @@ cameras = []
res = requests.get("https://api.algotraffic.com/v3.0/Cameras")
res.raise_for_status()
-# {
-# "id": 1164,
-# "location": {
-# "latitude": 30.56705,
-# "longitude": -88.19211,
-# "city": "Theodore",
-# "county": "Mobile",
-# "displayRouteDesignator": "I-10",
-# "routeDesignator": "I-10",
-# "routeDesignatorType": "Interstate",
-# "displayCrossStreet": "Theodore Dawes Rd",
-# "crossStreet": "Theodore Dawes Rd",
-# "crossStreetType": "Arterial",
-# "direction": "East",
-# "linearReference": 14.0
-# },
-# "responsibleRegion": "Southwest",
-# "hlsUrl": "https://cdn3.wowza.com/5/aTZuSEJVaHcxakdx/mobile-fastly/mob-cam-c090.stream/playlist.m3u8",
-# "imageUrl": "https://api.algotraffic.com/v3/Cameras/1164/snapshot.jpg",
-# "accessLevel": "Public"
-# }
for c in res.json():
- try:
- if c['accessLevel'] != 'Public':
- print("warn: access level is not public; ignoring:", c)
- continue
- if not c['hlsUrl'].startswith('https://'):
- raise Exception("invalid hlsUrl")
- if not c['imageUrl'].startswith('https://'):
- raise Exception("invalid imageUrl")
- cameras.append({
- "type": "Feature",
- "geometry": {
- "type": "Point",
- "coordinates": [c['location']['longitude'], c['location']['latitude']], # yes, [lon, lat] since it's [x, y]
- },
- "properties": {
- "name": c['location']['displayRouteDesignator'] + '/' + c['location']['displayCrossStreet'],
- "views": [
- {
- 'hasVideo': True,
- 'src': c['hlsUrl'],
- },
- # {
- # 'hasVideo': False,
- # 'src': c['imageUrl'],
- # }
- ]
- },
- })
- except Exception as e:
- print(c)
- raise e
+ cameras.append({
+ "type": "Feature",
+ "geometry": {
+ "type": "Point",
+ "coordinates": [c['location']['longitude'], c['location']['latitude']], # yes, [lon, lat] since it's [x, y]
+ },
+ "properties": {
+ 'originalData': c,
+ },
+ })
geojson = {
"type": "FeatureCollection",
diff --git a/layers/dot-cams/al/layer.js b/layers/dot-cams/al/layer.js
new file mode 100644
index 0000000..cffc4fb
--- /dev/null
+++ b/layers/dot-cams/al/layer.js
@@ -0,0 +1,46 @@
+import VectorLayer from 'ol/layer/Vector';
+import {Vector as VectorSource} from 'ol/source.js';
+import GeoJSON from 'ol/format/GeoJSON.js';
+
+import Hls from 'hls.js';
+
+import {Style} from 'ol/style.js';
+import Icon from 'ol/style/Icon.js';
+
+import url from './data.geojson?url'; // TODO: remove `?url`?
+import pin from './pin.svg?url'; // TODO: remove `?url`?
+
+const vectorLayer = new VectorLayer({
+ source: new VectorSource({
+ url: url,
+ format: new GeoJSON,
+ }),
+ style: new Style({
+ image: new Icon({
+ anchor: [0.5, 1],
+ src: pin,
+ }),
+ }),
+});
+
+vectorLayer.customPopup = function(feature) {
+ return ``;
+};
+
+vectorLayer.customPopupCallback = function(feature) {
+
+ const video = document.getElementById('popupVideo');
+
+ const videoSrc = feature.values_.originalData.hlsUrl;
+ if (Hls.isSupported()) {
+ var hls = new Hls();
+ hls.loadSource(videoSrc);
+ hls.attachMedia(video);
+ }
+ // iDevice support, untested (only works in Safari; required for iPhones)
+ else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+ video.src = videoSrc;
+ }
+}
+
+export default vectorLayer;
diff --git a/layers/dot-cams/pin.svg b/layers/dot-cams/al/pin.svg
similarity index 100%
rename from layers/dot-cams/pin.svg
rename to layers/dot-cams/al/pin.svg
diff --git a/layers/dot-cams/castle-rock/get_data.py b/layers/dot-cams/castle-rock/get_data.py
index 4067bd7..6a4111e 100755
--- a/layers/dot-cams/castle-rock/get_data.py
+++ b/layers/dot-cams/castle-rock/get_data.py
@@ -4,21 +4,15 @@ import requests
import json
import re
-states = {
- "Minnesota": "https://511mn.org/",
- "Colorado": "https://maps.cotrip.org/",
- "Iowa": "https://511ia.org/",
- "Indiana": "https://511in.org/",
- "Kansas": "https://www.kandrive.gov/",
- "Massachusetts": "https://mass511.com/",
- "Nebraska": "https://new.511.nebraska.gov/"
-}
+with open('states.json') as f:
+ states = json.loads(f.read())
with open("query.graphql") as f:
QUERY = f.read()
+data = {}
+
for state, baseURL in states.items():
- print(f"{state}: ", end="", flush=True)
PAYLOAD = [
{
"query": QUERY,
@@ -29,7 +23,7 @@ for state, baseURL in states.items():
"south":0,
"east":0,
"west":-179,
- "zoom":15,
+ "zoom":12,
"layerSlugs": ["normalCameras"],
"nonClusterableUris": ["dashboard"],
},
@@ -46,8 +40,6 @@ for state, baseURL in states.items():
cameras = []
viewCount = 0
- photoCount = 0
- videoCount = 0
for c in camera_views:
if len(c['features']) != 1:
@@ -57,36 +49,22 @@ for state, baseURL in states.items():
if re.match(r"Show .* cameras", c['tooltip']):
raise Exception(f"Not zoomed in enough! Finding aggregate cameras: {c}")
- if len(c['views']) == 0:
- raise Exception("Camera has no views")
-
for view in c['views']:
- if view['category'] != c['views'][0]['category']:
- print(f"warn: Differing types detected: {c['views']}")
- if view['category'] == 'VIDEO':
- if state == "Nebraska":
- print(c)
- videoCount += 1
- if len(view['sources']) != 1:
- raise Exception(f"Unexpected number of sources ({len(view['sources'])})")
- else:
- photoCount += 1
+ if len(view['sources']) != 1 if view['category'] == 'VIDEO' else 0:
+ print(view)
+ raise Exception(f"Unexpected number of sources ({len(view['sources'])})")
for source in view['sources'] or []:
if source['type'] != 'application/x-mpegURL':
raise Exception(f"Unexpected type {source['type']}")
+ viewCount += len(c['views'])
cameras.append({
"type": "Feature",
"geometry": c['features'][0]['geometry'],
"properties": {
- 'name': c['tooltip'],
- 'views': [
- {
- 'hasVideo': v['category'] == 'VIDEO',
- 'src': v['sources'][0]['src'] if v['category'] == 'VIDEO' else v['url'],
- } for v in c['views']
- ],
- # 'originalData': c,
+ 'address': c['tooltip'],
+ 'website': c['views'][0]['url'],
+ 'originalData': c,
},
})
@@ -98,8 +76,8 @@ for state, baseURL in states.items():
with open(f"data/{state}.geojson", "w") as f:
f.write(json.dumps(geojson))
- print(f"{len(cameras)} locations found")
- print(f"{state}: {photoCount} photo + {videoCount} video cameras")
+ print(f"{len(cameras)} locations found for {state}")
+ print(f"{viewCount} total views for {state}")
# hack hack hack
#
diff --git a/layers/dot-cams/castle-rock/index.js b/layers/dot-cams/castle-rock/index.js
new file mode 100644
index 0000000..2f24c52
--- /dev/null
+++ b/layers/dot-cams/castle-rock/index.js
@@ -0,0 +1,75 @@
+import VectorLayer from 'ol/layer/Vector';
+import {Vector as VectorSource} from 'ol/source.js';
+import GeoJSON from 'ol/format/GeoJSON.js';
+
+import Hls from 'hls.js';
+
+import {Style} from 'ol/style.js';
+import Icon from 'ol/style/Icon.js';
+
+import states from './data/states.js';
+
+import pin from './pin.svg?url'; // TODO: remove `?url`?
+
+// https://en.wikipedia.org/wiki/Department_of_transportation#List_of_U.S._state_and_insular_area_departments_of_transportation
+const dot_names = {
+ mn: "Minnesota: MNDOT/511MN",
+ ia: "Iowa: Iowa DOT/511IA",
+ wi: "Wisconsin: WisDOT/511WI",
+ co: "Colorado: CDOT/COtrip",
+ ks: "Kansas: KDOT/KanDrive",
+ ne: "Nebraska: NDOT/Nebraska 511",
+ ma: "Massachusetts: MassDOT/Mass511",
+}
+
+let vectorLayers = []
+
+for (let [state, url] of Object.entries(states)) {
+ const vectorLayer = new VectorLayer({
+ source: new VectorSource({
+ url: url,
+ format: new GeoJSON,
+ }),
+ style: new Style({
+ image: new Icon({
+ anchor: [0.5, 1],
+ src: pin,
+ }),
+ }),
+ });
+
+ vectorLayer.customPopup = function(feature) {
+ const view = feature.values_.originalData.views[0];
+ if (view.category.toLowerCase() == "video") {
+ return ``;
+ } else if (view.category.toLowerCase() == "image") {
+ return ``;
+ } else {
+ throw new Exception(`unknown category ${view.category}`);
+ }
+ };
+
+ vectorLayer.customPopupCallback = function(feature) {
+ const view = feature.values_.originalData.views[0];
+ if (view.category.toLowerCase() == "video") {
+ const video = document.getElementById('popupVideo');
+
+ const videoSrc = view.sources[0].src;
+ if (Hls.isSupported()) {
+ var hls = new Hls();
+ hls.loadSource(videoSrc);
+ hls.attachMedia(video);
+ }
+ // iDevice support, untested (only works in Safari; required for iPhones)
+ else if (video.canPlayType('application/vnd.apple.mpegurl')) {
+ video.src = videoSrc;
+ }
+ }
+ }
+ vectorLayers.push({
+ name: dot_names[state] ?? state,
+ layer: vectorLayer,
+ });
+}
+
+export default vectorLayers;
diff --git a/layers/dot-cams/castle-rock/pin.svg b/layers/dot-cams/castle-rock/pin.svg
new file mode 100644
index 0000000..fb93212
--- /dev/null
+++ b/layers/dot-cams/castle-rock/pin.svg
@@ -0,0 +1,21 @@
+
+
diff --git a/layers/dot-cams/castle-rock/states.json b/layers/dot-cams/castle-rock/states.json
new file mode 100644
index 0000000..ce4d810
--- /dev/null
+++ b/layers/dot-cams/castle-rock/states.json
@@ -0,0 +1,8 @@
+{
+ "mn": "https://511mn.org/",
+ "co": "https://maps.cotrip.org/",
+ "ia": "https://511ia.org/",
+ "ks": "https://www.kandrive.gov/",
+ "ma": "https://mass511.com/",
+ "ne": "https://new.511.nebraska.gov/"
+}
diff --git a/layers/dot-cams/index.js b/layers/dot-cams/index.js
index b047a88..7d16f40 100644
--- a/layers/dot-cams/index.js
+++ b/layers/dot-cams/index.js
@@ -1,80 +1,22 @@
-import VectorLayer from 'ol/layer/Vector';
-import {Vector as VectorSource} from 'ol/source.js';
-import GeoJSON from 'ol/format/GeoJSON.js';
+import al from './al/layer.js';
+import wi from './wi/layer.js';
-import Hls from 'hls.js';
+import castlerocklayers from './castle-rock/index.js';
-import {Style} from 'ol/style.js';
-import Icon from 'ol/style/Icon.js';
-
-import pin from './pin.svg?url'; // TODO: remove `?url`?
-import pinVideo from './pin-video.svg?url'; // TODO: remove `?url`?
-
-import castleRockStates from './castle-rock/data/states.js';
-import travelIqStates from './travel-iq/data/states.js';
-import al from './al/data.geojson?url';
-
-import dot_names from './layer_names.js';
-
-const allStates = {
- ...castleRockStates,
- ...travelIqStates,
- 'Alabama': al,
-}
-console.log(allStates, castleRockStates, travelIqStates);
-let dot_cams = {
+const dot_cams = {
name: "State DOT Cameras",
+ layers: [
+ ...castlerocklayers,
+ {
+ name: "Alabama: ALDOT/ALGO",
+ layer: al,
+ },
+ {
+ name: "WisDOT/511WI",
+ layer: wi,
+ },
+ ],
details: `Enable All`,
- layers: [],
};
-for (let [state, url] of Object.entries(allStates)) {
- const vectorLayer = new VectorLayer({
- source: new VectorSource({
- url: url,
- format: new GeoJSON,
- }),
- style: function(feature, resolution){
- return new Style({
- image: new Icon({
- anchor: [0.5, 1],
- src: feature.values_.views[0].hasVideo ? pinVideo : pin,
- }),
- });
- },
- });
-
- vectorLayer.customPopup = function(feature) {
- const view = feature.values_.views[0];
- if (view.hasVideo) {
- return `
${feature.values_.name}
`; - } else { - return `