diff --git a/package-lock.json b/package-lock.json index 5302b2d..750ec43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1015,6 +1015,22 @@ "regenerator-runtime": "^0.12.0" } }, + "@babel/runtime-corejs2": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.5.4.tgz", + "integrity": "sha512-sHv74OzyZ18d6tjHU0HmlVES3+l+lydkOMTiKsJSTGWcTBpIMfXLEgduahlJrQjknW9RCQAqLIEdLOHjBmq/hg==", + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + } + } + }, "@babel/template": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", @@ -1126,6 +1142,103 @@ "physical-cpu-count": "^2.0.0" } }, + "@turf/bbox": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-5.1.5.tgz", + "integrity": "sha1-MFHfUUrUxQ9KT5uKLRX9i2hA7aM=", + "requires": { + "@turf/helpers": "^5.1.5", + "@turf/meta": "^5.1.5" + } + }, + "@turf/boolean-point-in-polygon": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-5.1.5.tgz", + "integrity": "sha1-8BzBlNHgMKVIv9qYHLpDz9YpQbc=", + "requires": { + "@turf/helpers": "^5.1.5", + "@turf/invariant": "^5.1.5" + } + }, + "@turf/center": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-5.1.5.tgz", + "integrity": "sha1-RKss2VT2PA03dX9xWKmcPvURS4A=", + "requires": { + "@turf/bbox": "^5.1.5", + "@turf/helpers": "^5.1.5" + } + }, + "@turf/clone": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-5.1.5.tgz", + "integrity": "sha1-JT6NNUdxgZduM636tQoPAqfw42c=", + "requires": { + "@turf/helpers": "^5.1.5" + } + }, + "@turf/distance": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-5.1.5.tgz", + "integrity": "sha1-Oc8YIEu/h1h9cH5gmmARiQkVZAk=", + "requires": { + "@turf/helpers": "^5.1.5", + "@turf/invariant": "^5.1.5" + } + }, + "@turf/explode": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-5.1.5.tgz", + "integrity": "sha1-sSsvd0AEobSPYrqVsgocZVo94Rg=", + "requires": { + "@turf/helpers": "^5.1.5", + "@turf/meta": "^5.1.5" + } + }, + "@turf/helpers": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-5.1.5.tgz", + "integrity": "sha1-FTQFInq5M9AEpbuWQantmZ/L4M8=" + }, + "@turf/invariant": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-5.2.0.tgz", + "integrity": "sha1-8BUP9ykLOFd7c9CIt5MsHuCqkKc=", + "requires": { + "@turf/helpers": "^5.1.5" + } + }, + "@turf/meta": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-5.2.0.tgz", + "integrity": "sha1-OxrUhe4MOwsXdRMqMsOE1T5LpT0=", + "requires": { + "@turf/helpers": "^5.1.5" + } + }, + "@turf/nearest-point": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-5.1.5.tgz", + "integrity": "sha1-EgUN5Bw5hEMiTHl43g9iE5ANNPs=", + "requires": { + "@turf/clone": "^5.1.5", + "@turf/distance": "^5.1.5", + "@turf/helpers": "^5.1.5", + "@turf/meta": "^5.1.5" + } + }, + "@turf/point-on-feature": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-5.1.5.tgz", + "integrity": "sha1-MMfwMkMCd8ZBjZbSieRba/shP+c=", + "requires": { + "@turf/boolean-point-in-polygon": "^5.1.5", + "@turf/center": "^5.1.5", + "@turf/explode": "^5.1.5", + "@turf/helpers": "^5.1.5", + "@turf/nearest-point": "^5.1.5" + } + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", @@ -1929,6 +2042,11 @@ "upath": "^1.1.1" } }, + "chroma-js": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-1.4.1.tgz", + "integrity": "sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ==" + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -2142,8 +2260,7 @@ "core-js": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==", - "dev": true + "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" }, "core-util-is": { "version": "1.0.2", @@ -2623,6 +2740,11 @@ "node-addon-api": "^1.6.0" } }, + "debounce-promise": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", + "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==" + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -2750,6 +2872,15 @@ "randombytes": "^2.0.0" } }, + "distinct-colors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/distinct-colors/-/distinct-colors-1.0.4.tgz", + "integrity": "sha1-Z9/KV5S8qVDAHSUaUqozWG5a97Q=", + "requires": { + "chroma-js": "^1.1.1", + "mout": "^0.11.0" + } + }, "dom-serializer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", @@ -4740,6 +4871,11 @@ "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", "dev": true }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, "merge-source-map": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", @@ -4886,6 +5022,11 @@ } } }, + "mout": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/mout/-/mout-0.11.1.tgz", + "integrity": "sha1-ujYR318OWx/7/QEWa48C0fX6K5k=" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5144,6 +5285,11 @@ "rbush": "2.0.2" } }, + "ol-tilecache": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ol-tilecache/-/ol-tilecache-3.0.2.tgz", + "integrity": "sha512-oA+UM8juw775gVm0ZgH5WIV9DnDhfLYWnY7zxkpIR8FDZaoFiPa0idG+GFRdRokeCqMoeoNMSD8aRguFbDE9XA==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -5321,6 +5467,21 @@ "safe-buffer": "^5.1.1" } }, + "parse-color": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", + "integrity": "sha1-e3SLlag/A/FqlPU15S1/PZRlhhk=", + "requires": { + "color-convert": "~0.5.0" + }, + "dependencies": { + "color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=" + } + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -6329,6 +6490,14 @@ "inherits": "^2.0.1" } }, + "rxjs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", + "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -7028,6 +7197,11 @@ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -7294,8 +7468,7 @@ "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v8-compile-cache": { "version": "2.0.3", @@ -7358,6 +7531,23 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuelayers": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/vuelayers/-/vuelayers-0.11.4.tgz", + "integrity": "sha512-6asCq2vDCPM4mS60NGeEBGKiVVJZ/GACYtu7UUVPkhtdmvI7bSzKjKuv0cTQkwcgh6YRtqzAIzML0l9fwb4AQw==", + "requires": { + "@babel/runtime-corejs2": "^7.1.2", + "@turf/point-on-feature": "^5.1.5", + "debounce-promise": "^3.1.0", + "merge-descriptors": "^1.0.1", + "ol": "^5.3.1", + "ol-tilecache": "^3.0.1", + "parse-color": "^1.0.0", + "rxjs": "^6.3.3", + "uuid": "^3.3.2", + "whatwg-fetch": "^3.0.0" + } + }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -7391,6 +7581,11 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", diff --git a/package.json b/package.json index 37246b8..016f0b7 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,11 @@ "private": true, "dependencies": { "aprs-parser": "^1.0.4", + "distinct-colors": "^1.0.4", "ol": "^5.3.3", "vue": "^2.6.10", "vue-hot-reload-api": "^2.3.3", + "vuelayers": "^0.11.4", "ws": "^5.2.2" }, "devDependencies": { diff --git a/src/Map.vue b/src/Map.vue new file mode 100644 index 0000000..3d5eb62 --- /dev/null +++ b/src/Map.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/map.css b/src/map.css deleted file mode 100644 index 994ee16..0000000 --- a/src/map.css +++ /dev/null @@ -1,48 +0,0 @@ -html, body { - height: 100%; - margin: 0; -} -.map { - height: 100%; - width: 100%; -} -.ol-control.layer-toggles { - top: 0.5em; - right: 0.5em; - background-color: rgba(70, 115, 164, 0.7); - color: #eee; - white-space: nowrap; - max-height: calc(100vh - 1em); - overflow-y: auto; -} -.layer-toggles > div { - margin: 0.5em; - margin-right: 1em; -} -.ol-control.layer-toggles:hover { - background-color: rgba(0,60,136,0.7); -} -.layer-toggles > div > label { - display: block; -} -.expand { - display: none; -} -.expand + span::before { - content: '\25B6'; -} -.expand:checked + span::before { - content: '\25BC'; -} -.expand ~ .collapsible-content { - display: none; -} -.expand:checked ~ .collapsible-content { - display: block; -} -.collapsible-content { - margin-left: 0.8em; -} -.collapsible-content > label { - display: block; -} diff --git a/src/map.html b/src/map.html deleted file mode 100644 index 83f10f7..0000000 --- a/src/map.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - OpenLayers example - - -
- - - diff --git a/src/map.js b/src/map.js deleted file mode 100644 index 345278e..0000000 --- a/src/map.js +++ /dev/null @@ -1,314 +0,0 @@ -import 'ol/ol.css'; -import {Map as olMap, View} from 'ol'; -import {Control} from 'ol/control'; -import {Group as LayerGroup, Tile as TileLayer, Vector as VectorLayer} from 'ol/layer'; -import {OSM, Vector as VectorSource} from 'ol/source'; -import {GPX} from 'ol/format'; -import Feature from 'ol/Feature'; -import {fromLonLat} from 'ol/proj'; -import {Circle as CircleStyle, Icon, Fill, Stroke, Style, Text} from 'ol/style'; -import Projection from 'ol/proj/Projection'; -import {LineString, Point} from 'ol/geom'; - -import {readFileSync} from 'fs'; -const packetLog = readFileSync(__dirname + '/../IS_packets.txt', 'utf-8'); - -import {APRSParser} from 'aprs-parser'; - -let tile_layer = new TileLayer({source: new OSM()}); - -import route_data from 'gpx/*.gpx'; - -let routes = new LayerGroup({ - title: "Routes", - layers: Object.keys(route_data).map(name => new VectorLayer({ - title: name.replace(/_/g, " "), - source: new VectorSource({ - url: route_data[name], - format: new GPX() - }), - style: new Style({ - stroke: new Stroke( - {color: 'hsl(200, 90%, 30%)', width: 5} - )}) - })) -}); - - -let map = new olMap({ - target: 'map', - layers: [ - tile_layer, - routes - ], - view: new View({ - center: fromLonLat([-72.15, 43.90]), - zoom: 10 - }) -}); - -class ColorGenerator { - constructor(count) { - let mult = Math.floor(360 / count); - this.hues = Array.from(Array(count).keys()).map(x => x * mult); - - // Shuffle (this is not a great shuffle, but I don't care) - this.hues.forEach((current, index, arr) => { - let randomIndex = Math.floor(Math.random() * index); - [arr[index], arr[randomIndex]] = [arr[randomIndex], arr[index]]; - }); - } - get() { - return this.hues.pop(); - } -}; - -function transformGeometry(geometry) { - return geometry.transform(new Projection({code: "EPSG:4326"}), - tile_layer.getSource().getProjection()); -} - -function plotPaths(packets) { - let path_layers = new LayerGroup({title: "Station Paths"}); - map.addLayer(path_layers); - - let paths = packets - .filter(packet => packet.date > new Date("2018-07-14") && packet.date < new Date("2018-07-15")) - // filter to just positional data - .filter(packet => 'data' in packet && 'latitude' in packet.data) - // join into Arrays of points - .reduce((acc, packet) => { - let callsign = packet.from.toString().trim(); - if (!acc.has(callsign)) acc.set(callsign, []); - acc.get(callsign).push([packet.data.longitude, packet.data.latitude, packet]); - return acc; - }, new Map()); - - let colorGen = new ColorGenerator(paths.size); - - // plot on map - paths.forEach((points, callsign) => { - let color = 'hsl(' + colorGen.get() + ', 75%, 50%)'; - let path_layer = new VectorLayer({ - title: "Path", - source: new VectorSource({features: [ - new Feature({geometry: transformGeometry(new LineString(points))}) - ]}), - style: new Style({stroke: new Stroke({color: color, width: 2})}) - }); - - let points_layer = new VectorLayer({ - title: "Points", - source: new VectorSource({ - features: points.map(point => new Feature({ - geometry: transformGeometry(new Point(point)), - packet: point[3] // TODO: this seems a bit bad - })) - }), - style: new Style({ - image: new CircleStyle({ - radius: 4, - fill: new Fill({color: color}) - }) - }) - }); - - let callsign_layer = new LayerGroup({ - title: callsign, - layers: [path_layer, points_layer] - }); - path_layers.getLayers().push(callsign_layer); - }); -} - -function plotPacketPaths(packets) { - function pathToString(path) { - return path - .filter(station => !station.call.match(/WIDE[12]|qA?|UV[123]|.*\*$|UNCAN/)) - .map(station => station.toString().trim().replace(/\*$/, "")); - } - - let digiCalls = - new Set(packets - .map(packet => pathToString(packet.via)) - .reduce((acc, stations) => acc.concat(stations))); - - let digiPos = packets - // filter to digis - .filter(packet => digiCalls.has(packet.from.toString().trim())) - // filter to just positional data - .filter(packet => 'data' in packet && 'latitude' in packet.data) - // convert to callsign -> position mapping - .reduce((stations, packet) => - stations.set(packet.from.toString().trim(), - [packet.data.longitude, packet.data.latitude]), - new Map()); - - let colorGen = new ColorGenerator(digiPos.size); - let colorMap = Array.from(digiPos.keys()).reduce( - (acc, callsign, index) => - acc.set(callsign, colorGen.hues[index]), new Map()); - - // plot digis - // TODO: icons - let digi_style = new Style({ - image: new CircleStyle({ - radius: 5, - fill: new Fill({color: 'grey'}) - }), - text: new Text({ - font: 'bold 11px "Open Sans", "Arial Unicode MS", "sans-serif"', - overflow: true, - fill: new Fill({color: 'black'}) - }) - }); - let digi_layer = new VectorLayer({ - title: "Digipeater Labels", - zIndex: 1, // TODO: probably not the best way to do this - source: new VectorSource({ - features: Array.from(digiPos.keys()).map(callsign => - new Feature({ - geometry: transformGeometry(new Point(digiPos.get(callsign))), - callsign: callsign - }) - )}), - style: feature => { - digi_style.setText(new Text({ - text: feature.get('callsign'), - offsetY: 12 - })); - return digi_style; - } - }); - map.addLayer(digi_layer); - - let packet_path_layers = new LayerGroup({title: "Packet Paths"}); - let layers_map = new Map(); - map.addLayer(packet_path_layers); - packets - .filter(packet => packet.date > new Date("2018-07-14") && packet.date < new Date("2018-07-15")) - // filter by callsign - //.filter(packet => packet.from.toString() === "W1HS-9") - // filter to just positional data - .filter(packet => 'data' in packet && 'latitude' in packet.data) - .forEach(packet => { - pathToString(packet.via) - .forEach((station, index, stations) => { - if (digiPos.get(station) === undefined) { - console.log(station); - } - - // first point in path is originating station - let previous = (index === 0) ? - [packet.data.longitude, packet.data.latitude] : - digiPos.get(stations[index - 1]) || [0, 0]; - - let pathFeature = new Feature(transformGeometry( - new LineString([previous, digiPos.get(station) || [0, 0]]))); - - // TODO: want to color per station that hears it, probably means - // making a lot more features - let color = colorMap.get(station); - pathFeature.setStyle(new Style({ - stroke: new Stroke( - {color: 'hsl(' + color + ', 60%, 60%)', width: 2} - )})); - - if (!layers_map.has(station)) { - layers_map.set(station, new VectorLayer({ - title: station, - source: new VectorSource(), - renderMode: 'image' - })); - packet_path_layers.getLayers().push(layers_map.get(station)); - } - layers_map.get(station).getSource().addFeature(pathFeature); - }); - }); -} - -function layer_toggle(layer) { - if (layer.toggle_element === undefined) { - layer.toggle_element = document.createElement('label'); - - let checkbox = layer.toggle_element.appendChild( - document.createElement('input')); - checkbox.type = "checkbox"; - checkbox.checked = layer.getVisible(); - checkbox.addEventListener('change', event => { - layer.setVisible(event.target.checked); - }); - layer.toggle_element.appendChild( - document.createTextNode(layer.get('title'))); - } - return layer.toggle_element; -} - -function layer_toggles(layer, parentElement) { - if (layer instanceof LayerGroup) { - if (layer.group_toggle === undefined) { - let label = layer.group_toggle = parentElement.appendChild( - document.createElement('label')); - let input = label.appendChild(document.createElement('input')); - input.type = 'checkbox'; - input.className = "expand"; - label.appendChild(document.createElement('span')); - - label.appendChild(layer_toggle(layer)); // whole LayerGroup - - let child_toggle_element = label.appendChild( - document.createElement('input')); - child_toggle_element.type = 'checkbox'; - child_toggle_element.checked = true; - child_toggle_element.addEventListener('change', event => { - layer.getLayers().forEach(subLayer => { - subLayer.setVisible(event.target.checked); - subLayer.toggle_element.querySelector("input").checked = - event.target.checked; - }); - }); - - let container = label.appendChild(document.createElement('div')); - container.className = 'collapsible-content'; - layer.getLayers().forEach( - subLayer => layer_toggles(subLayer, container)); - return label; - } - } - else { - parentElement.appendChild(layer_toggle(layer)); - } -} - -function render_layer_toggles(event, element) { - event.map.getLayers().getArray() - .filter(layer => layer.get('title') !== undefined) - .forEach(layer => layer_toggles(layer, element)); -} - -(function makeLayerToogleControl() { - let element = document.createElement('div'); - element.className = 'layer-toggles ol-unselectable ol-control'; - let inner = element.appendChild(document.createElement('div')); - - let control = new Control( - {element: element, - render: event => render_layer_toggles(event, inner)}); - map.addControl(control); -})(); - - -function parsePackets(packetLog) { - let parser = new APRSParser(); - return packetLog.trim().split("\n") - // parse to Date and APRS packet - .map(line => { - let packet = parser.parse(line.slice(29)); - packet.date = new Date(line.slice(0,18)); - return packet; - }); -} - -let packets = parsePackets(packetLog); -plotPaths(packets); -plotPacketPaths(packets);