diff --git a/examples/deckgl-demo/app.js b/examples/deckgl-demo/app.js new file mode 100644 index 0000000..b862059 --- /dev/null +++ b/examples/deckgl-demo/app.js @@ -0,0 +1,278 @@ +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: 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"); + + 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); + this.updatePolygonLayer(); + } + + 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 + 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, + getPosition: d => d, + 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; + } + + this.resetDrawingState(); + + // 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.resetDrawingState(); + + 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/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...
+
+
+ + +
+
+ + + + diff --git a/examples/deckgl-demo/index.html b/examples/deckgl-demo/index.html new file mode 100644 index 0000000..e45552a --- /dev/null +++ b/examples/deckgl-demo/index.html @@ -0,0 +1,27 @@ + + + + GeoAI.js + Deck.gl Demo + + + + + + + + +
Initializing...
+
+
+ + +
+
+ + \ No newline at end of file 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); } +}