cleanup of obsolote leaflet components

This commit is contained in:
Christian Beutel
2024-11-30 01:13:57 +01:00
parent 8c42e9e695
commit 8f8d28ef7a
56 changed files with 118 additions and 9198 deletions

View File

@@ -1,6 +1,5 @@
<script lang="ts">
import type { SummitLog } from "$lib/models/summit_log";
import "leaflet/dist/leaflet.css";
import { onMount, tick } from "svelte";
import Modal from "../base/modal.svelte";
import SummitLogTableRow from "./summit_log_table_row.svelte";

View File

@@ -1,250 +0,0 @@
<script lang="ts">
import { page } from "$app/stores";
import { Settings } from "$lib/models/settings";
import type { Trail } from "$lib/models/trail";
import {
createMarkerFromWaypoint,
endIcon,
startIcon,
} from "$lib/util/leaflet_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
import type { Map, Marker } from "leaflet";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
import Dropdown from "../base/dropdown.svelte";
export let trail: Trail | null;
export let markers: Marker[] = [];
export let map: Map | null = null;
export let options: any = {};
export let graticule: AutoGraticule | null = null;
export let crosshair: boolean = false;
const dispatch = createEventDispatcher();
let L: any;
let controlElevation: any;
let selectedMetric: "altitude" | "slope" | "speed" | false = "altitude";
$: gpxData = trail?.expand.gpx_data;
$: if (gpxData && controlElevation) {
controlElevation.updateOptions({
autofitBounds: options.autofitBounds ?? true,
});
controlElevation.clear();
controlElevation.load(gpxData);
}
$: if (options) {
controlElevation?.updateOptions(options);
}
$: hotlineSwitcherItems = [
{
text: $_("altitude"),
value: "altitude",
icon: selectedMetric == "altitude" ? "circle-dot" : "circle",
},
{
text: $_("slope"),
value: "slope",
icon: selectedMetric == "slope" ? "circle-dot" : "circle",
},
{
text: $_("speed"),
value: "speed",
icon: selectedMetric == "speed" ? "circle-dot" : "circle",
},
{
text: $_("off"),
value: false,
icon: selectedMetric == false ? "circle-dot" : "circle",
},
];
onMount(async () => {
L = (await import("leaflet")).default;
await import("leaflet-gpx");
await import("leaflet.awesome-markers");
//@ts-ignore
await import("$lib/vendor/leaflet-elevation/src/index.js");
const AutoGraticule = (
await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")
).default;
map = L.map("map", { preferCanvas: true }).setView(
[trail?.lat ?? 0, trail?.lon ?? 0],
3,
);
map!.attributionControl.setPrefix(false);
map!.on("zoomend", function () {
dispatch("zoomend", map);
});
map!.on("click", function (e) {
dispatch("click", e);
});
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: Record<string, L.TileLayer> = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
...($page.data.settings as Settings)?.tilesets?.reduce<
Record<string, string>
>((t, current) => {
t[current.name] = L.tileLayer(current.url);
return t;
}, {}),
};
L.control.layers(baseMaps).addTo(map);
const layerPreference = localStorage.getItem("layer");
if (
layerPreference &&
Object.keys(baseMaps).includes(layerPreference)
) {
baseMaps[layerPreference].addTo(map!);
} else {
baseLayer.addTo(map);
}
map!.on("baselayerchange", function (e) {
localStorage.setItem("layer", e.name);
});
const localMetric =
(localStorage.getItem("gradient") as any) ?? "altitude";
selectedMetric = localMetric === "false" ? false : localMetric;
const default_elevation_options = {
height: 200,
theme: "lightblue-theme",
detached: true,
elevationDiv: "#elevation",
closeBtn: false,
followMarker: true,
autofitBounds: true,
imperial: ($page.data.settings?.unit ?? "metric") == "imperial",
reverseCoords: false,
acceleration: false,
slope: true,
speed: true,
altitude: true,
time: true,
distance: true,
// Summary track info style: "inline" || "multiline" || false
summary: false,
downloadLink: false,
ruler: false,
legend: true,
// Toggle "leaflet-almostover" integration
almostOver: false,
// Toggle "leaflet-distance-markers" integration
distanceMarkers: {
lazy: false,
distance: false,
direction: true,
offset: 500,
},
// Toggle "leaflet-edgescale" integration
edgeScale: false,
// Toggle "leaflet-hotline" integration
hotline: selectedMetric,
// Display track datetimes: true || false
timestamps: false,
waypoints: false,
wptIcons: false,
wptLabels: false,
preferCanvas: true,
trkStart: {
interactive: false,
className: "hihi",
icon: startIcon(),
},
trkEnd: {
icon: endIcon(),
},
graticule: false,
drawing: false,
};
const elevation_options = Object.assign(
default_elevation_options,
options,
);
controlElevation = L.control.elevation(elevation_options).addTo(map);
for (const waypoint of trail?.expand.waypoints ?? []) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(map!);
markers.push(marker);
}
if (elevation_options.graticule) {
graticule = new AutoGraticule();
graticule.addTo(map!);
}
});
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
controlElevation.updateOptions({
hotline: metric,
autofitBounds: false,
});
selectedMetric = metric;
localStorage.setItem("gradient", metric.toString());
controlElevation.clear();
controlElevation.load(gpxData);
}
</script>
<div id="map-container" class="flex flex-col h-full relative">
<div
id="map"
class="rounded-xl z-0 basis-full min-h-96 md:min-h-0"
style={crosshair
? "position: relative; outline-style: none;cursor: crosshair !important"
: "position: relative; outline-style: none;"}
>
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
<Dropdown
items={hotlineSwitcherItems}
on:change={(e) => switchHotline(e.detail.value)}
let:toggleMenu={openDropdown}
>
<button
class="rounded-md border-2 border-black border-opacity-30 bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
on:click={openDropdown}
>
<i class="fa fa-route text-lg"></i>
</button>
</Dropdown>
</div>
</div>
<div class="flex items-center justify-between">
<slot />
<div class="basis-[300px] flex-grow flex-shrink-0" id="elevation"></div>
</div>
</div>

