Eishaan's picture
Redesign site analysis workspace UI
d01e724
Raw
History Blame Contribute Delete
8.87 kB
function initSiteMap() {
const mapNode = document.getElementById("site-map");
if (!mapNode || mapNode.dataset.ready === "true" || typeof L === "undefined") return false;
mapNode.dataset.ready = "true";
const map = L.map("site-map", { zoomControl: true }).setView([20.92, 70.36], 13);
const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
let mode = "pin";
let points = [];
let markers = [];
let shapeLayer = null;
let pinMarker = null;
let radiusCircle = null;
const status = document.getElementById("map-status");
const radiusSelect = document.getElementById("radius-select");
const buttons = {
polygon: document.getElementById("mode-polygon"),
rectangle: document.getElementById("mode-rectangle"),
pin: document.getElementById("mode-pin")
};
function setStatus(text) {
if (status) status.textContent = text;
}
let tileErrorCount = 0;
let tileLoadCount = 0;
tileLayer.on("tileload", () => {
tileLoadCount += 1;
});
tileLayer.on("tileerror", () => {
tileErrorCount += 1;
if (tileErrorCount === 3) {
setStatus("Base map tiles are unavailable in this browser/network. Drawing still works; verify the boundary with CAD/KML/GeoJSON or coordinates.");
}
});
setTimeout(() => {
if (tileLoadCount === 0) {
setStatus("Base map tiles are unavailable in this browser/network. Drawing still works; verify the boundary with CAD/KML/GeoJSON or coordinates.");
}
}, 3500);
function setMode(nextMode) {
mode = nextMode;
Object.entries(buttons).forEach(([key, btn]) => {
if (btn) btn.classList.toggle("active", key === mode);
});
if (mode === "polygon") setStatus("Polygon mode: click corners, then Finish.");
if (mode === "rectangle") setStatus("Rectangle mode: click two opposite corners.");
if (mode === "pin") setStatus("Pin mode: click site center and choose radius.");
}
function setGradioState(state) {
const textarea = document.querySelector("#map_state textarea");
if (!textarea) return;
textarea.value = JSON.stringify(state);
textarea.dispatchEvent(new Event("input", { bubbles: true }));
textarea.dispatchEvent(new Event("change", { bubbles: true }));
}
function clearDrawing(pushState = true) {
markers.forEach((m) => map.removeLayer(m));
markers = [];
points = [];
if (shapeLayer) map.removeLayer(shapeLayer);
if (pinMarker) map.removeLayer(pinMarker);
if (radiusCircle) map.removeLayer(radiusCircle);
shapeLayer = null;
pinMarker = null;
radiusCircle = null;
if (pushState) setGradioState({});
setStatus("Selection cleared.");
}
function drawPolygonPreview(closeIt = false) {
if (shapeLayer) map.removeLayer(shapeLayer);
if (points.length < 2) return;
const latLngs = closeIt ? [...points, points[0]] : points;
shapeLayer = L.polyline(latLngs, { color: "#123b51", weight: 3 }).addTo(map);
if (closeIt && points.length >= 3) {
map.removeLayer(shapeLayer);
shapeLayer = L.polygon(points, { color: "#123b51", fillColor: "#0f766e", fillOpacity: 0.22, weight: 3 }).addTo(map);
}
}
function finishPolygon() {
if (points.length < 3) {
setStatus("A polygon needs at least three points.");
return;
}
drawPolygonPreview(true);
const ring = points.map((p) => [p.lng, p.lat]);
ring.push([points[0].lng, points[0].lat]);
setGradioState({
mode: "polygon",
geometry: { type: "Polygon", coordinates: [ring] }
});
setStatus(`Polygon finished with ${points.length} points. Drawn boundaries are approximate.`);
}
function finishRectangle(a, b) {
clearDrawing(false);
const corners = [
L.latLng(a.lat, a.lng),
L.latLng(a.lat, b.lng),
L.latLng(b.lat, b.lng),
L.latLng(b.lat, a.lng)
];
points = corners;
shapeLayer = L.polygon(corners, { color: "#123b51", fillColor: "#0f766e", fillOpacity: 0.22, weight: 3 }).addTo(map);
const ring = corners.map((p) => [p.lng, p.lat]);
ring.push([corners[0].lng, corners[0].lat]);
setGradioState({
mode: "rectangle",
geometry: { type: "Polygon", coordinates: [ring] }
});
setStatus("Rectangle selection finished. Drawn boundaries are approximate.");
}
function placePin(latlng) {
if (pinMarker) map.removeLayer(pinMarker);
if (radiusCircle) map.removeLayer(radiusCircle);
pinMarker = L.circleMarker(latlng, {
radius: 7,
color: "#123b51",
fillColor: "#ffffff",
fillOpacity: 1,
weight: 3
}).addTo(map);
const radius = Number(radiusSelect ? radiusSelect.value : 250);
radiusCircle = L.circle(latlng, { radius, color: "#123b51", fillColor: "#0f766e", fillOpacity: 0.16 }).addTo(map);
setGradioState({
mode: "pin_radius",
lat: latlng.lat,
lon: latlng.lng,
radius_m: radius
});
setStatus(`Pin set at ${latlng.lat.toFixed(6)}, ${latlng.lng.toFixed(6)} with ${radius} m radius.`);
}
map.on("click", (event) => {
if (mode === "pin") {
clearDrawing(false);
placePin(event.latlng);
return;
}
if (mode === "polygon") {
const marker = L.circleMarker(event.latlng, { radius: 5, color: "#123b51", fillColor: "#c88920", fillOpacity: 1 }).addTo(map);
markers.push(marker);
points.push(event.latlng);
drawPolygonPreview(false);
setStatus(`Polygon point ${points.length}: ${event.latlng.lat.toFixed(6)}, ${event.latlng.lng.toFixed(6)}`);
return;
}
if (mode === "rectangle") {
points.push(event.latlng);
markers.push(L.circleMarker(event.latlng, { radius: 5, color: "#123b51", fillColor: "#c88920", fillOpacity: 1 }).addTo(map));
if (points.length === 2) finishRectangle(points[0], points[1]);
else setStatus("Rectangle mode: click opposite corner.");
}
});
if (buttons.polygon) buttons.polygon.addEventListener("click", () => { clearDrawing(); setMode("polygon"); });
if (buttons.rectangle) buttons.rectangle.addEventListener("click", () => { clearDrawing(); setMode("rectangle"); });
if (buttons.pin) buttons.pin.addEventListener("click", () => { clearDrawing(); setMode("pin"); });
const finishBtn = document.getElementById("finish-shape");
if (finishBtn) finishBtn.addEventListener("click", finishPolygon);
const undoBtn = document.getElementById("undo-point");
if (undoBtn) undoBtn.addEventListener("click", () => {
if (points.length > 0) {
points.pop();
const marker = markers.pop();
if (marker) map.removeLayer(marker);
drawPolygonPreview(false);
setStatus(`Removed last point. ${points.length} points remain.`);
}
});
const clearBtn = document.getElementById("clear-map");
if (clearBtn) clearBtn.addEventListener("click", () => clearDrawing());
if (radiusSelect) radiusSelect.addEventListener("change", () => {
if (pinMarker) placePin(pinMarker.getLatLng());
});
setMode("pin");
setTimeout(() => map.invalidateSize(), 250);
return true;
}
let leafletFallbackRequested = false;
function requestLeafletFallback() {
if (leafletFallbackRequested) return;
leafletFallbackRequested = true;
const status = document.getElementById("map-status");
if (status) status.textContent = "Map library is taking longer than expected. Trying fallback map assets...";
const css = document.createElement("link");
css.rel = "stylesheet";
css.href = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css";
document.head.appendChild(css);
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js";
script.onload = () => initSiteMap();
script.onerror = () => {
if (status) {
status.textContent = "Map library could not load. Use the lat/lon field or upload DXF/KML/GeoJSON, then generate the report.";
}
};
document.head.appendChild(script);
}
function waitForLeafletAndInit(attempt = 0) {
const hasMapNode = Boolean(document.getElementById("site-map"));
if (typeof L !== "undefined" && hasMapNode) {
if (initSiteMap()) return;
}
if (typeof L !== "undefined" && !hasMapNode) {
const status = document.getElementById("map-status");
if (status) status.textContent = "Map UI is loading...";
}
if (attempt === 6 && typeof L === "undefined") requestLeafletFallback();
if (attempt > 80) {
const status = document.getElementById("map-status");
if (status) {
status.textContent = "Map could not initialize. Use the lat/lon field or upload DXF/KML/GeoJSON, then generate the report.";
}
return;
}
setTimeout(() => waitForLeafletAndInit(attempt + 1), 250);
}
waitForLeafletAndInit();