adds style switcher for maplibre
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
FontawesomeMarker,
|
FontawesomeMarker,
|
||||||
} from "$lib/util/maplibre_util";
|
} from "$lib/util/maplibre_util";
|
||||||
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
import type { ElevationProfileControl } from "$lib/vendor/maplibre-elevation-profile/elevationprofile-control";
|
||||||
|
import { StyleSwitcherControl } from "$lib/vendor/maplibre-style-switcher/style-switcher-control";
|
||||||
import type { GeoJSON } from "geojson";
|
import type { GeoJSON } from "geojson";
|
||||||
import * as M from "maplibre-gl";
|
import * as M from "maplibre-gl";
|
||||||
import "maplibre-gl/dist/maplibre-gl.css";
|
import "maplibre-gl/dist/maplibre-gl.css";
|
||||||
@@ -62,33 +63,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const waypoint of trail?.expand.waypoints ?? []) {
|
|
||||||
const marker = createMarkerFromWaypoint(waypoint);
|
|
||||||
marker.addTo(map);
|
|
||||||
markers.push(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
epc.setData(data, trail!.expand.waypoints);
|
epc.setData(data, trail!.expand.waypoints);
|
||||||
epc.showProfile();
|
epc.showProfile();
|
||||||
|
|
||||||
const trailSource = map.getSource("trail-source");
|
const trailSource = map.getSource("trail-source");
|
||||||
if (!trailSource) {
|
if (!trailSource) {
|
||||||
map.on("load", () => {
|
addTrailLayer();
|
||||||
map!.addSource("trail-source", {
|
|
||||||
type: "geojson",
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
map!.addLayer({
|
|
||||||
id: "uploaded-polygons",
|
|
||||||
type: "line",
|
|
||||||
source: "trail-source",
|
|
||||||
paint: {
|
|
||||||
"line-color": "#648ad5",
|
|
||||||
"line-width": 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
(trailSource as M.GeoJSONSource).setData(data);
|
(trailSource as M.GeoJSONSource).setData(data);
|
||||||
}
|
}
|
||||||
@@ -98,6 +78,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addTrailLayer() {
|
||||||
|
if (!data || !map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const trailSource = map.getSource("trail-source");
|
||||||
|
if (!trailSource) {
|
||||||
|
try {
|
||||||
|
map.addSource("trail-source", {
|
||||||
|
type: "geojson",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const trailLayer = map.getLayer("trail-layer");
|
||||||
|
if (!trailLayer) {
|
||||||
|
map.addLayer({
|
||||||
|
id: "trail-layer",
|
||||||
|
type: "line",
|
||||||
|
source: "trail-source",
|
||||||
|
paint: {
|
||||||
|
"line-color": "#648ad5",
|
||||||
|
"line-width": 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function addStartEndMarkers() {
|
function addStartEndMarkers() {
|
||||||
if (!map || !data) {
|
if (!map || !data) {
|
||||||
return;
|
return;
|
||||||
@@ -137,9 +147,28 @@
|
|||||||
)
|
)
|
||||||
).ElevationProfileControl;
|
).ElevationProfileControl;
|
||||||
|
|
||||||
|
const mapStyles = [
|
||||||
|
{
|
||||||
|
text: "Carto Light",
|
||||||
|
value: "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
|
||||||
|
thumbnail:
|
||||||
|
"https://basemaps.cartocdn.com/light_all/1/0/0@2x.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Carto Dark",
|
||||||
|
value: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
|
||||||
|
thumbnail:
|
||||||
|
"https://basemaps.cartocdn.com/dark_all/1/0/0@2x.png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const preferredMapStyleIndex = mapStyles.findIndex(
|
||||||
|
(s) => s.text === localStorage.getItem("layer"),
|
||||||
|
);
|
||||||
|
|
||||||
map = new M.Map({
|
map = new M.Map({
|
||||||
container: mapContainer,
|
container: mapContainer,
|
||||||
style: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
|
style:
|
||||||
|
mapStyles[preferredMapStyleIndex].value ?? mapStyles[0].value,
|
||||||
center: [initialState.lng, initialState.lat],
|
center: [initialState.lng, initialState.lat],
|
||||||
zoom: initialState.zoom,
|
zoom: initialState.zoom,
|
||||||
});
|
});
|
||||||
@@ -175,6 +204,16 @@
|
|||||||
elevationMarker.setLngLat(data.position as M.LngLatLike);
|
elevationMarker.setLngLat(data.position as M.LngLatLike);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const switcherControl = new StyleSwitcherControl({
|
||||||
|
styles: mapStyles,
|
||||||
|
onSwitch: (style) => {
|
||||||
|
map?.setStyle(style.value);
|
||||||
|
localStorage.setItem("layer", style.text);
|
||||||
|
},
|
||||||
|
selectedIndex:
|
||||||
|
preferredMapStyleIndex !== -1 ? preferredMapStyleIndex : 0,
|
||||||
|
});
|
||||||
map.addControl(new M.NavigationControl());
|
map.addControl(new M.NavigationControl());
|
||||||
map.addControl(
|
map.addControl(
|
||||||
new M.ScaleControl({
|
new M.ScaleControl({
|
||||||
@@ -184,10 +223,21 @@
|
|||||||
"top-left",
|
"top-left",
|
||||||
);
|
);
|
||||||
map.addControl(epc);
|
map.addControl(epc);
|
||||||
|
map.addControl(switcherControl);
|
||||||
|
|
||||||
|
map.on("styledata", () => {
|
||||||
|
addTrailLayer();
|
||||||
|
});
|
||||||
|
|
||||||
map.on("click", (e) => {
|
map.on("click", (e) => {
|
||||||
dispatch("click", e);
|
dispatch("click", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const waypoint of trail?.expand.waypoints ?? []) {
|
||||||
|
const marker = createMarkerFromWaypoint(waypoint);
|
||||||
|
marker.addTo(map);
|
||||||
|
markers.push(marker);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export async function gpx2trail(gpxString: string, fallbackName?: string) {
|
|||||||
for (const wpt of gpx.wpt ?? []) {
|
for (const wpt of gpx.wpt ?? []) {
|
||||||
const wp = new Waypoint(wpt.$.lat ?? 0, wpt.$.lon ?? 0);
|
const wp = new Waypoint(wpt.$.lat ?? 0, wpt.$.lon ?? 0);
|
||||||
wp.id = cryptoRandomString({ length: 15 });
|
wp.id = cryptoRandomString({ length: 15 });
|
||||||
wp.name = wpt.name
|
wp.name = wpt.name ?? ""
|
||||||
wp.description = wpt.desc;
|
wp.description = wpt.desc;
|
||||||
trail.expand.waypoints.push(wp);
|
trail.expand.waypoints.push(wp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function createMarkerFromWaypoint(waypoint: Waypoint, onDragEnd?: (marker
|
|||||||
const marker = new FontawesomeMarker({
|
const marker = new FontawesomeMarker({
|
||||||
icon: `fa fa-${waypoint.icon}`,
|
icon: `fa fa-${waypoint.icon}`,
|
||||||
}, {
|
}, {
|
||||||
draggable: onDragEnd != null,
|
draggable: onDragEnd !== undefined,
|
||||||
color: "#6b7280"
|
color: "#6b7280"
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -720,7 +720,6 @@ export class ElevationProfile {
|
|||||||
this.waypointPositions.forEach((position: number, index: number) => {
|
this.waypointPositions.forEach((position: number, index: number) => {
|
||||||
|
|
||||||
const xPos = xScale.getPixelForValue(position); // X-axis pixel for tick
|
const xPos = xScale.getPixelForValue(position); // X-axis pixel for tick
|
||||||
console.log(xPos);
|
|
||||||
|
|
||||||
// Create custom HTML tick
|
// Create custom HTML tick
|
||||||
const wpDiv = document.createElement("div");
|
const wpDiv = document.createElement("div");
|
||||||
|
|||||||
141
web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts
vendored
Normal file
141
web/src/lib/vendor/maplibre-style-switcher/style-switcher-control.ts
vendored
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import * as M from "maplibre-gl";
|
||||||
|
import { type ControlPosition, type IControl } from "maplibre-gl";
|
||||||
|
/**
|
||||||
|
* Style switcher control options
|
||||||
|
*/
|
||||||
|
|
||||||
|
type MapStyle = {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
thumbnail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StyleSwitcherControlOptions = {
|
||||||
|
styles: MapStyle[];
|
||||||
|
onSwitch: (style: MapStyle) => void
|
||||||
|
selectedIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class StyleSwitcherControl implements IControl {
|
||||||
|
private buttonContainer?: HTMLDivElement;
|
||||||
|
private toggleButton?: HTMLButtonElement;
|
||||||
|
private isSwitcherShown = false;
|
||||||
|
private iconSpan?: HTMLSpanElement;
|
||||||
|
private styleLis: HTMLLIElement[] = [];
|
||||||
|
|
||||||
|
private switcherContainer?: HTMLDivElement;
|
||||||
|
private settings: StyleSwitcherControlOptions;
|
||||||
|
|
||||||
|
constructor(options: StyleSwitcherControlOptions) {
|
||||||
|
if (typeof window === "undefined")
|
||||||
|
throw new Error("This pluggin must be mounted client-side");
|
||||||
|
this.settings = { ...options };
|
||||||
|
}
|
||||||
|
|
||||||
|
getContainer(): HTMLDivElement | undefined {
|
||||||
|
return this.switcherContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(map: M.Map): HTMLElement {
|
||||||
|
this.buttonContainer = document.createElement("div");
|
||||||
|
|
||||||
|
this.buttonContainer.classList.add(
|
||||||
|
"maplibregl-ctrl",
|
||||||
|
"maplibregl-ctrl-group"
|
||||||
|
);
|
||||||
|
this.toggleButton = document.createElement("button");
|
||||||
|
this.buttonContainer.appendChild(this.toggleButton);
|
||||||
|
// this.buttonContainer.classList.add("w-16", "aspect-square")
|
||||||
|
this.iconSpan = document.createElement("i");
|
||||||
|
this.iconSpan.classList.add("fa", "fa-layer-group", "text-black");
|
||||||
|
this.toggleButton.appendChild(this.iconSpan);
|
||||||
|
this.toggleButton.addEventListener("click", this.toggleSwitcher.bind(this));
|
||||||
|
|
||||||
|
|
||||||
|
this.switcherContainer = document.createElement("div");
|
||||||
|
this.switcherContainer.style.setProperty("display", "none");
|
||||||
|
this.switcherContainer.classList.add("bg-menu-background", "rounded-lg", "py-3", "mt-2", "shadow-xl")
|
||||||
|
this.switcherContainer.style.setProperty("position", "absolute");
|
||||||
|
this.switcherContainer.style.setProperty("transform", "translateX(calc(-100% + 29px))");
|
||||||
|
// this.switcherContainer.style.setProperty("max-width", "120px");
|
||||||
|
|
||||||
|
const headingDiv = document.createElement("div");
|
||||||
|
headingDiv.classList.add("flex", "items-center", "gap-x-12", "px-3");
|
||||||
|
const closeButton = document.createElement("button");
|
||||||
|
closeButton.classList.add("fa", "fa-close", "!rounded-full");
|
||||||
|
closeButton.addEventListener("click", () => this.hideSwitcher());
|
||||||
|
const switcherContainerHeading = document.createElement("span")
|
||||||
|
switcherContainerHeading.classList.add("text-lg", "font-semibold", "whitespace-nowrap")
|
||||||
|
switcherContainerHeading.textContent = "Select map style"
|
||||||
|
headingDiv.appendChild(switcherContainerHeading);
|
||||||
|
headingDiv.appendChild(closeButton);
|
||||||
|
|
||||||
|
this.switcherContainer.appendChild(headingDiv)
|
||||||
|
|
||||||
|
const buttonDiv = document.createElement("ul")
|
||||||
|
buttonDiv.classList.add("mt-2");
|
||||||
|
this.settings.styles.forEach((style, i) => {
|
||||||
|
const styleLi = document.createElement("li");
|
||||||
|
styleLi.classList.add("flex", "items-center", "gap-x-4", "px-3", "py-2", "cursor-pointer", "hover:bg-menu-item-background-hover")
|
||||||
|
const styleImg = document.createElement("img");
|
||||||
|
styleImg.classList.add("w-12", "h-12", "rounded-md")
|
||||||
|
styleImg.src = style.thumbnail;
|
||||||
|
const styleName = document.createElement("span");
|
||||||
|
styleName.classList.add("!text-sm")
|
||||||
|
if (i == this.settings.selectedIndex) {
|
||||||
|
styleName.classList.add("font-semibold")
|
||||||
|
}
|
||||||
|
styleName.textContent = style.text;
|
||||||
|
styleLi.appendChild(styleImg)
|
||||||
|
styleLi.appendChild(styleName)
|
||||||
|
|
||||||
|
styleLi.addEventListener("click", () => {
|
||||||
|
this.hideSwitcher();
|
||||||
|
this.styleLis?.at(this.settings.selectedIndex)?.lastElementChild?.classList.remove("font-semibold")
|
||||||
|
this.settings.selectedIndex = i;
|
||||||
|
styleName.classList.add("font-semibold")
|
||||||
|
this.settings.onSwitch(style)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.styleLis?.push(styleLi)
|
||||||
|
buttonDiv.appendChild(styleLi)
|
||||||
|
})
|
||||||
|
this.switcherContainer.appendChild(buttonDiv)
|
||||||
|
this.buttonContainer.appendChild(this.switcherContainer);
|
||||||
|
|
||||||
|
|
||||||
|
return this.buttonContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleSwitcher() {
|
||||||
|
if (!this.switcherContainer) return;
|
||||||
|
|
||||||
|
if (this.isSwitcherShown) {
|
||||||
|
this.hideSwitcher();
|
||||||
|
} else {
|
||||||
|
this.showSwitcher();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSwitcher() {
|
||||||
|
this.switcherContainer?.style.setProperty("display", "inherit");
|
||||||
|
this.isSwitcherShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideSwitcher() {
|
||||||
|
this.switcherContainer?.style.setProperty("display", "none");
|
||||||
|
this.isSwitcherShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(): void {
|
||||||
|
// remove button
|
||||||
|
if (this.buttonContainer?.parentNode) {
|
||||||
|
this.buttonContainer.parentNode.removeChild(this.buttonContainer);
|
||||||
|
}
|
||||||
|
this.buttonContainer = undefined;
|
||||||
|
this.toggleButton = undefined;
|
||||||
|
this.isSwitcherShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPosition?: (() => ControlPosition) | undefined;
|
||||||
|
}
|
||||||
@@ -922,7 +922,6 @@
|
|||||||
<MapWithElevationMaplibre
|
<MapWithElevationMaplibre
|
||||||
trail={$form}
|
trail={$form}
|
||||||
drawing={drawingActive}
|
drawing={drawingActive}
|
||||||
|
|
||||||
bind:map
|
bind:map
|
||||||
on:click={(e) => handleMapClick(e.detail)}
|
on:click={(e) => handleMapClick(e.detail)}
|
||||||
></MapWithElevationMaplibre>
|
></MapWithElevationMaplibre>
|
||||||
|
|||||||
Reference in New Issue
Block a user