From 8e98230faa66120aa4b936beefa6ef66add42f59 Mon Sep 17 00:00:00 2001 From: Shoaib Burq Date: Sun, 31 Aug 2025 00:45:43 +0200 Subject: [PATCH 1/4] Add nodes and loader --- examples/deckgl-demo/app.js | 294 ++++++++++++++++++++++++++++++++ examples/deckgl-demo/index.html | 103 +++++++++++ 2 files changed, 397 insertions(+) create mode 100644 examples/deckgl-demo/app.js create mode 100644 examples/deckgl-demo/index.html diff --git a/examples/deckgl-demo/app.js b/examples/deckgl-demo/app.js new file mode 100644 index 0000000..7c18ce7 --- /dev/null +++ b/examples/deckgl-demo/app.js @@ -0,0 +1,294 @@ +import { geoai } from "https://cdn.jsdelivr.net/npm/geoai@1.0.3/geoai.js"; + +const { Deck, TileLayer, BitmapLayer, GeoJsonLayer, ScatterplotLayer } = deck; + +const INITIAL_VIEW_STATE = { + longitude: 56.35167998561383, + latitude: 25.204961334530914, + zoom: 15, + pitch: 0, + bearing: 0, +}; + +const mapProviderConfig = { + provider: "esri", + serviceUrl: "https://server.arcgisonline.com/ArcGIS/rest/services", + serviceName: "World_Imagery", + tileSize: 256, + attribution: "ESRI World Imagery", +}; + +class DeckGLDemo { + constructor() { + this.deck = null; + this.pipeline = null; + this.currentPolygon = []; + this.detectionResults = null; + this.isDrawing = false; + this.satelliteLayer = null; + this.initializeApp(); + } + + async initializeApp() { + this.updateStatus("Initializing AI Model...", "#ffa500"); + + try { + // Create satellite layer once + this.satelliteLayer = this.createSatelliteLayer(); + + // Initialize Deck.gl + this.deck = new Deck({ + container: "map", + initialViewState: INITIAL_VIEW_STATE, + controller: { + dragPan: true, + dragRotate: true, + doubleClickZoom: true, + touchZoom: true, + touchRotate: true, + keyboard: true, + scrollZoom: true, + }, + layers: [this.satelliteLayer], + onClick: this.handleMapClick.bind(this), + }); + + // Initialize GeoAI pipeline + this.pipeline = await geoai.pipeline( + [{ task: "oil-storage-tank-detection" }], + mapProviderConfig + ); + + this.updateStatus( + 'AI Model Ready! Click "Draw Polygon" and click on map to create polygon.', + "#4caf50" + ); + document.getElementById("draw-polygon").disabled = false; + document.getElementById("clear-map").disabled = false; + } catch (error) { + console.error("Initialization error:", error); + this.updateStatus("Failed to Initialize Model", "#f44336"); + } + + this.setupEventListeners(); + } + + createSatelliteLayer() { + return new TileLayer({ + id: "arcgis-world-imagery", + data: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", + tileSize: 256, + minZoom: 0, + maxZoom: 19, + loadOptions: { image: { type: "imagebitmap" } }, + onTileError: err => console.error("tile error", err), + renderSubLayers: props => { + const { boundingBox } = props.tile; + return new BitmapLayer(props, { + data: null, + image: props.data, + bounds: [ + boundingBox[0][0], + boundingBox[0][1], + boundingBox[1][0], + boundingBox[1][1], + ], + }); + }, + }); + } + + setupEventListeners() { + document.getElementById("draw-polygon").addEventListener("click", () => { + this.toggleDrawing(); + }); + + document.getElementById("clear-map").addEventListener("click", () => { + this.clearMap(); + }); + } + + toggleDrawing() { + this.isDrawing = !this.isDrawing; + const button = document.getElementById("draw-polygon"); + const mapElement = document.getElementById("map"); + + if (this.isDrawing) { + button.textContent = "Finish Polygon"; + button.style.background = "#ff9800"; + this.updateStatus( + 'Click on map to add points. Click "Finish Polygon" when done.', + "#2196f3" + ); + this.currentPolygon = []; + } else { + this.finishPolygon(); + } + } + + handleMapClick(info) { + if (!this.isDrawing) return; + + const { coordinate } = info; + this.currentPolygon.push(coordinate); + + // Update visual feedback + this.updatePolygonLayer(); + } + + updatePolygonLayer() { + console.log("updatePolygonLayer called", this.currentPolygon.length); + + const layers = [this.satelliteLayer]; + + // Add current polygon being drawn + if (this.currentPolygon.length > 0) { + const polygonGeoJson = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: this.currentPolygon.length > 2 ? "Polygon" : "LineString", + coordinates: + this.currentPolygon.length > 2 + ? [[...this.currentPolygon, this.currentPolygon[0]]] + : this.currentPolygon, + }, + }, + ], + }; + + layers.push( + new GeoJsonLayer({ + id: "drawing-polygon", + data: polygonGeoJson, + getFillColor: [0, 0, 255, 100], + getLineColor: [0, 0, 255, 255], + getLineWidth: 2, + filled: this.currentPolygon.length > 2, + stroked: true, + }) + ); + + // Add point markers for each clicked node + layers.push( + new ScatterplotLayer({ + id: "drawing-points", + data: this.currentPolygon.map((coord, index) => ({ + position: coord, + index: index, + })), + getPosition: d => d.position, + getRadius: 8, + getFillColor: [255, 255, 255, 255], + getLineColor: [0, 0, 255, 255], + getLineWidth: 2, + stroked: true, + filled: true, + }) + ); + } + + // Add detection results if they exist + if (this.detectionResults) { + layers.push( + new GeoJsonLayer({ + id: "detections", + data: this.detectionResults, + getFillColor: [255, 0, 0, 128], + getLineColor: [255, 0, 0, 255], + getLineWidth: 2, + filled: true, + stroked: true, + }) + ); + } + + this.deck.setProps({ layers }); + } + + async finishPolygon() { + if (this.currentPolygon.length < 3) { + alert("Please draw at least 3 points to create a polygon"); + return; + } + + const button = document.getElementById("draw-polygon"); + button.textContent = "Draw Polygon"; + button.style.background = "#2196f3"; + this.isDrawing = false; + + // Create GeoJSON polygon + const polygon = { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [[...this.currentPolygon, this.currentPolygon[0]]], + }, + }; + + // Run detection + this.updateStatus("Processing detection...", "#2196f3"); + this.showLoader(); + + try { + const result = await this.pipeline.inference({ + inputs: { polygon }, + mapSourceParams: { zoomLevel: 15 }, + }); + + this.detectionResults = result.detections; + this.updatePolygonLayer(); + + const count = result.detections.features?.length || 0; + this.updateStatus( + `Found ${count} oil storage tank${count !== 1 ? "s" : ""}!`, + "#4caf50" + ); + } catch (error) { + console.error("Detection error:", error); + this.updateStatus("Error during detection", "#f44336"); + } finally { + this.hideLoader(); + } + } + + clearMap() { + this.currentPolygon = []; + this.detectionResults = null; + this.isDrawing = false; + + const button = document.getElementById("draw-polygon"); + button.textContent = "Draw Polygon"; + button.style.background = "#2196f3"; + + this.deck.setProps({ + layers: [this.satelliteLayer], + }); + + this.updateStatus( + 'AI Model Ready! Click "Draw Polygon" and click on map to create polygon.', + "#4caf50" + ); + } + + updateStatus(text, color) { + const statusEl = document.getElementById("status"); + statusEl.textContent = text; + statusEl.style.background = color; + } + + showLoader() { + const loader = document.getElementById("loader"); + loader.style.display = "block"; + } + + hideLoader() { + const loader = document.getElementById("loader"); + loader.style.display = "none"; + } +} + +// Start the demo when DOM is ready +new DeckGLDemo(); diff --git a/examples/deckgl-demo/index.html b/examples/deckgl-demo/index.html new file mode 100644 index 0000000..c195871 --- /dev/null +++ b/examples/deckgl-demo/index.html @@ -0,0 +1,103 @@ + + + + GeoAI.js + Deck.gl Demo + + + + + + + + +
Initializing...
+
+
+ + +
+
+ + \ No newline at end of file From e839b2d0641531ea7b3c2a325af15c1e6a0f2a29 Mon Sep 17 00:00:00 2001 From: Shoaib Burq Date: Sun, 31 Aug 2025 00:48:49 +0200 Subject: [PATCH 2/4] Clean up code --- examples/deckgl-demo/app.js | 40 +++++++++++-------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/examples/deckgl-demo/app.js b/examples/deckgl-demo/app.js index 7c18ce7..b862059 100644 --- a/examples/deckgl-demo/app.js +++ b/examples/deckgl-demo/app.js @@ -40,15 +40,7 @@ class DeckGLDemo { this.deck = new Deck({ container: "map", initialViewState: INITIAL_VIEW_STATE, - controller: { - dragPan: true, - dragRotate: true, - doubleClickZoom: true, - touchZoom: true, - touchRotate: true, - keyboard: true, - scrollZoom: true, - }, + controller: true, layers: [this.satelliteLayer], onClick: this.handleMapClick.bind(this), }); @@ -111,7 +103,6 @@ class DeckGLDemo { toggleDrawing() { this.isDrawing = !this.isDrawing; const button = document.getElementById("draw-polygon"); - const mapElement = document.getElementById("map"); if (this.isDrawing) { button.textContent = "Finish Polygon"; @@ -131,14 +122,17 @@ class DeckGLDemo { const { coordinate } = info; this.currentPolygon.push(coordinate); - - // Update visual feedback this.updatePolygonLayer(); } - updatePolygonLayer() { - console.log("updatePolygonLayer called", this.currentPolygon.length); + resetDrawingState() { + this.isDrawing = false; + const button = document.getElementById("draw-polygon"); + button.textContent = "Draw Polygon"; + button.style.background = "#2196f3"; + } + updatePolygonLayer() { const layers = [this.satelliteLayer]; // Add current polygon being drawn @@ -175,11 +169,8 @@ class DeckGLDemo { layers.push( new ScatterplotLayer({ id: "drawing-points", - data: this.currentPolygon.map((coord, index) => ({ - position: coord, - index: index, - })), - getPosition: d => d.position, + data: this.currentPolygon, + getPosition: d => d, getRadius: 8, getFillColor: [255, 255, 255, 255], getLineColor: [0, 0, 255, 255], @@ -214,10 +205,7 @@ class DeckGLDemo { return; } - const button = document.getElementById("draw-polygon"); - button.textContent = "Draw Polygon"; - button.style.background = "#2196f3"; - this.isDrawing = false; + this.resetDrawingState(); // Create GeoJSON polygon const polygon = { @@ -257,11 +245,7 @@ class DeckGLDemo { clearMap() { this.currentPolygon = []; this.detectionResults = null; - this.isDrawing = false; - - const button = document.getElementById("draw-polygon"); - button.textContent = "Draw Polygon"; - button.style.background = "#2196f3"; + this.resetDrawingState(); this.deck.setProps({ layers: [this.satelliteLayer], From e5aa3974c540039e34aec007c83b753220be19a6 Mon Sep 17 00:00:00 2001 From: Shoaib Burq Date: Sun, 31 Aug 2025 00:55:23 +0200 Subject: [PATCH 3/4] Extract CSS to separate file --- examples/deckgl-demo/index.html | 86 ++------------------------------ examples/deckgl-demo/styles.css | 87 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 81 deletions(-) create mode 100644 examples/deckgl-demo/styles.css diff --git a/examples/deckgl-demo/index.html b/examples/deckgl-demo/index.html index c195871..e45552a 100644 --- a/examples/deckgl-demo/index.html +++ b/examples/deckgl-demo/index.html @@ -5,91 +5,15 @@ + -
Initializing...
diff --git a/examples/deckgl-demo/styles.css b/examples/deckgl-demo/styles.css new file mode 100644 index 0000000..cedc6c4 --- /dev/null +++ b/examples/deckgl-demo/styles.css @@ -0,0 +1,87 @@ +body { + margin: 0; + padding: 0; + font-family: 'Helvetica Neue', Arial, sans-serif; + overflow: hidden; +} + +#map { + position: fixed; + top: 70px; + left: 0; + right: 0; + bottom: 0; +} + +#status { + position: fixed; + top: 0; + left: 0; + right: 0; + background: #9e9e9e; + color: white; + padding: 16px; + font-size: 20px; + font-weight: bold; + text-align: center; + z-index: 1000; +} + +#controls { + position: fixed; + top: 90px; + right: 20px; + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + z-index: 10000; +} + +button { + display: block; + width: 100%; + margin: 10px 0; + padding: 12px; + font-size: 16px; + border: none; + border-radius: 4px; + cursor: pointer; + background: #2196f3; + color: white; + transition: background-color 0.2s ease; +} + +button:hover { + background: #1976d2; +} + +button:disabled { + background: #ccc; + cursor: not-allowed; +} + +button:disabled:hover { + background: #ccc; +} + +.loader { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 50px; + height: 50px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top: 4px solid #2196f3; + border-radius: 50%; + animation: spin 1s linear infinite; + background: rgba(255, 255, 255, 0.5); + z-index: 10000; + display: none; +} + +@keyframes spin { + 0% { transform: translate(-50%, -50%) rotate(0deg); } + 100% { transform: translate(-50%, -50%) rotate(360deg); } +} From c996b36d02ad650141b7506a75d0227accf8ae83 Mon Sep 17 00:00:00 2001 From: Shoaib Burq Date: Sun, 31 Aug 2025 01:05:20 +0200 Subject: [PATCH 4/4] Create codepen.html --- examples/deckgl-demo/codepen.html | 395 ++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 examples/deckgl-demo/codepen.html diff --git a/examples/deckgl-demo/codepen.html b/examples/deckgl-demo/codepen.html new file mode 100644 index 0000000..9559d03 --- /dev/null +++ b/examples/deckgl-demo/codepen.html @@ -0,0 +1,395 @@ + + + + GeoAI.js + Deck.gl Demo + + + + + + + +
Initializing...
+
+
+ + +
+
+ + + +