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 ``; - } - }; - - vectorLayer.customPopupCallback = function(feature) { - const view = feature.values_.views[0]; - if (view.hasVideo) { - const video = document.getElementById('popupVideo'); - - if (Hls.isSupported()) { - var hls = new Hls(); - hls.loadSource(view.src); - hls.attachMedia(video); - } - // iDevice support, untested (only works in Safari; required for iPhones) - else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = view.src; - } - } - } - dot_cams.layers.push({ - name: dot_names[state] ?? state, - layer: vectorLayer, - }); -} - -dot_cams.layers.sort((a, b) => a.name > b.name ? 1 : -1); // Names are always unique - export default dot_cams; diff --git a/layers/dot-cams/layer_names.js b/layers/dot-cams/layer_names.js deleted file mode 100644 index 2bf0f7a..0000000 --- a/layers/dot-cams/layer_names.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - 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", -}; diff --git a/layers/dot-cams/pin-video.svg b/layers/dot-cams/pin-video.svg deleted file mode 100644 index 0e0daa8..0000000 --- a/layers/dot-cams/pin-video.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/layers/dot-cams/travel-iq/get_data.py b/layers/dot-cams/travel-iq/get_data.py deleted file mode 100755 index 29f49e1..0000000 --- a/layers/dot-cams/travel-iq/get_data.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/python3 - -import requests -import json - -states = { - "Georgia": "https://511ga.org/", - "Louisiana": "https://www.511la.org/", - "Nevada": "https://www.nvroads.com/", - "NewYork": "https://www.511ny.org/", - "Wisconsin": "https://511wi.gov/" -} - -for state, baseURL in states.items(): - query={ - "columns": [ # no clue what any of this is, so here it stays - { - "data": None, - "name": "", - }, - { - "name": "sortId", - "s": True, - }, - { - "name": "region", - "s": True, - }, - { - "name": "county", - "s": True, - }, - { - "name": "roadway", - "s": True, - }, - { - "name": "description1", - }, - { - "data": 6, - "name": "", - }, - ], - "start": 0, - "length": 100, - } - - cameras = [] - available_cameras = 999_999 # lots - - while len(cameras) < available_cameras: - res = requests.get(f"{baseURL}/List/GetData/Cameras", { - "query": json.dumps(query), - "lang": "en", - }) - res.raise_for_status() - res = res.json() - available_cameras = res['recordsTotal'] - for c in res['data']: - cameras.append({ - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [c['longitude'], c['latitude']], # yes, [lon, lat] since it's [x, y] - }, - "properties": { - 'name': c['displayName'], - 'views': [{ - 'hasVideo': c['videoUrl'], - 'src': c['videoUrl'][0] if isinstance(c['videoUrl'], list) else c['videoUrl'], # LA returns multiple (identical?) streams - }], - }, - }) - query['start'] += 100 - - geojson = { - "type": "FeatureCollection", - "features": cameras, - } - - with open(f"data/{state}.geojson", "w") as f: - f.write(json.dumps(geojson)) - - print(f"{len(cameras)} locations found for {state}") - -# hack hack hack -# -# If I write this to one big file, I can't take advantage of any lazy loading -# for performance reasons, so I'm constrained to having a bunch of files. I -# can't programmatically import those, since es6 imports don't allow for that. -# So, codegen it is (and fairly gross codegen at that!). -with open('data/states.js', 'w') as f: - for state in states: - f.write(f"import {state} from './{state}.geojson?url';\n") - f.write('\nexport default {\n') - for state in states: - f.write(f" {state}: {state},\n") - f.write("};\n") diff --git a/layers/dot-cams/wi/get_data.py b/layers/dot-cams/wi/get_data.py new file mode 100755 index 0000000..c21fb45 --- /dev/null +++ b/layers/dot-cams/wi/get_data.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +import requests +import json + +query={ + "columns": [ # no clue what any of this is, so here it stays + { + "data": None, + "name": "", + }, + { + "name": "sortId", + "s": True, + }, + { + "name": "region", + "s": True, + }, + { + "name": "county", + "s": True, + }, + { + "name": "roadway", + "s": True, + }, + { + "name": "description1", + }, + { + "data": 6, + "name": "", + }, + ], + "start": 0, + "length": 100, +} + +cameras = [] +available_cameras = 999_999 # lots + +while len(cameras) < available_cameras: + res = requests.get("https://511wi.gov/List/GetData/Cameras", { + "query": json.dumps(query), + "lang": "en", + }) + res.raise_for_status() + res = res.json() + available_cameras = res['recordsTotal'] + for c in res['data']: + cameras.append({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [c['longitude'], c['latitude']], # yes, [lon, lat] since it's [x, y] + }, + "properties": { + 'address': c['displayName'], + 'website': c['videoUrl'], + 'originalData': c, + }, + }) + query['start'] += 100 + +geojson = { + "type": "FeatureCollection", + "features": cameras, +} + +with open("data.geojson", "w") as f: + f.write(json.dumps(geojson)) + +print(f"{len(cameras)} locations found") diff --git a/layers/dot-cams/wi/layer.js b/layers/dot-cams/wi/layer.js new file mode 100644 index 0000000..7a8a6e3 --- /dev/null +++ b/layers/dot-cams/wi/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.videoUrl; + 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/wi/pin.svg b/layers/dot-cams/wi/pin.svg new file mode 100644 index 0000000..fb93212 --- /dev/null +++ b/layers/dot-cams/wi/pin.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + +