sync: 2026-08-21 15:18:36
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
href="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.css"
|
||||||
>
|
>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -612,30 +612,57 @@
|
|||||||
|
|
||||||
|
|
||||||
/* =====================================================
|
/* =====================================================
|
||||||
LEAFLET
|
MAPLIBRE CONTROLS
|
||||||
===================================================== */
|
===================================================== */
|
||||||
|
|
||||||
.leaflet-control-zoom {
|
.maplibregl-ctrl-group {
|
||||||
margin-right: 18px !important;
|
|
||||||
|
|
||||||
margin-bottom: 82px !important;
|
|
||||||
|
|
||||||
border: none !important;
|
border: none !important;
|
||||||
|
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 2px 7px rgba(0, 0, 0, 0.25) !important;
|
0 2px 7px rgba(0, 0, 0, 0.25) !important;
|
||||||
|
|
||||||
|
border-radius: 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-zoom a {
|
.maplibregl-ctrl-group button {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-attribution {
|
.maplibregl-ctrl-attrib {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Position nav controls away from the search UI */
|
||||||
|
.maplibregl-ctrl-topleft {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maplibregl-ctrl-topright {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =====================================================
|
||||||
|
MARKER STYLES
|
||||||
|
===================================================== */
|
||||||
|
|
||||||
|
/* Wrapper that holds both the label and the pin circle */
|
||||||
|
.poi-pin-wrapper {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Permanent name label shown above each pin */
|
/* Permanent name label shown above each pin */
|
||||||
.pin-label {
|
.pin-label {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 38px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: rgba(255, 255, 255, 0.92);
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -645,18 +672,10 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 2px 7px;
|
padding: 2px 7px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
pointer-events: none;
|
||||||
|
|
||||||
.pin-label::before {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom circular pin that reflects the POI type */
|
/* Custom circular pin that reflects the POI type */
|
||||||
.poi-pin {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poi-pin-inner {
|
.poi-pin-inner {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
@@ -681,8 +700,7 @@
|
|||||||
|
|
||||||
/* User location marker (blue dot) */
|
/* User location marker (blue dot) */
|
||||||
.user-loc-marker {
|
.user-loc-marker {
|
||||||
background: transparent;
|
cursor: default;
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-loc-dot {
|
.user-loc-dot {
|
||||||
@@ -762,10 +780,6 @@
|
|||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 72px;
|
bottom: 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-zoom {
|
|
||||||
margin-bottom: 125px !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -929,7 +943,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/maplibre-gl@4/dist/maplibre-gl.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -949,32 +963,28 @@
|
|||||||
*/
|
*/
|
||||||
const CLICK_ZOOM = 15;
|
const CLICK_ZOOM = 15;
|
||||||
|
|
||||||
|
const MAPTILER_KEY = "aUftDXS9UHdRVbSuaUWz";
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
MAP
|
MAP
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
const map =
|
const map = new maplibregl.Map({
|
||||||
L.map("map").setView(
|
container: "map",
|
||||||
[
|
style:
|
||||||
AMSTERDAM_LAT,
|
"https://api.maptiler.com/maps/streets-v4/style.json?key=" +
|
||||||
AMSTERDAM_LON
|
MAPTILER_KEY,
|
||||||
],
|
center: [AMSTERDAM_LON, AMSTERDAM_LAT],
|
||||||
DEFAULT_ZOOM
|
zoom: DEFAULT_ZOOM
|
||||||
|
});
|
||||||
|
|
||||||
|
map.addControl(
|
||||||
|
new maplibregl.NavigationControl(),
|
||||||
|
"top-right"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
L.tileLayer(
|
|
||||||
"https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=aUftDXS9UHdRVbSuaUWz",
|
|
||||||
{
|
|
||||||
maxZoom: 19,
|
|
||||||
|
|
||||||
attribution:
|
|
||||||
"© OpenStreetMap contributors"
|
|
||||||
}
|
|
||||||
).addTo(map);
|
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
ELEMENTS
|
ELEMENTS
|
||||||
========================================================= */
|
========================================================= */
|
||||||
@@ -1014,6 +1024,8 @@
|
|||||||
|
|
||||||
let selectedMarker = null;
|
let selectedMarker = null;
|
||||||
|
|
||||||
|
let selectedPopup = null;
|
||||||
|
|
||||||
let userLocationMarker = null;
|
let userLocationMarker = null;
|
||||||
|
|
||||||
let zoomMessageTimer = null;
|
let zoomMessageTimer = null;
|
||||||
@@ -1052,13 +1064,13 @@
|
|||||||
|
|
||||||
// Build a viewbox from the currently visible map area so
|
// Build a viewbox from the currently visible map area so
|
||||||
// results are restricted to what the user can see.
|
// results are restricted to what the user can see.
|
||||||
const visibleBounds = map.getBounds();
|
const mb = map.getBounds();
|
||||||
|
|
||||||
const viewbox = [
|
const viewbox = [
|
||||||
visibleBounds.getWest(),
|
mb.west,
|
||||||
visibleBounds.getNorth(),
|
mb.north,
|
||||||
visibleBounds.getEast(),
|
mb.east,
|
||||||
visibleBounds.getSouth()
|
mb.south
|
||||||
].join(",");
|
].join(",");
|
||||||
|
|
||||||
|
|
||||||
@@ -1182,27 +1194,42 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const marker =
|
// Build the marker element with label + pin
|
||||||
L.marker([
|
const el =
|
||||||
place.lat,
|
document.createElement(
|
||||||
place.lon
|
"div"
|
||||||
], {
|
|
||||||
icon: createPoiIcon(place)
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
|
|
||||||
marker.bindTooltip(
|
|
||||||
escapeHtml(getName(place)),
|
|
||||||
{
|
|
||||||
permanent: true,
|
|
||||||
direction: "top",
|
|
||||||
offset: [0, -20],
|
|
||||||
className: "pin-label"
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
el.className =
|
||||||
|
"poi-pin-wrapper";
|
||||||
|
|
||||||
marker.on(
|
el.innerHTML = `
|
||||||
|
<div class="pin-label">
|
||||||
|
${escapeHtml(
|
||||||
|
getName(place)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="poi-pin-inner"
|
||||||
|
style="background:${getPoiColor(place)};"
|
||||||
|
>
|
||||||
|
${getIcon(place)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const marker =
|
||||||
|
new maplibregl.Marker({
|
||||||
|
element: el
|
||||||
|
})
|
||||||
|
.setLngLat([
|
||||||
|
place.lon,
|
||||||
|
place.lat
|
||||||
|
])
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
|
||||||
|
marker.getElement().addEventListener(
|
||||||
"click",
|
"click",
|
||||||
() => {
|
() => {
|
||||||
|
|
||||||
@@ -1231,26 +1258,36 @@
|
|||||||
|
|
||||||
const zoom = getZoomForPlace(data[0]);
|
const zoom = getZoomForPlace(data[0]);
|
||||||
|
|
||||||
map.flyTo(
|
map.flyTo({
|
||||||
bounds[0],
|
center: [
|
||||||
zoom,
|
bounds[0][1],
|
||||||
{
|
bounds[0][0]
|
||||||
duration: 0.7
|
],
|
||||||
}
|
zoom: zoom,
|
||||||
);
|
duration: 700
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
map.fitBounds(
|
const lons =
|
||||||
bounds,
|
bounds.map(b => b[1]);
|
||||||
{
|
|
||||||
padding: [
|
const lats =
|
||||||
80,
|
bounds.map(b => b[0]);
|
||||||
80
|
|
||||||
|
map.fitBounds([
|
||||||
|
[
|
||||||
|
Math.min(...lons),
|
||||||
|
Math.min(...lats)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Math.max(...lons),
|
||||||
|
Math.max(...lats)
|
||||||
]
|
]
|
||||||
}
|
], {
|
||||||
);
|
padding: 80
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1361,47 +1398,66 @@
|
|||||||
"none";
|
"none";
|
||||||
|
|
||||||
|
|
||||||
if (selectedMarker) {
|
clearSelection();
|
||||||
|
|
||||||
map.removeLayer(
|
|
||||||
selectedMarker
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
selectedMarker =
|
selectedMarker =
|
||||||
L.marker([
|
new maplibregl.Marker()
|
||||||
place.lat,
|
.setLngLat([
|
||||||
place.lon
|
place.lon,
|
||||||
]).addTo(map);
|
place.lat
|
||||||
|
])
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
|
||||||
const zoom = getZoomForPlace(place);
|
const zoom = getZoomForPlace(place);
|
||||||
|
|
||||||
|
|
||||||
map.flyTo(
|
map.flyTo({
|
||||||
[
|
center: [
|
||||||
place.lat,
|
place.lon,
|
||||||
place.lon
|
place.lat
|
||||||
],
|
],
|
||||||
zoom,
|
zoom: zoom,
|
||||||
{
|
duration: 700
|
||||||
duration: 0.7
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
renderPlace(place);
|
renderPlace(place);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
CLEAR SELECTION
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
function clearSelection() {
|
||||||
|
|
||||||
|
if (selectedMarker) {
|
||||||
|
|
||||||
|
selectedMarker.remove();
|
||||||
|
|
||||||
|
selectedMarker =
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPopup) {
|
||||||
|
|
||||||
|
selectedPopup.remove();
|
||||||
|
|
||||||
|
selectedPopup =
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
MAP CLICK
|
MAP CLICK
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
map.on(
|
map.on(
|
||||||
"click",
|
"click",
|
||||||
async event => {
|
async e => {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Don't reverse-geocode at low zoom.
|
* Don't reverse-geocode at low zoom.
|
||||||
@@ -1418,10 +1474,10 @@
|
|||||||
|
|
||||||
|
|
||||||
const lat =
|
const lat =
|
||||||
event.latlng.lat;
|
e.lngLat.lat;
|
||||||
|
|
||||||
const lon =
|
const lon =
|
||||||
event.latlng.lng;
|
e.lngLat.lng;
|
||||||
|
|
||||||
|
|
||||||
resultsCard.style.display =
|
resultsCard.style.display =
|
||||||
@@ -1441,19 +1497,16 @@
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
if (selectedMarker) {
|
clearSelection();
|
||||||
|
|
||||||
map.removeLayer(
|
|
||||||
selectedMarker
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
selectedMarker =
|
selectedMarker =
|
||||||
L.marker([
|
new maplibregl.Marker()
|
||||||
lat,
|
.setLngLat([
|
||||||
lon
|
lon,
|
||||||
]).addTo(map);
|
lat
|
||||||
|
])
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1496,11 +1549,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
selectedMarker.bindPopup(
|
selectedPopup =
|
||||||
|
new maplibregl.Popup({
|
||||||
|
offset: 25
|
||||||
|
})
|
||||||
|
.setHTML(
|
||||||
escapeHtml(
|
escapeHtml(
|
||||||
getName(place)
|
getName(place)
|
||||||
)
|
)
|
||||||
).openPopup();
|
)
|
||||||
|
.setLngLat([
|
||||||
|
lon,
|
||||||
|
lat
|
||||||
|
])
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
|
|
||||||
renderPlace(place);
|
renderPlace(place);
|
||||||
@@ -1923,15 +1985,7 @@
|
|||||||
"none";
|
"none";
|
||||||
|
|
||||||
|
|
||||||
if (selectedMarker) {
|
clearSelection();
|
||||||
|
|
||||||
map.removeLayer(
|
|
||||||
selectedMarker
|
|
||||||
);
|
|
||||||
|
|
||||||
selectedMarker =
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2185,30 +2239,41 @@
|
|||||||
|
|
||||||
function showUserLocation(lat, lon) {
|
function showUserLocation(lat, lon) {
|
||||||
|
|
||||||
const coords = [lat, lon];
|
const coords = [lon, lat];
|
||||||
|
|
||||||
|
|
||||||
if (!userLocationMarker) {
|
if (!userLocationMarker) {
|
||||||
|
|
||||||
|
const el =
|
||||||
|
document.createElement(
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
el.className =
|
||||||
|
"user-loc-marker";
|
||||||
|
|
||||||
|
el.innerHTML =
|
||||||
|
'<div class="user-loc-dot"></div>';
|
||||||
|
|
||||||
|
|
||||||
userLocationMarker =
|
userLocationMarker =
|
||||||
L.marker(coords, {
|
new maplibregl.Marker({
|
||||||
icon: createUserLocationIcon(),
|
element: el
|
||||||
zIndexOffset: 1000
|
})
|
||||||
}).addTo(map);
|
.setLngLat(coords)
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
userLocationMarker.setLatLng(coords);
|
userLocationMarker.setLngLat(coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
map.flyTo(
|
map.flyTo({
|
||||||
coords,
|
center: coords,
|
||||||
16,
|
zoom: 16,
|
||||||
{
|
duration: 800
|
||||||
duration: 0.8
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2340,24 +2405,14 @@
|
|||||||
|
|
||||||
markers.forEach(
|
markers.forEach(
|
||||||
marker =>
|
marker =>
|
||||||
map.removeLayer(
|
marker.remove()
|
||||||
marker
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
markers = [];
|
markers = [];
|
||||||
|
|
||||||
|
|
||||||
if (selectedMarker) {
|
clearSelection();
|
||||||
|
|
||||||
map.removeLayer(
|
|
||||||
selectedMarker
|
|
||||||
);
|
|
||||||
|
|
||||||
selectedMarker =
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2655,38 +2710,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createPoiIcon(place) {
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div
|
|
||||||
class="poi-pin-inner"
|
|
||||||
style="background:${getPoiColor(place)};"
|
|
||||||
>
|
|
||||||
${getIcon(place)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
return L.divIcon({
|
|
||||||
className: "poi-pin",
|
|
||||||
html: html,
|
|
||||||
iconSize: [34, 34],
|
|
||||||
iconAnchor: [17, 17]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function createUserLocationIcon() {
|
|
||||||
|
|
||||||
return L.divIcon({
|
|
||||||
className: "user-loc-marker",
|
|
||||||
html: `<div class="user-loc-dot"></div>`,
|
|
||||||
iconSize: [18, 18],
|
|
||||||
iconAnchor: [9, 9]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function safeUrl(url) {
|
function safeUrl(url) {
|
||||||
|
|
||||||
if (!url)
|
if (!url)
|
||||||
|
|||||||
Reference in New Issue
Block a user