adds mapwithelevationmultiple to map page
This commit is contained in:
@@ -40,7 +40,6 @@
|
|||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip:before,
|
.tooltip:before,
|
||||||
@@ -52,6 +51,7 @@
|
|||||||
|
|
||||||
transform: translate3d(0, -10px, 0);
|
transform: translate3d(0, -10px, 0);
|
||||||
transition: all .15s ease-in-out;
|
transition: all .15s ease-in-out;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip:before {
|
.tooltip:before {
|
||||||
|
|||||||
@@ -214,7 +214,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="map-container" class="flex flex-col h-full">
|
<div id="map-container" class="flex flex-col h-full relative">
|
||||||
<div
|
<div
|
||||||
id="map"
|
id="map"
|
||||||
class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
|
class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
|
||||||
|
|||||||
@@ -81,10 +81,6 @@
|
|||||||
await import("$lib/vendor/leaflet-elevation/src/index.js");
|
await import("$lib/vendor/leaflet-elevation/src/index.js");
|
||||||
await import("$lib/vendor/leaflet-elevation/libs/leaflet-gpxgroup");
|
await import("$lib/vendor/leaflet-elevation/libs/leaflet-gpxgroup");
|
||||||
|
|
||||||
const AutoGraticule = (
|
|
||||||
await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")
|
|
||||||
).default;
|
|
||||||
|
|
||||||
map = L.map("map", {
|
map = L.map("map", {
|
||||||
preferCanvas: true,
|
preferCanvas: true,
|
||||||
plugins: ["/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js"],
|
plugins: ["/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js"],
|
||||||
@@ -95,6 +91,8 @@
|
|||||||
],
|
],
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dispatch("init", map);
|
||||||
map!.attributionControl.setPrefix(false);
|
map!.attributionControl.setPrefix(false);
|
||||||
|
|
||||||
map!.on("zoomend", function () {
|
map!.on("zoomend", function () {
|
||||||
@@ -105,6 +103,10 @@
|
|||||||
dispatch("click", e);
|
dispatch("click", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
map!.on("moveend", function (e: any) {
|
||||||
|
dispatch("moveend", e);
|
||||||
|
});
|
||||||
|
|
||||||
const baseLayer = L.tileLayer(
|
const baseLayer = L.tileLayer(
|
||||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
{
|
{
|
||||||
@@ -203,12 +205,11 @@
|
|||||||
|
|
||||||
gpxGroup = L.gpxGroup(gpxData, {
|
gpxGroup = L.gpxGroup(gpxData, {
|
||||||
points: [],
|
points: [],
|
||||||
// points_options: opts.points,
|
|
||||||
elevation: true,
|
elevation: true,
|
||||||
elevation_options: elevation_options,
|
elevation_options: elevation_options,
|
||||||
flyToBounds: true,
|
flyToBounds: options.flyToBounds,
|
||||||
distanceMarkers: true,
|
distanceMarkers: true,
|
||||||
removeElevationOnDeselect: options.removeElevationOnDeselect,
|
itinerary: options.itinerary,
|
||||||
});
|
});
|
||||||
|
|
||||||
const markerLayerGroup = L.layerGroup().addTo(map);
|
const markerLayerGroup = L.layerGroup().addTo(map);
|
||||||
@@ -234,8 +235,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
gpxGroup.on("route_loaded", ({ route }: { route: any }) => {
|
gpxGroup.on("route_loaded", ({ route }: { route: any }) => {
|
||||||
|
const trail = trails.find(
|
||||||
const trail = trails.find((t) => gpxGroup?._hashCode(t.expand.gpx_data) == route.options.hash)
|
(t) =>
|
||||||
|
gpxGroup?._hashCode(t.expand.gpx_data) ==
|
||||||
|
route.options.hash,
|
||||||
|
);
|
||||||
|
|
||||||
if (!trail) {
|
if (!trail) {
|
||||||
return;
|
return;
|
||||||
@@ -275,6 +279,14 @@
|
|||||||
gpxGroup.select(index);
|
gpxGroup.select(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openPopup(index: number) {
|
||||||
|
gpxGroup.openPopup(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closePopup(index: number) {
|
||||||
|
gpxGroup.closePopup(index);
|
||||||
|
}
|
||||||
|
|
||||||
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
|
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
|
||||||
if (metric === selectedMetric) {
|
if (metric === selectedMetric) {
|
||||||
return;
|
return;
|
||||||
@@ -293,10 +305,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="map-container" class="flex flex-col h-full">
|
<div id="map-container" class="h-full relative">
|
||||||
<div
|
<div
|
||||||
id="map"
|
id="map"
|
||||||
class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
|
class="rounded-xl z-0 h-full min-h-96 md:min-h-0"
|
||||||
style="position: relative; outline-style: none;"
|
style="position: relative; outline-style: none;"
|
||||||
>
|
>
|
||||||
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
|
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
|
||||||
@@ -314,8 +326,5 @@
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="absolute bottom-0 bg-background/80 w-full" id="elevation"></div>
|
||||||
<slot />
|
|
||||||
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -147,6 +147,23 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
|
|
||||||
this.setSelection(this._routes[index])
|
this.setSelection(this._routes[index])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openPopup(index) {
|
||||||
|
if (index > this._routes.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._routes[index].openPopup();
|
||||||
|
},
|
||||||
|
|
||||||
|
closePopup(index) {
|
||||||
|
if (index > this._routes.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._routes[index].closePopup();
|
||||||
|
},
|
||||||
|
|
||||||
_addTrack: function (track) {
|
_addTrack: function (track) {
|
||||||
if (track instanceof Object) {
|
if (track instanceof Object) {
|
||||||
this._loadGeoJSON(track);
|
this._loadGeoJSON(track);
|
||||||
@@ -156,7 +173,7 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_hashCode: (s) => s.split('').reduce((a,b) => (((a << 5) - a) + b.charCodeAt(0))|0, 0),
|
_hashCode: (s) => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0),
|
||||||
|
|
||||||
clear: function () {
|
clear: function () {
|
||||||
this._elevation.clear()
|
this._elevation.clear()
|
||||||
@@ -190,9 +207,8 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
|
|
||||||
_loadRoute: function (data) {
|
_loadRoute: function (data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
var line_style = {
|
var line_style = {
|
||||||
color: this._uniqueColors(this._tracks.length)[this._count++],
|
color: this._hashToColor(data.hash),
|
||||||
opacity: 0.75,
|
opacity: 0.75,
|
||||||
weight: 5,
|
weight: 5,
|
||||||
distanceMarkers: this.options.distanceMarkers_options,
|
distanceMarkers: this.options.distanceMarkers_options,
|
||||||
@@ -241,7 +257,7 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
});
|
});
|
||||||
const latlngs = polyline.getLatLngs();
|
const latlngs = polyline.getLatLngs();
|
||||||
|
|
||||||
if (this._loadedCount == 0) {
|
if (this._loadedCount == 0 || !this.options.itinerary) {
|
||||||
L.marker(latlngs[0], { icon: startIcon }).addTo(this._markers)
|
L.marker(latlngs[0], { icon: startIcon }).addTo(this._markers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,18 +349,25 @@ export const GpxGroup = L.GpxGroup = L.Class.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_uniqueColors: function (count) {
|
_hashToColor(hash) {
|
||||||
return count === 1 ? ['#0058ca'] : new Array(count).fill(null).map((_, i) => this._hsvToHex(i * (1 / count), 1, 0.7));
|
// Ensure hash is a positive integer
|
||||||
},
|
hash = Math.abs(hash);
|
||||||
|
|
||||||
_hsvToHex: function (h, s, v) {
|
// Define the maximum brightness (e.g., 200 ensures the color is not too light)
|
||||||
var i = Math.floor(h * 6);
|
const maxBrightness = 200;
|
||||||
var f = h * 6 - i;
|
|
||||||
var p = v * (1 - s);
|
// Convert the hash to a color in a reduced RGB range to prevent light colors
|
||||||
var q = v * (1 - f * s);
|
const r = (hash >> 16) & 0xFF; // Extract the red component
|
||||||
var t = v * (1 - (1 - f) * s);
|
const g = (hash >> 8) & 0xFF; // Extract the green component
|
||||||
var rgb = { 0: [v, t, p], 1: [q, v, p], 2: [p, v, t], 3: [p, q, v], 4: [t, p, v], 5: [v, p, q] }[i % 6];
|
const b = hash & 0xFF; // Extract the blue component
|
||||||
return rgb.map(d => d * 255).reduce((hex, byte) => hex + ((byte >> 4) & 0x0F).toString(16) + (byte & 0x0F).toString(16), "#");
|
|
||||||
|
// Adjust the RGB values to be within the allowed range (0 to maxBrightness)
|
||||||
|
const red = Math.floor((r / 255) * maxBrightness);
|
||||||
|
const green = Math.floor((g / 255) * maxBrightness);
|
||||||
|
const blue = Math.floor((b / 255) * maxBrightness);
|
||||||
|
|
||||||
|
// Format as HEX color
|
||||||
|
return `#${(1 << 24 | red << 16 | green << 8 | blue).toString(16).slice(1).toUpperCase()}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeFrom: function (map) {
|
removeFrom: function (map) {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<div class:hidden={!showMap}>
|
<div class:hidden={!showMap}>
|
||||||
<MapWithElevationMultiple trails={$list.expand?.trails ?? []} bind:map
|
<MapWithElevationMultiple trails={$list.expand?.trails ?? []} bind:map options={{itinerary: true, flyToBounds: true}}
|
||||||
></MapWithElevationMultiple>
|
></MapWithElevationMultiple>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0" class:hidden={showMap}>
|
<div class="min-w-0" class:hidden={showMap}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
type SearchItem,
|
type SearchItem,
|
||||||
} from "$lib/components/base/search.svelte";
|
} from "$lib/components/base/search.svelte";
|
||||||
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte";
|
||||||
|
import MapWithElevationMultiple from "$lib/components/trail/map_with_elevation_multiple.svelte";
|
||||||
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
||||||
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte";
|
||||||
import type { Settings } from "$lib/models/settings";
|
import type { Settings } from "$lib/models/settings";
|
||||||
@@ -19,21 +20,12 @@
|
|||||||
trails,
|
trails,
|
||||||
trails_search_bounding_box,
|
trails_search_bounding_box,
|
||||||
} from "$lib/stores/trail_store";
|
} from "$lib/stores/trail_store";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
|
||||||
import { country_codes } from "$lib/util/country_code_util";
|
import { country_codes } from "$lib/util/country_code_util";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
|
||||||
import {
|
|
||||||
formatDistance,
|
|
||||||
formatElevation,
|
|
||||||
formatTimeHHMM,
|
|
||||||
} from "$lib/util/format_util";
|
|
||||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||||
import type {
|
import type {
|
||||||
GPX,
|
GPX,
|
||||||
Icon,
|
|
||||||
LatLng,
|
LatLng,
|
||||||
LatLngBoundsExpression,
|
LatLngBoundsExpression,
|
||||||
LeafletEvent,
|
|
||||||
Map,
|
Map,
|
||||||
Marker,
|
Marker,
|
||||||
} from "leaflet";
|
} from "leaflet";
|
||||||
@@ -42,11 +34,8 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { slide } from "svelte/transition";
|
import { slide } from "svelte/transition";
|
||||||
let L: any;
|
|
||||||
let map: Map;
|
let map: Map;
|
||||||
let gpxLayers: Record<string, GPX> = {};
|
let mapWithElevation: MapWithElevationMultiple;
|
||||||
let startMarkers: Record<string, Marker> = {};
|
|
||||||
|
|
||||||
let searchDropdownItems: SearchItem[] = [];
|
let searchDropdownItems: SearchItem[] = [];
|
||||||
|
|
||||||
let showFilter: boolean = false;
|
let showFilter: boolean = false;
|
||||||
@@ -56,103 +45,7 @@
|
|||||||
const maxBoundingBox: TrailBoundingBox = $page.data.boundingBox;
|
const maxBoundingBox: TrailBoundingBox = $page.data.boundingBox;
|
||||||
const settings: Settings = $page.data.settings;
|
const settings: Settings = $page.data.settings;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {});
|
||||||
L = (await import("leaflet")).default;
|
|
||||||
await import("leaflet-gpx");
|
|
||||||
await import("leaflet.awesome-markers");
|
|
||||||
|
|
||||||
map = L.map("map").setView([0, 0], 4);
|
|
||||||
map.attributionControl.setPrefix(false);
|
|
||||||
|
|
||||||
const baseLayer = L.tileLayer(
|
|
||||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
||||||
{
|
|
||||||
attribution: "© OpenStreetMap contributors",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const topoLayer = L.tileLayer(
|
|
||||||
"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png ",
|
|
||||||
{
|
|
||||||
attribution: "© OpenStreetMap contributors",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseMaps = {
|
|
||||||
OpenStreetMaps: baseLayer,
|
|
||||||
OpenTopoMaps: topoLayer,
|
|
||||||
};
|
|
||||||
|
|
||||||
L.control.layers(baseMaps).addTo(map);
|
|
||||||
|
|
||||||
switch (localStorage.getItem("layer")) {
|
|
||||||
case "OpenTopoMaps":
|
|
||||||
topoLayer.addTo(map);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
baseLayer.addTo(map);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.on("baselayerchange", function (e) {
|
|
||||||
localStorage.setItem("layer", e.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
map.on("moveend", async (e) => {
|
|
||||||
const bounds = map.getBounds();
|
|
||||||
await searchTrails(bounds.getNorthEast(), bounds.getSouthWest());
|
|
||||||
|
|
||||||
$page.url.searchParams.set("tl_lat", bounds.getNorth().toString());
|
|
||||||
$page.url.searchParams.set("tl_lon", bounds.getEast().toString());
|
|
||||||
$page.url.searchParams.set("br_lat", bounds.getSouth().toString());
|
|
||||||
$page.url.searchParams.set("br_lon", bounds.getWest().toString());
|
|
||||||
|
|
||||||
goto(`?${$page.url.searchParams.toString()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const lat = $page.url.searchParams.get("lat");
|
|
||||||
const lon = $page.url.searchParams.get("lon");
|
|
||||||
|
|
||||||
const tl_lat = $page.url.searchParams.get("tl_lat");
|
|
||||||
const tl_lon = $page.url.searchParams.get("tl_lon");
|
|
||||||
const br_lat = $page.url.searchParams.get("br_lat");
|
|
||||||
const br_lon = $page.url.searchParams.get("br_lon");
|
|
||||||
|
|
||||||
if (lat && lon) {
|
|
||||||
map.setView([parseFloat(lat), parseFloat(lon)], 12);
|
|
||||||
} else if (tl_lat && tl_lon && br_lat && br_lon) {
|
|
||||||
const boundingBox: LatLngBoundsExpression = [
|
|
||||||
[parseFloat(tl_lat), parseFloat(tl_lon)],
|
|
||||||
[parseFloat(br_lat), parseFloat(br_lon)],
|
|
||||||
];
|
|
||||||
|
|
||||||
map.fitBounds(boundingBox);
|
|
||||||
} else if (settings && settings.mapFocus == "trails") {
|
|
||||||
const boundingBox: LatLngBoundsExpression = [
|
|
||||||
[maxBoundingBox.max_lat, maxBoundingBox.min_lon],
|
|
||||||
[maxBoundingBox.min_lat, maxBoundingBox.max_lon],
|
|
||||||
];
|
|
||||||
map.fitBounds(boundingBox);
|
|
||||||
} else if (
|
|
||||||
settings &&
|
|
||||||
settings.mapFocus == "location" &&
|
|
||||||
settings.location
|
|
||||||
) {
|
|
||||||
map.setView([settings.location.lat, settings.location.lon], 12);
|
|
||||||
} else {
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(position) => {
|
|
||||||
const lat = position.coords.latitude;
|
|
||||||
const lon = position.coords.longitude;
|
|
||||||
|
|
||||||
map.setView([lat, lon], 13);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error("Error getting user location:", error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function search(q: string) {
|
async function search(q: string) {
|
||||||
const r = await fetch("/api/v1/search/multi", {
|
const r = await fetch("/api/v1/search/multi", {
|
||||||
@@ -209,103 +102,59 @@
|
|||||||
southWest,
|
southWest,
|
||||||
filter,
|
filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const newTrail of changes.added) {
|
|
||||||
const gpxLayer = await addGPXLayer(newTrail);
|
|
||||||
|
|
||||||
if (gpxLayer) {
|
|
||||||
gpxLayers[newTrail.id!] = gpxLayer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const deletedTrail of changes.deleted) {
|
function handleTrailCardMouseEnter(index: number) {
|
||||||
map.removeLayer(gpxLayers[deletedTrail.id!]);
|
mapWithElevation.openPopup(index)
|
||||||
delete gpxLayers[deletedTrail.id!];
|
|
||||||
delete startMarkers[deletedTrail.id!];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addGPXLayer(trail: Trail) {
|
function handleTrailCardMouseLeave(index: number) {
|
||||||
return new Promise<GPX>(function (resolve, reject) {
|
mapWithElevation.closePopup(index)
|
||||||
if (!trail.expand.gpx_data) {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
const thumbnail = trail.photos.length
|
|
||||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
|
||||||
: "/imgs/default_thumbnail.webp";
|
|
||||||
const gpxLayer = new L.GPX(trail.expand.gpx_data!, {
|
|
||||||
async: true,
|
|
||||||
polyline_options: {
|
|
||||||
className: "lightblue-theme elevation-polyline",
|
|
||||||
weight: 5,
|
|
||||||
},
|
|
||||||
gpx_options: {
|
|
||||||
parseElements: ["track", "route"],
|
|
||||||
},
|
|
||||||
marker_options: {
|
|
||||||
startIcon: L.AwesomeMarkers.icon({
|
|
||||||
icon: "circle-half-stroke",
|
|
||||||
prefix: "fa",
|
|
||||||
markerColor: "cadetblue",
|
|
||||||
iconColor: "white",
|
|
||||||
}) as Icon,
|
|
||||||
startIconUrl: "",
|
|
||||||
endIconUrl: "",
|
|
||||||
shadowUrl: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.on("addpoint", function (e: any) {
|
|
||||||
if (e.point_type === "start") {
|
|
||||||
const marker: Marker = e.point as Marker;
|
|
||||||
startMarkers[trail.id!] = marker;
|
|
||||||
marker.bindPopup(
|
|
||||||
`<a href="map/trail/${trail.id}">
|
|
||||||
<li class="flex items-center gap-4 cursor-pointer text-black">
|
|
||||||
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${thumbnail}" alt="">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h4 class="font-semibold text-lg">${trail.name}</h4>
|
|
||||||
<div class="flex gap-x-4">
|
|
||||||
${trail.location ? `<h5><i class="fa fa-location-dot mr-2"></i>${trail.location}</h5>` : ""}
|
|
||||||
<h5><i class="fa fa-gauge mr-2"></i>${$_(trail.difficulty as string)}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="flex mt-2 gap-4 text-sm text-gray-500"><span class="shrink-0"><i
|
|
||||||
class="fa fa-left-right mr-2"></i>${formatDistance(
|
|
||||||
trail.distance,
|
|
||||||
)}</span> <span class="shrink-0"><i class="fa fa-up-down mr-2"></i>${formatElevation(
|
|
||||||
trail.elevation_gain,
|
|
||||||
)}</span> <span class="shrink-0"><i class="fa fa-clock mr-2"></i>${formatTimeHHMM(
|
|
||||||
trail.duration,
|
|
||||||
)}</span></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</a>`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("loaded", function (e: LeafletEvent) {
|
|
||||||
resolve(gpxLayer);
|
|
||||||
})
|
|
||||||
.on("error", reject)
|
|
||||||
.addTo(map);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTrailCardMouseEnter(trail: Trail) {
|
|
||||||
const marker: Marker = startMarkers[trail.id!];
|
|
||||||
marker?.openPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTrailCardMouseLeave(trail: Trail) {
|
|
||||||
const marker: Marker = startMarkers[trail.id!];
|
|
||||||
marker?.closePopup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFilterUpdate(filter: TrailFilter) {
|
async function handleFilterUpdate(filter: TrailFilter) {
|
||||||
const bounds = map.getBounds();
|
const bounds = map.getBounds();
|
||||||
await searchTrails(bounds.getNorthEast(), bounds.getSouthWest());
|
await searchTrails(bounds.getNorthEast(), bounds.getSouthWest());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleMapMove() {
|
||||||
|
const bounds = map.getBounds();
|
||||||
|
await searchTrails(bounds.getNorthEast(), bounds.getSouthWest());
|
||||||
|
|
||||||
|
$page.url.searchParams.set("tl_lat", bounds.getNorth().toString());
|
||||||
|
$page.url.searchParams.set("tl_lon", bounds.getEast().toString());
|
||||||
|
$page.url.searchParams.set("br_lat", bounds.getSouth().toString());
|
||||||
|
$page.url.searchParams.set("br_lon", bounds.getWest().toString());
|
||||||
|
|
||||||
|
goto(`?${$page.url.searchParams.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMapInit() {
|
||||||
|
if (settings && settings.mapFocus == "trails") {
|
||||||
|
const boundingBox: LatLngBoundsExpression = [
|
||||||
|
[maxBoundingBox.max_lat, maxBoundingBox.min_lon],
|
||||||
|
[maxBoundingBox.min_lat, maxBoundingBox.max_lon],
|
||||||
|
];
|
||||||
|
map.fitBounds(boundingBox);
|
||||||
|
} else if (
|
||||||
|
settings &&
|
||||||
|
settings.mapFocus == "location" &&
|
||||||
|
settings.location
|
||||||
|
) {
|
||||||
|
map.setView([settings.location.lat, settings.location.lon], 12);
|
||||||
|
} else {
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => {
|
||||||
|
const lat = position.coords.latitude;
|
||||||
|
const lon = position.coords.longitude;
|
||||||
|
map.setView([lat, lon], 13);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Error getting user location:", error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -357,22 +206,27 @@
|
|||||||
{#if $trails.length == 0}
|
{#if $trails.length == 0}
|
||||||
<EmptyStateSearch></EmptyStateSearch>
|
<EmptyStateSearch></EmptyStateSearch>
|
||||||
{/if}
|
{/if}
|
||||||
{#each $trails as trail}
|
{#each $trails as trail,i}
|
||||||
<a href="map/trail/{trail.id}">
|
<a href="map/trail/{trail.id}">
|
||||||
<TrailCard
|
<TrailCard
|
||||||
{trail}
|
{trail}
|
||||||
on:mouseenter={() => handleTrailCardMouseEnter(trail)}
|
on:mouseenter={() => handleTrailCardMouseEnter(i)}
|
||||||
on:mouseleave={() => handleTrailCardMouseLeave(trail)}
|
on:mouseleave={() => handleTrailCardMouseLeave(i)}
|
||||||
></TrailCard>
|
></TrailCard>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class:hidden={!showMap && browser && window.innerWidth < 768}>
|
||||||
id="map"
|
<MapWithElevationMultiple
|
||||||
class="rounded-xl z-0"
|
on:moveend={handleMapMove}
|
||||||
class:hidden={!showMap && browser && window.innerWidth < 768}
|
on:init={handleMapInit}
|
||||||
></div>
|
trails={$trails}
|
||||||
|
options={{ flyToBounds: false }}
|
||||||
|
bind:map
|
||||||
|
bind:this={mapWithElevation}
|
||||||
|
></MapWithElevationMultiple>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user