let COLORS = {
B: "#bbb",
L: "blue",
R: "orange",
C: "teal",
};
let activeZone = "";
let baseAttributes = {
opacity: 0.8,
fillOpacity: 0.2,
weight: 1,
};
let selectedAttributes = {
opacity: 0.8,
fillOpacity: 0.4,
weight: 5,
};
App.resetActive = () => {
history.replaceState("", null, "/");
App.clearActiveZone();
App.map.setZoom(12);
};
App.getZoneStyle = feature => {
if (feature.properties.state == "B") {
return {
color: COLORS[feature.properties.state],
opacity: 1,
fillOpacity: 0,
weight: 3,
className: `zone-${feature.id}`,
};
}
return {
color: COLORS[feature.properties.state],
className: `zone-${feature.id}`,
...baseAttributes,
};
};
App.renderTooltip = feature => {
let props = feature.properties;
let seedId = props.seed;
let seedHtml = "";
if (seedId) {
const seed = App.seeds[seedId.toString()];
seedHtml = `
${seed.name}
`;
}
let custodianHtml = props.custodian_name
? `
${props.custodian_name}
`
: "";
return `
${props.name}
${
props.state_str
}
${seedHtml}
${custodianHtml}
`;
};
App.getCenter = feature => {
const coords = feature.geometry.coordinates[0];
const xs = coords.map(c => c[0]);
const ys = coords.map(c => c[1]);
const minx = Math.min(...xs);
const maxx = Math.max(...xs);
const miny = Math.min(...ys);
const maxy = Math.max(...ys);
return [(miny + maxy) / 2, (minx + maxx) / 2];
};
App.clickedZone = feature => {
document
.getElementById("map")
.dispatchEvent(new CustomEvent("map:click", { detail: feature }));
};
App.goToZoneFromHash = () => {
let chosenZone = document.location.hash.replace("#", "");
if (chosenZone == "") {
App.clearActiveZone();
} else {
App.clickedZone({ id: parseInt(chosenZone) });
}
};
App.focusOnZone = id => {
App.clearActiveZone();
activeZone = `zone-${id}`;
let elem = document.getElementsByClassName(activeZone)[0];
elem.setAttribute("fill-opacity", selectedAttributes.fillOpacity);
elem.setAttribute("stroke-width", selectedAttributes.weight);
setTimeout(() => elem.focus(), 2);
let feature = App.findFeature(id);
App.map.setView(App.getCenter(feature), 15, {
animate: true,
});
};
App.clearActiveZone = () => {
try {
let elem = document.getElementsByClassName(activeZone)[0];
elem.setAttribute("fill-opacity", baseAttributes.fillOpacity);
elem.setAttribute("stroke-width", baseAttributes.weight);
} catch (e) {
// ignore
}
activeZone = "";
};
App.findFeature = id => {
return App.features.find(f => f.id === id);
};
App.initMap = async (map, options) => {
// Global variable App defined in map.html
App.map = map;
let spinner = document.getElementById("spinner");
spinner.classList.add("htmx-request");
// Download GeoJSON via Ajax
let resp = await fetch(App.geoDataUrl);
let data = await resp.json();
App.features = data.features;
// Download seed data
resp = await fetch(App.seedsUrl);
App.seeds = await resp.json();
spinner.classList.remove("htmx-request");
// https://github.com/brunob/leaflet.fullscreen
if (L.control.fullscreen) {
L.control
.fullscreen({
position: "topleft", // change the position of the button can be topleft, topright, bottomright or bottomleft, default topleft
title: "Pantalla completa", // change the title of the button, default Full Screen
titleCancel: "Surt de pantalla completa", // change the title of the button when fullscreen is on, default Exit Full Screen
// content: null, // change the content of the button, can be HTML, default null
forceSeparateButton: true, // force separate button to detach from zoom buttons, default false
forcePseudoFullscreen: true, // force use of pseudo full screen even if full screen API is available, default false
fullscreenElement: false, // Dom element to render in full screen, false by default, fallback to map._container
})
.addTo(map);
}
L.geoJson(data, {
// for the base layer
// interactive: false,
onEachFeature: (feature, layer) => {
feature.layer = layer;
// skip "base" features, those are non-interactive, e.g. perimeter
if (feature.properties.state !== "B") {
layer.bindTooltip(App.renderTooltip(feature), {
direction: "top",
offset: L.point(0, -4),
opacity: 1,
className: "z-10",
});
layer.on({
click: () => {
document.location.hash = feature.id.toString();
},
});
} else {
layer.interactive = false;
}
},
style: App.getZoneStyle,
}).addTo(map);
var legend = L.control({ position: "topright" });
legend.onAdd = function (map) {
let div = L.DomUtil.create("div", "info legend"),
elements = [
["L", "Desprotegida"],
["R", "Reservada"],
["C", "Custodiada"],
];
for (var [elem, label] of elements) {
div.innerHTML += `
${label}
`;
}
return div;
};
legend.addTo(map);
// Interactivity
let mapElem = document.getElementById("map");
mapElem.addEventListener("map:click", async e => {
App.focusOnZone(e.detail.id);
// WARNING: hardcoded as we would need to reverse from JS
let url = `/zones/${e.detail.id}?x=true`;
let resp = await htmx.ajax("GET", url, {
target: "#sidebar",
swap: "innerHTML",
});
_hyperscript.browserInit();
document.getElementById("sidebar-close").classList.remove("hidden");
});
mapElem.addEventListener("zone:refresh", e => {
let feature = App.findFeature(e.detail.id);
console.log("Handle zone:refresh", { detail: e.detail, feature });
if (e.detail.state) {
feature.properties.custodian_name = e.detail.custodian_name;
feature.properties.state = e.detail.state;
feature.properties.state_str = e.detail.state_str;
}
// update map element in case zone state has changed
// (after form submission)
let style = App.getZoneStyle(feature);
if (e.detail.autofocus) {
style.fillOpacity = selectedAttributes.fillOpacity;
style.weight = selectedAttributes.weight;
}
feature.layer.setStyle(style);
feature.layer.setTooltipContent(App.renderTooltip(feature));
});
App.refreshedZone = (zone, autofocus = true) => {
document
.getElementById("map")
.dispatchEvent(
new CustomEvent("zone:refresh", { detail: { ...zone, autofocus } })
);
};
window.addEventListener("hashchange", event => {
App.goToZoneFromHash();
});
// upon page load, honor hash in URL to be zone id
App.goToZoneFromHash();
};