diff --git a/gpx/src/index.ts b/gpx/src/index.ts index f2fe5f3f..94790a79 100644 --- a/gpx/src/index.ts +++ b/gpx/src/index.ts @@ -1,5 +1,5 @@ export * from './gpx'; -export { Coordinates, LineStyleExtension } from './types'; +export { Coordinates, LineStyleExtension, WaypointType } from './types'; export { parseGPX, buildGPX } from './io'; export * from './simplify'; diff --git a/website/package-lock.json b/website/package-lock.json index 76a349ba..6fc955e2 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -10,12 +10,15 @@ "dependencies": { "@internationalized/date": "^3.5.4", "@mapbox/mapbox-gl-geocoder": "^5.0.2", + "@mapbox/sphericalmercator": "^1.2.0", + "@types/mapbox__sphericalmercator": "^1.2.3", "bits-ui": "^0.21.12", "chart.js": "^4.4.3", "clsx": "^2.1.1", "dexie": "^4.0.7", "gpx": "file:../gpx", "immer": "^10.1.1", + "lucide-static": "^0.408.0", "lucide-svelte": "^0.395.0", "mapbox-gl": "^3.4.0", "mapillary-js": "^4.1.2", @@ -1422,6 +1425,17 @@ "polyline": "bin/polyline.bin.js" } }, + "node_modules/@mapbox/sphericalmercator": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz", + "integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==", + "bin": { + "bbox": "bin/bbox.js", + "to4326": "bin/to4326.js", + "to900913": "bin/to900913.js", + "xyz": "bin/xyz.js" + } + }, "node_modules/@mapbox/tiny-sdf": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", @@ -1962,6 +1976,11 @@ "@types/mapbox-gl": "*" } }, + "node_modules/@types/mapbox__sphericalmercator": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/mapbox__sphericalmercator/-/mapbox__sphericalmercator-1.2.3.tgz", + "integrity": "sha512-gBXMMNhRTA8HzAzLdBzVYET0dH1p8jDPYZoT9+KnfFRYIRwHnbW+3IyiSlwS7kvr97PMn501QY+Dd3kjxb2dAA==" + }, "node_modules/@types/mapbox-gl": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.1.0.tgz", @@ -4353,6 +4372,11 @@ "es5-ext": "~0.10.2" } }, + "node_modules/lucide-static": { + "version": "0.408.0", + "resolved": "https://registry.npmjs.org/lucide-static/-/lucide-static-0.408.0.tgz", + "integrity": "sha512-XJioz3vKagiyA6qMDWkYqU1RUS/bMjqio0/TCOItievnV/C4wwgJZGAbk6eVDe6Wv+d0e9NbhS7Y8yMEpGkElQ==" + }, "node_modules/lucide-svelte": { "version": "0.395.0", "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.395.0.tgz", diff --git a/website/package.json b/website/package.json index 8b6376d5..15093849 100644 --- a/website/package.json +++ b/website/package.json @@ -48,12 +48,15 @@ "dependencies": { "@internationalized/date": "^3.5.4", "@mapbox/mapbox-gl-geocoder": "^5.0.2", + "@mapbox/sphericalmercator": "^1.2.0", + "@types/mapbox__sphericalmercator": "^1.2.3", "bits-ui": "^0.21.12", "chart.js": "^4.4.3", "clsx": "^2.1.1", "dexie": "^4.0.7", "gpx": "file:../gpx", "immer": "^10.1.1", + "lucide-static": "^0.408.0", "lucide-svelte": "^0.395.0", "mapbox-gl": "^3.4.0", "mapillary-js": "^4.1.2", diff --git a/website/src/lib/assets/layers.ts b/website/src/lib/assets/layers.ts index e8a9734b..423fbc61 100644 --- a/website/src/lib/assets/layers.ts +++ b/website/src/lib/assets/layers.ts @@ -1,4 +1,5 @@ import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public'; +import { TramFront, Utensils, ShoppingBasket, Droplet, ShowerHead, Fuel, CircleParking, Fence, FerrisWheel, Telescope, Bed, Mountain, Pickaxe, Store, TrainFront, Bus, Ship, Croissant } from 'lucide-static'; import { type AnySourceData, type Style } from 'mapbox-gl'; export const basemaps: { [key: string]: string | Style; } = { @@ -540,6 +541,45 @@ export const overlayTree: LayerTreeType = { }, } +// Hierachy containing all Overpass layers +export const overpassTree: LayerTreeType = { + points_of_interest: { + food: { + bakery: true, + "food-store": true, + "eat-and-drink": true, + }, + amenities: { + toilets: true, + "water": true, + "water-spring": true, + shower: true, + "fuel-station": true, + parking: true, + barrier: true + }, + tourism: { + attraction: true, + viewpoint: true, + accommodation: true, + summit: true, + pass: true, + climbing: true, + }, + bicycle: { + "bicycle-parking": true, + "bicycle-rental": true, + "bicycle-shop": true + }, + "public-transport": { + "railway-station": true, + "tram-stop": true, + "bus-stop": true, + ferry: true + }, + }, +}; + // Default basemap used export const defaultBasemap = 'mapboxOutdoors'; @@ -585,6 +625,45 @@ export const defaultOverlays = { }, }; +// Default Overpass queries used (none) +export const defaultOverpassQueries: LayerTreeType = { + points_of_interest: { + "food": { + bakery: false, + "food-store": false, + "eat-and-drink": false, + }, + amenities: { + toilets: false, + "water": false, + "water-spring": false, + shower: false, + "fuel-station": false, + parking: false, + barrier: false + }, + tourism: { + attraction: false, + viewpoint: false, + accommodation: false, + summit: false, + pass: false, + climbing: false + }, + bicycle: { + "bicycle-parking": false, + "bicycle-rental": false, + "bicycle-shop": false + }, + "public-transport": { + "railway-station": false, + "tram-stop": false, + "bus-stop": false, + ferry: false + }, + }, +}; + // Default basemaps shown in the layer menu export const defaultBasemapTree: LayerTreeType = { basemaps: { @@ -680,6 +759,45 @@ export const defaultOverlayTree: LayerTreeType = { } } +// Default Overpass queries shown in the layer menu +export const defaultOverpassTree: LayerTreeType = { + points_of_interest: { + "food": { + bakery: true, + "food-store": true, + "eat-and-drink": true, + }, + amenities: { + toilets: true, + "water": true, + "water-spring": false, + shower: false, + "fuel-station": false, + parking: false, + barrier: false + }, + tourism: { + attraction: false, + viewpoint: false, + accommodation: true, + summit: true, + pass: true, + climbing: false + }, + bicycle: { + "bicycle-parking": false, + "bicycle-rental": false, + "bicycle-shop": true + }, + "public-transport": { + "railway-station": true, + "tram-stop": true, + "bus-stop": true, + ferry: false + }, + }, +}; + export type CustomLayer = { id: string, name: string, @@ -690,6 +808,226 @@ export type CustomLayer = { value: string | {}, }; +type OverpassQueryData = { + icon: { + svg: string, + color: string, + }, + tags: Record, +}; + +export const overpassQueryData: Record = { + "bakery": { + icon: { + svg: Croissant, + color: "Coral", + }, + tags: { + shop: "bakery" + } + }, + "food-store": { + icon: { + svg: ShoppingBasket, + color: "Coral", + }, + tags: { + shop: ["supermarket", "convenience"], + } + }, + "eat-and-drink": { + icon: { + svg: Utensils, + color: "Coral", + }, + tags: { + amenity: ["restaurant", "fast_food", "cafe", "pub", "bar"] + } + }, + "toilets": { + icon: { + svg: Droplet, + color: "DeepSkyBlue", + }, + tags: { + amenity: "toilets" + } + }, + water: { + icon: { + svg: Droplet, + color: "DeepSkyBlue", + }, + tags: { + amenity: ["drinking_water", "water_point"] + } + }, + "water-spring": { + icon: { + svg: Droplet, + color: "DeepSkyBlue", + }, + tags: { + natural: "spring", + drinking_water: "yes" + } + }, + shower: { + icon: { + svg: ShowerHead, + color: "DeepSkyBlue", + }, + tags: { + amenity: "shower" + } + }, + "fuel-station": { + icon: { + svg: Fuel, + color: "#000000", + }, + tags: { + amenity: "fuel" + } + }, + parking: { + icon: { + svg: CircleParking, + color: "#000000", + }, + tags: { + amenity: "parking" + } + }, + barrier: { + icon: { + svg: Fence, + color: "#000000", + }, + tags: { + barrier: true + } + }, + attraction: { + icon: { + svg: FerrisWheel, + color: "Green", + }, + tags: { + tourism: "attraction" + } + }, + viewpoint: { + icon: { + svg: Telescope, + color: "Green", + }, + tags: { + tourism: "viewpoint" + } + }, + accommodation: { + icon: { + svg: Bed, + color: "#e6c100", + }, + tags: { + tourism: ["hotel", "hostel", "guest_house", "motel", "camp_site", "alpine_hut", "wilderness_hut"] + } + }, + summit: { + icon: { + svg: Mountain, + color: "Green", + }, + tags: { + natural: "peak" + } + }, + pass: { + icon: { + svg: Mountain, + color: "Green", + }, + tags: { + mountain_pass: "yes" + } + }, + climbing: { + icon: { + svg: Pickaxe, + color: "Green", + }, + tags: { + sport: "climbing" + } + }, + "bicycle-parking": { + icon: { + svg: CircleParking, + color: "HotPink", + }, + tags: { + amenity: "bicycle_parking" + } + }, + "bicycle-rental": { + icon: { + svg: Store, + color: "HotPink", + }, + tags: { + amenity: "bicycle_rental" + } + }, + "bicycle-shop": { + icon: { + svg: Store, + color: "HotPink", + }, + tags: { + shop: "bicycle" + } + }, + "railway-station": { + icon: { + svg: TrainFront, + color: "DarkBlue", + }, + tags: { + railway: "station" + } + }, + "tram-stop": { + icon: { + svg: TramFront, + color: 'DarkBlue', + }, + tags: { + railway: "tram_stop" + }, + }, + "bus-stop": { + icon: { + svg: Bus, + color: "DarkBlue", + }, + tags: { + "public_transport": ["stop_position", "platform"], + bus: "yes" + } + }, + ferry: { + icon: { + svg: Ship, + color: "DarkBlue", + }, + tags: { + amenity: "ferry_terminal" + } + } +}; + export const stravaHeatmapServers = ['https://heatmap-external-a.strava.com/tiles-auth', 'https://heatmap-external-b.strava.com/tiles-auth', 'https://heatmap-external-c.strava.com/tiles-auth']; export const stravaHeatmapActivityIds: { [key: string]: string } = { stravaHeatmapRun: 'sport_Run', diff --git a/website/src/lib/components/gpx-layer/WaypointPopup.svelte b/website/src/lib/components/gpx-layer/WaypointPopup.svelte index 29ac9e1e..42bc1ad7 100644 --- a/website/src/lib/components/gpx-layer/WaypointPopup.svelte +++ b/website/src/lib/components/gpx-layer/WaypointPopup.svelte @@ -34,24 +34,22 @@ {/if} {#if $currentPopupWaypoint[0].desc} - {$currentPopupWaypoint[0].desc} + {$currentPopupWaypoint[0].desc} {/if} {#if $currentPopupWaypoint[0].cmt && $currentPopupWaypoint[0].cmt !== $currentPopupWaypoint[0].desc} - {$currentPopupWaypoint[0].cmt} + {$currentPopupWaypoint[0].cmt} {/if} {#if $currentTool === Tool.WAYPOINT} -
- -
+ {/if} diff --git a/website/src/lib/components/layer-control/LayerControl.svelte b/website/src/lib/components/layer-control/LayerControl.svelte index 0d82b9e8..1e85e68f 100644 --- a/website/src/lib/components/layer-control/LayerControl.svelte +++ b/website/src/lib/components/layer-control/LayerControl.svelte @@ -12,15 +12,20 @@ import { map } from '$lib/stores'; import { get, writable } from 'svelte/store'; import { getLayers } from './utils'; + import { OverpassLayer } from './OverpassLayer'; + import OverpassPopup from './OverpassPopup.svelte'; let container: HTMLDivElement; + let overpassLayer: OverpassLayer; const { currentBasemap, previousBasemap, currentOverlays, + currentOverpassQueries, selectedBasemapTree, selectedOverlayTree, + selectedOverpassTree, customLayers, opacities } = settings; @@ -54,6 +59,14 @@ }); } + $: if ($map) { + if (overpassLayer) { + overpassLayer.remove(); + } + overpassLayer = new OverpassLayer($map); + overpassLayer.add(); + } + let selectedBasemap = writable(get(currentBasemap)); selectedBasemap.subscribe((value) => { // Updates coming from radio buttons @@ -157,12 +170,25 @@ /> {/if} + +
+ {#if $currentOverpassQueries} + + {/if} +
+ + { if (open && !cancelEvents && !container.contains(e.target)) { diff --git a/website/src/lib/components/layer-control/LayerControlSettings.svelte b/website/src/lib/components/layer-control/LayerControlSettings.svelte index 99f02c36..39cf84aa 100644 --- a/website/src/lib/components/layer-control/LayerControlSettings.svelte +++ b/website/src/lib/components/layer-control/LayerControlSettings.svelte @@ -9,7 +9,7 @@ import * as Select from '$lib/components/ui/select'; import { Slider } from '$lib/components/ui/slider'; - import { basemapTree, overlays, overlayTree } from '$lib/assets/layers'; + import { basemapTree, overlays, overlayTree, overpassTree } from '$lib/assets/layers'; import { isSelected } from '$lib/components/layer-control/utils'; import { settings } from '$lib/db'; @@ -22,6 +22,7 @@ const { selectedBasemapTree, selectedOverlayTree, + selectedOverpassTree, stravaHeatmapColor, currentOverlays, customLayers, @@ -29,6 +30,7 @@ } = settings; export let open: boolean; + let accordionValue = 'layer-selection'; let selectedOverlay = writable(undefined); let overlayOpacity = writable([1]); @@ -113,115 +115,122 @@ {$_('layers.settings')} - - {$_('layers.settings_help')} - - - - {$_('layers.selection')} - - - - - - - - - - - - {$_('layers.opacity')} - -
- - - - - - - {#each Object.keys(overlays) as id} - {#if isSelected($selectedOverlayTree, id)} - {$_(`layers.label.${id}`)} - {/if} - {/each} - {#each Object.entries($customLayers) as [id, layer]} - {#if layer.layerType === 'overlay'} - {layer.name} - {/if} - {/each} - - -
-