diff --git a/db/util/meilisearch.go b/db/util/meilisearch.go
index 7c613948..1b70110c 100644
--- a/db/util/meilisearch.go
+++ b/db/util/meilisearch.go
@@ -42,10 +42,10 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record,
category = trailCategory.GetString("name")
}
- // polyline, err := getPolyline(app, r)
- // if err != nil {
- // polyline = ""
- // }
+ polyline, err := getPolyline(app, r)
+ if err != nil {
+ polyline = ""
+ }
domain := ""
if !author.GetBool("isLocal") {
@@ -78,9 +78,9 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record,
"thumbnail": thumbnail,
"gpx": r.GetString("gpx"),
"tags": tags,
- // "polyline": polyline,
- "domain": domain,
- "iri": r.GetString("iri"),
+ "polyline": polyline,
+ "domain": domain,
+ "iri": r.GetString("iri"),
"_geo": map[string]float64{
"lat": r.GetFloat("lat"),
"lng": r.GetFloat("lon"),
diff --git a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte
index 1afa8724..7baf8569 100644
--- a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte
+++ b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte
@@ -12,6 +12,7 @@
createPopupFromTrail,
FontawesomeMarker,
} from "$lib/util/maplibre_util";
+ import { decodePolyline } from "$lib/util/polyline_util";
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
@@ -19,6 +20,7 @@
import { ClusterLayer } from "$lib/vendor/maplibre-layer-manager/cluster-layer";
import { baseMapStyles } from "$lib/vendor/maplibre-layer-manager/layers";
import { LayerManager } from "$lib/vendor/maplibre-layer-manager/maplibre-layer-manager";
+ import { PreviewLayer } from "$lib/vendor/maplibre-layer-manager/preview-layer";
import { TerrainLayer } from "$lib/vendor/maplibre-layer-manager/terrain-layer";
import { TrailLayer } from "$lib/vendor/maplibre-layer-manager/trail-layer";
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
@@ -114,21 +116,21 @@
let mapLoaded: boolean = false;
const trailColors = [
- "#3549BB",
- "#592E9E",
- "#47A2CD",
- "#62D5BC",
- "#7DDE95",
- "#759E2E",
- "#BBA535",
- "#CD6F47",
- "#D5627B",
- "#DE7DC5",
+ "#3549bb", // blue
+ "#ff7f0e", // orange
+ "#2ca02c", // green
+ "#d62728", // red
+ "#9467bd", // purple
+ "#8c564b", // brown
+ "#e377c2", // pink
+ "#373642", // gray
+ "#fae455", // yellow
+ "#17becf", // teal
];
let clusterPopup: M.Popup | null = null;
- let [data, clusterData] = $derived(getData(trails));
+ let [data, clusterData, previewData] = $derived(getData(trails));
$effect(() => {
if (data && map && mapLoaded) {
untrack(() => initMap(map?.loaded() ?? false));
@@ -182,35 +184,61 @@
function getData(
trails: Trail[],
- ): [FeatureCollection[], FeatureCollection] {
+ ): [FeatureCollection[], FeatureCollection, FeatureCollection] {
let clusterData: FeatureCollection = {
type: "FeatureCollection",
features: [],
};
+ let previewData: FeatureCollection = {
+ type: "FeatureCollection",
+ features: [],
+ };
let r: FeatureCollection[] = [];
- for (const t of trails) {
+ trails.forEach((t, i) => {
if (t.expand?.gpx) {
r.push(t.expand.gpx.toGeoJSON());
} else if (t.expand?.gpx_data) {
r.push(GPX.parse(t.expand.gpx_data).toGeoJSON());
}
- if (clusterTrails && t.lat !== null && t.lon !== null) {
- clusterData.features.push({
- id: t.id,
- type: "Feature",
- properties: {
- trail: t.id,
- },
- geometry: {
- type: "Point",
- coordinates: [t.lon ?? 0, t.lat ?? 0],
- },
- } as Feature);
- }
- }
+ if (clusterTrails) {
+ if (t.lat !== null && t.lon !== null) {
+ clusterData.features.push({
+ id: t.id,
+ type: "Feature",
+ properties: {
+ trail: t.id,
+ },
+ geometry: {
+ type: "Point",
+ coordinates: [t.lon ?? 0, t.lat ?? 0],
+ },
+ } as Feature);
+ }
- return [r, clusterData];
+ if (t.polyline) {
+ previewData.features.push({
+ id: t.id,
+ type: "Feature",
+ properties: {
+ trail: t.id,
+ color: trailColors[
+ hashStringToIndex(
+ t.id ?? "",
+ trailColors.length,
+ )
+ ],
+ },
+ geometry: {
+ type: "LineString",
+ coordinates: decodePolyline(t.polyline, 5),
+ },
+ });
+ }
+ }
+ });
+
+ return [r, clusterData, previewData];
}
function initMap(mapLoaded: boolean) {
@@ -231,6 +259,7 @@
});
if (clusterTrails) {
addClusterLayer(clusterData);
+ addPreviewLayer(previewData);
}
Object.entries(layerManager.layers).forEach(([id, layer]) => {
@@ -330,11 +359,14 @@
if (!geojson || !map) {
return;
}
-
const trailLayer = new TrailLayer(
id,
geojson,
- trailColors[index % trailColors.length],
+ trailColors[
+ clusterTrails
+ ? hashStringToIndex(id ?? "", trailColors.length)
+ : 0
+ ],
{
onEnter: (e) =>
highlightTrail(id, trails[activeTrail ?? -1]?.id == id),
@@ -359,34 +391,15 @@
if (!geojson || !map || !map.style) {
return;
}
- layerManager.addLayer(
- "clusters",
- new ClusterLayer(map, geojson, {
- "unclustered-point": {
- onMouseDown: (e) => {
- const trail = trails.find(
- (t) =>
- t.id == (e as any).features[0].properties.trail,
- );
- if (!trail || !map) {
- return;
- }
- highlightCluster(trail);
- },
- },
- }),
- );
+ layerManager.addLayer("clusters", new ClusterLayer(map, geojson));
}
- function addClusterHighlightLayer(geojson: FeatureCollection) {
+ function addPreviewLayer(geojson: FeatureCollection) {
if (!geojson || !map || !map.style) {
return;
}
- layerManager.addLayer(
- "cluster-highlight",
- new TrailLayer("cluster-highlight", geojson, trailColors[0]),
- );
+ layerManager.addLayer("preview", new PreviewLayer(geojson));
}
function moveCrosshairToCursorPosition(e: M.MapMouseEvent) {
@@ -490,10 +503,6 @@
clusterPopup = createPopupFromTrail(trail);
clusterPopup.setLngLat([trail.lon!, trail.lat!]).addTo(map);
- const gpx = await fetchGPX(trail);
-
- addClusterHighlightLayer(GPX.parse(gpx).toGeoJSON());
-
clusterPopup.on("close", () => {
unHighlightCluster(false);
});
@@ -927,6 +936,15 @@
}
}
}
+
+ function hashStringToIndex(str: string, max: number) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = (hash << 5) - hash + str.charCodeAt(i);
+ hash |= 0;
+ }
+ return Math.abs(hash) % max;
+ }
diff --git a/web/src/lib/stores/valhalla_store.svelte.ts b/web/src/lib/stores/valhalla_store.svelte.ts
index 5fff3cab..baf5d66c 100644
--- a/web/src/lib/stores/valhalla_store.svelte.ts
+++ b/web/src/lib/stores/valhalla_store.svelte.ts
@@ -104,7 +104,7 @@ export async function calculateRouteBetween(startLat: number, startLon: number,
const points = decodePolyline(shape);
const startTime = new Date().getTime();
- const waypoints = points.map((p, i) => new Waypoint({ $: { lat: p[0], lon: p[1] }, ele: heightResponse.height[i], time: new Date(startTime + (((duration * 1000) / points.length) * i)) }))
+ const waypoints = points.map((p, i) => new Waypoint({ $: { lat: p[1], lon: p[0] }, ele: heightResponse.height[i], time: new Date(startTime + (((duration * 1000) / points.length) * i)) }))
return waypoints
}
diff --git a/web/src/lib/util/polyline_util.ts b/web/src/lib/util/polyline_util.ts
index 28ec3f6d..44468794 100644
--- a/web/src/lib/util/polyline_util.ts
+++ b/web/src/lib/util/polyline_util.ts
@@ -61,7 +61,7 @@ export function decodePolyline(str: string, precision: number = 6) {
lat += latitude_change;
lng += longitude_change;
- coordinates.push([lat / factor, lng / factor]);
+ coordinates.push([lng / factor, lat / factor]);
}
return coordinates;
diff --git a/web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts b/web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts
index 717c0eb4..ef5885bb 100644
--- a/web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts
+++ b/web/src/lib/vendor/maplibre-layer-manager/cluster-layer.ts
@@ -11,11 +11,12 @@ export class ClusterLayer implements BaseLayer {
listeners: Record void; onMouseDown?: (e: MapMouseEvent) => void; onEnter?: (e: MapMouseEvent) => void; onLeave?: (e: MapMouseEvent) => void; onMouseMove?: (e: MapMouseEvent) => void; }> = {
"clusters": {
- onMouseDown: this.zoomOnCluster.bind(this),
+ onMouseUp: this.zoomOnCluster.bind(this),
onEnter: () => this.map!.getCanvas().style.cursor = "pointer",
onLeave: () => this.map!.getCanvas().style.cursor = ""
},
"unclustered-point": {
+ onMouseUp: this.zoomOnUnclusteredPoint.bind(this),
onEnter: () => this.map!.getCanvas().style.cursor = "pointer",
onLeave: () => this.map!.getCanvas().style.cursor = ""
}
@@ -45,6 +46,7 @@ export class ClusterLayer implements BaseLayer {
type: "circle",
source: "cluster-trails",
filter: ["has", "point_count"],
+ maxzoom: 10,
paint: {
"circle-color": "#242734",
"circle-radius": [
@@ -71,6 +73,7 @@ export class ClusterLayer implements BaseLayer {
type: "symbol",
source: "cluster-trails",
filter: ["has", "point_count"],
+ maxzoom: 10,
paint: {
"text-color": "#fff",
},
@@ -84,6 +87,7 @@ export class ClusterLayer implements BaseLayer {
id: "unclustered-point",
type: "circle",
source: "cluster-trails",
+ maxzoom: 10,
filter: ["!", ["has", "point_count"]],
paint: {
"circle-color": "#242734",
@@ -108,6 +112,17 @@ export class ClusterLayer implements BaseLayer {
this.map.flyTo({
center: (features[0].geometry as any).coordinates,
zoom,
+ maxDuration: 3000
+ });
+ }
+
+ private zoomOnUnclusteredPoint(e: MapMouseEvent) {
+ const coordinates = (e as any).features[0].geometry.coordinates.slice();
+
+ this.map.flyTo({
+ center: coordinates,
+ zoom: 12,
+ maxDuration: 3000
});
}
}
\ No newline at end of file
diff --git a/web/src/lib/vendor/maplibre-layer-manager/preview-layer.ts b/web/src/lib/vendor/maplibre-layer-manager/preview-layer.ts
new file mode 100644
index 00000000..2d9d6e08
--- /dev/null
+++ b/web/src/lib/vendor/maplibre-layer-manager/preview-layer.ts
@@ -0,0 +1,63 @@
+import type { StyleSpecification } from "maplibre-gl";
+import type { BaseLayer } from "./layers";
+
+export class PreviewLayer implements BaseLayer {
+
+ spec: StyleSpecification;
+
+ constructor(geojson: GeoJSON.FeatureCollection) {
+ const startPoints: GeoJSON.FeatureCollection = {
+ type: "FeatureCollection",
+ features: geojson.features.map((f, i) => ({
+ type: "Feature",
+ properties: {
+ ...f.properties,
+ id: i
+ },
+ geometry: {
+ type: "Point",
+ coordinates: (f.geometry as any).coordinates[0]
+ }
+ }))
+ };
+ this.spec = {
+ version: 8,
+ name: "preview",
+ sources: {
+ "preview": {
+ type: "geojson",
+ data: geojson,
+ },
+ "preview-start-point": {
+ type: "geojson",
+ data: startPoints,
+ }
+ },
+ layers: [
+ {
+ id: "preview",
+ type: "line",
+ source: "preview",
+ minzoom: 10,
+ paint: {
+ "line-color": ["get", "color"],
+ "line-width": 5,
+ },
+ },
+ {
+ id: "preview-start-point",
+ type: "circle",
+ source: "preview-start-point",
+ minzoom: 10,
+ paint: {
+ "circle-color": "#242734",
+ "circle-radius": 6,
+ "circle-stroke-width": 2,
+ "circle-stroke-color": "#fff",
+ },
+ }
+ ]
+
+ };
+ }
+}
\ No newline at end of file
diff --git a/web/src/routes/map/+page.svelte b/web/src/routes/map/+page.svelte
index cea10ba2..9b0e2b36 100644
--- a/web/src/routes/map/+page.svelte
+++ b/web/src/routes/map/+page.svelte
@@ -46,7 +46,7 @@
const maxBoundingBox: TrailBoundingBox = page.data.boundingBox;
const settings: Settings = page.data.settings;
- const MIN_ZOOM = 100;
+ const MIN_ZOOM = 10;
let loading: boolean = $state(true);
let loadingNextPage: boolean = false;
@@ -421,6 +421,7 @@
oninit={handleMapInit}
{trails}
showElevation={false}
+ showTerrain={true}
showInfoPopup={true}
activeTrail={-1}
fitBounds="off"