The origin-zone map drew its background from a public tile server, which made it
the only part of the admin page that depended on a third-party domain — and the
part that breaks. Where a viewer's network, proxy or browser filter refuses
tile.openstreetmap.org (the refusal arrives as a 403) the map never draws, while
the page, the Leaflet bundle and the polygon data are all same-origin and fine.
Fetch the tiles server-side and serve them from /tiles/{z}/{x}/{y}.png: cached
on disk, so a page opened repeatedly does not re-fetch, and gated on being
signed in, so the route is not an open tile relay. The upstream stays
configurable via LEAFLET_TILE_URL, and LEAFLET_TILE_PROXY_ENABLED=False puts the
browser back on direct fetches.
When a tile still cannot be fetched, answer an empty tile rather than an error
page and say so under the map, instead of leaving a viewer with a blank grid and
no explanation.
85 lines
2.6 KiB
JavaScript
85 lines
2.6 KiB
JavaScript
/* Draw the stored origin-zone polygon on a Leaflet map.
|
|
*
|
|
* Display only: this renders what is already stored on the record. It does not
|
|
* validate the geometry, and it does not approve the zone. Validation and the
|
|
* two-step verification are separate pieces of work.
|
|
*
|
|
* The geometry is passed in a data-* attribute so the browser decodes it (no
|
|
* JSON injected into a script block).
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
var OSM_ATTRIBUTION =
|
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
|
|
|
|
function init() {
|
|
var el = document.getElementById("zone-map");
|
|
if (!el || typeof L === "undefined") {
|
|
return;
|
|
}
|
|
|
|
var geometry;
|
|
try {
|
|
geometry = JSON.parse(el.dataset.geometry || "null");
|
|
} catch (err) {
|
|
el.innerHTML =
|
|
'<p style="padding:1rem;color:#b91c1c">Stored geometry is not valid JSON, so it cannot be drawn.</p>';
|
|
return;
|
|
}
|
|
if (!geometry || !geometry.coordinates) {
|
|
return;
|
|
}
|
|
|
|
var map = L.map(el, { scrollWheelZoom: false });
|
|
|
|
if (el.dataset.tileUrl) {
|
|
var tiles = L.tileLayer(el.dataset.tileUrl, {
|
|
maxZoom: 19,
|
|
attribution: OSM_ATTRIBUTION,
|
|
}).addTo(map);
|
|
|
|
// If the tile source cannot be reached, the polygon still draws but the
|
|
// background stays blank. Say so, rather than leaving the viewer to wonder
|
|
// whether the record itself is broken.
|
|
var note = null;
|
|
tiles.on("tileerror", function () {
|
|
if (note) {
|
|
return;
|
|
}
|
|
note = document.createElement("p");
|
|
note.style.cssText =
|
|
"max-width:900px;margin-top:.5rem;font-size:.8rem;color:#b45309";
|
|
note.textContent =
|
|
"Map imagery could not be loaded. The area below is still drawn from the " +
|
|
"stored coordinates, so the shape can still be checked.";
|
|
el.insertAdjacentElement("afterend", note);
|
|
});
|
|
}
|
|
|
|
var layer = L.geoJSON(geometry, {
|
|
style: { color: "#0f766e", weight: 2, fillColor: "#14b8a6", fillOpacity: 0.25 },
|
|
}).addTo(map);
|
|
|
|
var bounds = layer.getBounds();
|
|
if (bounds && bounds.isValid()) {
|
|
map.fitBounds(bounds.pad(0.25));
|
|
} else {
|
|
map.setView([0, 0], 2);
|
|
}
|
|
|
|
// Grappelli lays the form out after load; a stale size leaves grey tiles.
|
|
setTimeout(function () {
|
|
map.invalidateSize();
|
|
if (bounds && bounds.isValid()) {
|
|
map.fitBounds(bounds.pad(0.25));
|
|
}
|
|
}, 250);
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|