View File

@@ -16,6 +16,7 @@
import "maplibre-gl/dist/maplibre-gl.css";
import { createEventDispatcher, onDestroy, onMount } from "svelte";
import MaplibreGraticule from "$lib/vendor/maplibre-graticule/maplibre-graticule";
import { FullscreenControl } from "$lib/vendor/maplibre-fullscreen/fullscreen-control";
export let trails: Trail[] = [];
export let markers: M.Marker[] = [];
@@ -24,6 +25,8 @@
export let showElevation: boolean = true;
export let showInfoPopup: boolean = false;
export let showGrid: boolean = false;
export let showStyleSwitcher: boolean = true;
export let showFullscreen: boolean = false;
export let fitBounds: "animate" | "instant" | "off" = "instant";
export let elevationProfileContainer: string | HTMLDivElement | undefined =
@@ -520,7 +523,18 @@
});
map.addControl(epc);
}
map.addControl(switcherControl);
if (showStyleSwitcher) {
map.addControl(switcherControl);
}
if (showFullscreen) {
map.addControl(
new FullscreenControl(() => {
dispatch("fullscreen");
}),
"bottom-right",
);
}
map.on("styledata", () => {
trails.forEach((t, i) => {

View File

@@ -1,355 +0,0 @@
<script lang="ts">
import { page } from "$app/stores";
import { Settings } from "$lib/models/settings";
import type { Trail } from "$lib/models/trail";
import { getFileURL } from "$lib/util/file_util";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
import Dropdown from "../base/dropdown.svelte";
export let trails: Trail[];
export let map: any | null = null;
export let options: any = {};
export let activeTrailIndex: number | null = 0;
export let markers: any[] = [];
export let bindRoutePopup: boolean = true;
const dispatch = createEventDispatcher();
let L: any;
let gpxGroup: any;
let selectedMetric: "altitude" | "slope" | "speed" | false = "altitude";
$: gpxData = trails.map((t) => {
return { id: t.id, gpx: t.expand.gpx_data };
});
$: if (
gpxData &&
gpxGroup &&
map &&
(gpxData.length != gpxGroup._tracks.length ||
gpxGroup?._tracks[0] !== gpxData[0])
) {
// if (gpxData.length == 0) {
// map?.setView([0, 0], 4);
// }
gpxGroup._elevation.updateOptions({
autofitBounds: options.autofitBounds ?? true,
});
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
}
$: if (options && gpxGroup) {
gpxGroup._elevation.updateOptions(options);
}
$: hotlineSwitcherItems = [
{
text: $_("altitude"),
value: "altitude",
icon: selectedMetric == "altitude" ? "circle-dot" : "circle",
},
{
text: $_("slope"),
value: "slope",
icon: selectedMetric == "slope" ? "circle-dot" : "circle",
},
{
text: $_("speed"),
value: "speed",
icon: selectedMetric == "speed" ? "circle-dot" : "circle",
},
{
text: $_("off"),
value: false,
icon: selectedMetric == false ? "circle-dot" : "circle",
},
];
onMount(async () => {
L = (await import("leaflet")).default;
await import("leaflet-gpx");
await import("leaflet.awesome-markers");
await import("$lib/vendor/leaflet-elevation/src/index.js");
await import("$lib/vendor/leaflet-elevation/libs/leaflet-gpxgroup");
map = L.map("map", {
preferCanvas: true,
plugins: ["/vendor/leaflet-elevation/libs/leaflet-gpxgroup.js"],
}).setView(
[
trails.at(activeTrailIndex ?? 0)?.lat ?? 0,
trails.at(activeTrailIndex ?? 0)?.lon ?? 0,
],
3,
);
dispatch("init", map);
map!.attributionControl.setPrefix(false);
map!.on("zoomend", function () {
dispatch("zoomend", map);
});
map!.on("click", function (e: any) {
dispatch("click", e);
});
map!.on("moveend", function (e: any) {
dispatch("moveend", e);
});
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: Record<string, L.TileLayer> = {
OpenStreetMaps: baseLayer,
OpenTopoMaps: topoLayer,
...($page.data?.settings as Settings)?.tilesets?.reduce<
Record<string, string>
>((t, current) => {
t[current.name] = L.tileLayer(current.url);
return t;
}, {}),
};
L.control.layers(baseMaps).addTo(map);
const layerPreference = localStorage.getItem("layer");
if (
layerPreference &&
Object.keys(baseMaps).includes(layerPreference)
) {
baseMaps[layerPreference].addTo(map!);
} else {
baseLayer.addTo(map);
}
map!.on("baselayerchange", function (e: any) {
localStorage.setItem("layer", e.name);
});
const localMetric =
(localStorage.getItem("gradient") as any) ?? "altitude";
selectedMetric = localMetric === "false" ? false : localMetric;
const default_elevation_options = {
height: 200,
theme: "lightblue-theme",
detached: true,
elevationDiv: "#elevation",
closeBtn: false,
followMarker: true,
autofitBounds: true,
imperial: $page.data.settings?.unit == "imperial",
reverseCoords: false,
acceleration: false,
slope: true,
speed: true,
altitude: true,
time: true,
distance: true,
// Summary track info style: "inline" || "multiline" || false
summary: false,
downloadLink: false,
ruler: false,
legend: true,
// Toggle "leaflet-almostover" integration
almostOver: false,
// Toggle "leaflet-distance-markers" integration
distanceMarkers: {
lazy: false,
distance: false,
direction: true,
offset: 1000,
},
// Toggle "leaflet-edgescale" integration
edgeScale: false,
// Toggle "leaflet-hotline" integration
hotline: selectedMetric,
// Display track datetimes: true || false
timestamps: false,
waypoints: false,
wptIcons: false,
wptLabels: false,
preferCanvas: true,
graticule: false,
drawing: false,
};
const elevation_options = Object.assign(
default_elevation_options,
options,
);
gpxGroup = L.gpxGroup(gpxData, {
points: [],
elevation: true,
elevation_options: elevation_options,
flyToBounds: options.flyToBounds,
distanceMarkers: true,
itinerary: options.itinerary,
});
const markerLayerGroup = L.layerGroup().addTo(map);
gpxGroup.on("selection_changed", ({ polyline }: { polyline: any }) => {
markerLayerGroup.clearLayers();
activeTrailIndex = null;
markers = [];
if (polyline && polyline._selected) {
activeTrailIndex = trails.findIndex(
(t) => t.id == polyline.options.id,
);
for (const waypoint of trails.at(activeTrailIndex!)?.expand
.waypoints ?? []) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(markerLayerGroup!);
markers.push(marker);
}
dispatch(
"select",
trails.find((t) => t.id == polyline.options.id),
);
}
});
gpxGroup.on("clear", () => {
markerLayerGroup.clearLayers();
activeTrailIndex = null;
});
gpxGroup.on("route_loaded", ({ route }: { route: any }) => {
const trail = trails.find((t) => t.id == route.options.id);
if (!trail) {
return;
}
const thumbnail = trail.photos.length
? getFileURL(trail, trail.photos[trail.thumbnail])
: "/imgs/default_thumbnail.webp";
if (bindRoutePopup) {
route.bindPopup(
`<a href="/trail/view/${trail.id}" data-sveltekit-preload-data="off">
<li class="flex items-center gap-4 cursor-pointer text-black max-w-80">
<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="grid grid-cols-2 mt-2 gap-x-4 gap-y-2 text-sm text-gray-500 flex-wrap"><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-clock mr-2"></i>${formatTimeHHMM(
trail.duration,
)}</span><span class="shrink-0"><i class="fa fa-arrow-trend-up mr-2"></i>${formatElevation(
trail.elevation_gain,
)}</span></span> <span class="shrink-0"><i class="fa fa-arrow-trend-down mr-2"></i>${formatElevation(
trail.elevation_loss,
)}</span></div>
</div>
</li>
</a>`,
);
}
});
gpxGroup.addTo(map);
});
export function highlightTrail(id: string) {
gpxGroup?.highlightTrack(id);
}
export function unHighlightTrail(id: string) {
gpxGroup?.unHighlightTrack(id);
}
export function selectTrail(id: string) {
gpxGroup?.select(id);
}
export function resetSelection() {
gpxGroup?.resetSelection();
}
export function openPopup(id: string) {
gpxGroup?.openPopup(id);
}
export function closePopup(id: string) {
gpxGroup?.closePopup(id);
}
function switchHotline(metric: "altitude" | "slope" | "speed" | false) {
if (metric === selectedMetric) {
return;
}
gpxGroup._elevation.updateOptions({
hotline: metric,
});
gpxGroup.options.flyToBounds = false;
selectedMetric = metric;
localStorage.setItem("gradient", metric.toString());
gpxGroup.clear();
gpxGroup._tracks = gpxData;
gpxGroup.addTracks();
gpxGroup.options.flyToBounds = true;
}
</script>
<div id="map-container" class="h-full relative">
<div
id="map"
class="rounded-xl z-0 h-full min-h-96 md:min-h-0"
style="position: relative; outline-style: none;"
>
<div class="absolute top-20 right-3 text-sm" style="z-index: 500">
<Dropdown
items={hotlineSwitcherItems}
on:change={(e) => switchHotline(e.detail.value)}
let:toggleMenu={openDropdown}
>
<button
class="rounded-md border-2 border-black border-opacity-30 bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
on:click={openDropdown}
>
<i class="fa fa-route text-lg"></i>
</button>
</Dropdown>
</div>
</div>
<div class="absolute bottom-0 bg-background/80 w-full" id="elevation"></div>
</div>

View File

@@ -20,15 +20,7 @@
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import {
createMarkerFromWaypoint,
endIcon,
startIcon,
} from "$lib/util/leaflet_util";
import "$lib/vendor/leaflet-elevation/src/index.css";
import type { Map, Marker } from "leaflet";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import "photoswipe/style.css";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
@@ -40,10 +32,11 @@
import SummitLogTable from "../summit_log/summit_log_table.svelte";
import { pb } from "$lib/pocketbase";
import * as M from "maplibre-gl";
import MapWithElevationMaplibre from "./map_with_elevation_maplibre.svelte";
export let trail: Trail;
export let mode: "overview" | "map" | "list" = "map";
export let markers: (Marker | M.Marker)[] = [];
export let markers: M.Marker[] = [];
const tabs = [
$_("description"),
@@ -56,8 +49,6 @@
const trailIsShared =
(trail.expand?.trail_share_via_trail?.length ?? 0) > 0;
let map: Map;
let activeTab = 0;
let thumbnail = trail.photos.length
@@ -74,64 +65,14 @@
fetchComments();
}
onMount(async () => {
if (mode == "overview") {
const L = (await import("leaflet")).default;
await import("leaflet-gpx");
await import("leaflet.awesome-markers");
map = L.map("map").setView([0, 0], 2);
map.attributionControl.setPrefix(false);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors",
}).addTo(map);
const gpxLayer = new L.GPX(trail.expand.gpx_data!, {
async: true,
polyline_options: {
className: "lightblue-theme elevation-polyline",
opacity: 0.75,
weight: 5,
},
gpx_options: {
parseElements: ["track", "route"] as any,
},
marker_options: {
startIcon: startIcon() as any,
startIconUrl: "",
endIcon: endIcon() as any,
endIconUrl: "",
shadowUrl: "",
},
})
.on("loaded", function (e) {
map.fitBounds(e.target.getBounds());
})
.addTo(map);
for (const waypoint of trail.expand.waypoints) {
const marker = createMarkerFromWaypoint(L, waypoint);
marker.addTo(map);
markers.push(marker);
}
}
});
onMount(async () => {});
function openMarkerPopup(i: number) {
if (typeof (markers[i] as Marker<any>).openPopup === "function") {
(markers[i] as Marker<any>).openPopup();
} else {
(markers[i] as M.Marker).togglePopup();
}
(markers[i] as M.Marker).togglePopup();
}
function closeMarkerPopup(i: number) {
if (typeof (markers[i] as Marker<any>).closePopup === "function") {
(markers[i] as Marker<any>).closePopup();
} else {
(markers[i] as M.Marker).togglePopup();
}
(markers[i] as M.Marker).togglePopup();
}
async function toggleMapFullScreen() {
@@ -425,16 +366,16 @@
</div>
{/if}
{#if mode == "overview"}
<div class="relative">
<div class="rounded-xl h-72" id="map">
<div class="leaflet-top leaflet-right">
<button
class="leaflet-control fa fa-maximize rounded-full text-lg bg-white text-black px-[14px] py-2 hover:bg-gray-100"
style="cursor: pointer !important"
on:click={() => toggleMapFullScreen()}
></button>
</div>
</div>
<div class="relative h-72 rounded-xl">
<MapWithElevationMaplibre
trails={[trail]}
showElevation={false}
showStyleSwitcher={false}
showFullscreen={true}
mapOptions={{ attributionControl: false }}
on:fullscreen={toggleMapFullScreen}
bind:markers
></MapWithElevationMaplibre>
</div>
{/if}
</div>

View File

@@ -13,8 +13,8 @@
import TextField from "../base/text_field.svelte";
import Textarea from "../base/textarea.svelte";
import PhotoPicker from "../trail/photo_picker.svelte";
import { convertDMSToDD } from "$lib/util/leaflet_util";
import { show_toast } from "$lib/stores/toast_store";
import { convertDMSToDD } from "$lib/models/gpx/utils";
export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined;