ixg_platform/apps/compliance/static/js/ixg_zone_map.js
ixgadmin 5d0b5fe757 Origin zones: draw the stored polygon on a map in the admin (Leaflet 1.9.4)
Closes the "no map view" gap for origin zones: the polygon was stored as
coordinates and shown as raw JSON, so the shape could not be checked by eye.

- vendor Leaflet 1.9.4 into apps/core/static/vendor/leaflet. Self-hosted rather
  than pulled from a CDN, because this renders supplier-location data.
  PROVENANCE.md records the version, source, licence (BSD-2-Clause), checksums
  and upgrade steps; the checksums match the SRI hashes Leaflet publishes
- OriginZoneAdmin gains an "Area on a map" read-only row and its Media assets;
  apps/compliance/static/js/ixg_zone_map.js draws the polygon from the stored
  geometry, passed through a data attribute rather than injected into a script
- the tile source is a setting (LEAFLET_TILE_URL), so moving to self-hosted
  tiles is a config change, not a code change

Deliberately display only: it draws what is stored. It does not validate the
geometry, and it does not verify the zone - both remain open work, and the page
says so where the map appears.

Tests cover the drawn map, a zone with no coordinates, and the add page.
2026-09-16 16:39:07 +00:00

68 lines
1.9 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 =
'&copy; <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) {
L.tileLayer(el.dataset.tileUrl, {
maxZoom: 19,
attribution: OSM_ATTRIBUTION,
}).addTo(map);
}
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();
}
})();