| 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: '© <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(); |
|
|