diff --git a/web/public/sdf/cherrypicker.png b/web/public/sdf/cherrypicker.png new file mode 100644 index 0000000..1a33ed4 Binary files /dev/null and b/web/public/sdf/cherrypicker.png differ diff --git a/web/public/sdf/golf-buggy.png b/web/public/sdf/golf-buggy.png new file mode 100644 index 0000000..e57f304 Binary files /dev/null and b/web/public/sdf/golf-buggy.png differ diff --git a/web/public/sdf/parking.png b/web/public/sdf/parking.png new file mode 100644 index 0000000..4571c14 Binary files /dev/null and b/web/public/sdf/parking.png differ diff --git a/web/public/sdf/telehandler.png b/web/public/sdf/telehandler.png new file mode 100644 index 0000000..a5ae65d Binary files /dev/null and b/web/public/sdf/telehandler.png differ diff --git a/web/src/index.ts b/web/src/index.ts index 0692fea..1fbabdb 100644 --- a/web/src/index.ts +++ b/web/src/index.ts @@ -8,7 +8,7 @@ import URLHash from '@russss/maplibregl-layer-switcher/urlhash' import DistanceMeasure from './distancemeasure' import ContextMenu from './contextmenu' import VillagesEditor from './villages' -import { roundPosition } from './util' +import { roundPosition, fetchWithTimeout } from './util' import InstallControl from './installcontrol' import ExportControl from './export/export' import { manifest } from 'virtual:render-svg' @@ -33,14 +33,40 @@ async function loadIcons(map: maplibregl.Map) { }) .map((f) => f()) ) - /* - const sdfs = ['parking'] + + const sdfs = ['telehandler', 'golf-buggy', 'cherrypicker'] for (const sdf of sdfs) { const img = await map.loadImage(`/sdf/${sdf}.png`) map.addImage(sdf, img.data, { sdf: true }) } - */ +} + +let lastSuccessfulFetch = 0 + +function updateVehicles(map: maplibregl.Map) { + if (map.getLayer('vehicles')?.visibility == 'none') return + + fetchWithTimeout('https://geojson.thinkl33t.co.uk/') + .then((response) => response.json()) + .then((data) => { + const source = map.getSource('vehicles') as maplibregl.GeoJSONSource + if (!source) return + source.setData(data) + lastSuccessfulFetch = Date.now() + }) + .catch((error) => { + console.error(error) + if (Date.now() - lastSuccessfulFetch < 60000) return + const source = map.getSource('vehicles') as maplibregl.GeoJSONSource + if (!source) return + source.setData({ type: 'FeatureCollection', features: [] }) + }) +} + +function initVehicles(map: maplibregl.Map) { + window.setTimeout(() => updateVehicles(map), 0) + window.setInterval(() => updateVehicles(map), 20000) } class EventMap { @@ -58,6 +84,7 @@ class EventMap { Power: 'power_', Lighting: 'lighting_', Villages: 'villages_', + 'Vehicle tracking': 'vehicles', } map?: maplibregl.Map layer_switcher?: LayerSwitcher @@ -142,6 +169,12 @@ class EventMap { const [lng, lat] = roundPosition([coords.lng, coords.lat], this.map!.getZoom()) navigator.clipboard.writeText(lat + ', ' + lng) }) + + initVehicles(this.map) + + this.map.on('styledata', () => { + updateVehicles(this.map!) + }) } } diff --git a/web/src/style/map_style.ts b/web/src/style/map_style.ts index 9d128da..e11f347 100644 --- a/web/src/style/map_style.ts +++ b/web/src/style/map_style.ts @@ -975,6 +975,54 @@ const layers: LayerSpecificationWithZIndex[] = [ }, paint: {}, }, + { + id: 'vehicles', + type: 'symbol', + source: 'vehicles', + minzoom: 14, + filter: ['match', ['get', 'icon'], ['telehandler', 'cherrypicker', 'golf-buggy'], true, false], + layout: { + 'icon-image': [ + 'match', + ['get', 'icon'], + 'telehandler', + 'telehandler', + 'cherrypicker', + 'cherrypicker', + 'golf-buggy', + ], + 'icon-size': ['interpolate', ['linear'], ['zoom'], 14, 0.1, 24, 1], + 'icon-allow-overlap': true, + 'text-field': '{name}', + 'text-font': ['Open Sans Regular'], + 'text-size': ['step', ['zoom'], 0, 17, 12], + 'text-optional': true, + 'text-offset': [ + 'interpolate', + ['linear'], + ['zoom'], + 17, + ['literal', [0, 2]], + 24, + ['literal', [0, 6]], + ], + }, + paint: { + 'icon-color': [ + 'match', + ['get', 'icon'], + 'cherrypicker', + 'rgba(100, 100, 255, 1)', + 'golf-buggy', + 'rgba(100, 100, 100, 1)', + 'rgba(255, 50, 50, 1)', + ], + 'icon-halo-color': 'rgba(30, 30, 30, 1)', + 'icon-halo-width': ['step', ['zoom'], 0, 17.4, 2], + 'text-halo-color': 'rgba(255, 255, 255, 1)', + 'text-halo-width': 2, + }, + }, { id: 'villages_text', type: 'symbol', @@ -1160,6 +1208,10 @@ const style: StyleSpecification = { type: 'raster', tiles: ['https://map.emfcamp.org/data/ortho/{z}/{x}/{y}'], }, + vehicles: { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }, }, sprite: 'https://openmaptiles.github.io/positron-gl-style/sprite', glyphs: 'https://map.emfcamp.org/fonts/{fontstack}/{range}.pbf',