geoai 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +162 -207
- package/geoai.js +309 -2
- package/index.d.ts +108 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.8] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Image geolocation task (`image-geolocation`) powered by GeoCLIP Q4 (`Xenova/geoclip-large-patch14`)
|
|
14
|
+
- Live demo and docs for image geolocation (`/tasks/image-geolocation`)
|
|
15
|
+
- Google Maps provider (`provider: "google"`) via Map Tiles API session + 2D satellite XYZ tiles
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- OAM item tile URLs: drop deprecated HOT TiTiler `@1x` path suffix (was returning HTTP 422)
|
|
19
|
+
|
|
10
20
|
## [1.0.7] - 2026-07-17
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
|
@@ -1,295 +1,250 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
-
GeoAI
|
|
2
|
+
GeoAI.js
|
|
3
3
|
</h1>
|
|
4
4
|
|
|
5
|
-
<p align="center"
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Geospatial AI that runs in the browser.</strong><br/>
|
|
7
|
+
Detect buildings, vehicles, ships, solar panels, and more — on satellite & aerial imagery — without a GPU backend.
|
|
8
|
+
</p>
|
|
6
9
|
|
|
7
|
-
<p align="center"
|
|
10
|
+
<p align="center">
|
|
8
11
|
<a href="https://www.npmjs.com/package/geoai"><img alt="npm version" src="https://img.shields.io/npm/v/geoai.svg"></a>
|
|
9
|
-
<a href="https://github.com/decision-labs/geoai.js/actions/workflows/main.yml"
|
|
10
|
-
<a href="https://www.npmjs.com/package/geoai"
|
|
11
|
-
<a href="https://docs.geobase.app/geoai"
|
|
12
|
+
<a href="https://github.com/decision-labs/geoai.js/actions/workflows/main.yml"><img alt="CI" src="https://github.com/decision-labs/geoai.js/actions/workflows/main.yml/badge.svg"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/geoai"><img alt="npm downloads" src="https://img.shields.io/npm/dm/geoai.svg"></a>
|
|
14
|
+
<a href="https://docs.geobase.app/geoai"><img alt="docs" src="https://img.shields.io/badge/docs-online-blue"></a>
|
|
15
|
+
<a href="LICENSE.md"><img alt="license" src="https://img.shields.io/badge/license-MIT-green"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://docs.geobase.app/geoai-live"><strong>Live demos →</strong></a>
|
|
20
|
+
·
|
|
21
|
+
<a href="https://docs.geobase.app/geoai"><strong>Docs →</strong></a>
|
|
22
|
+
·
|
|
23
|
+
<a href="#agents--llms"><strong>Agent skill →</strong></a>
|
|
12
24
|
</p>
|
|
13
25
|
|
|
14
26
|
<div align="center">
|
|
15
|
-
<img src="_docs4devs/geoai.gif" alt="GeoAI
|
|
27
|
+
<img src="_docs4devs/geoai.gif" alt="GeoAI.js DINOv3 feature extraction demo" width="85%">
|
|
16
28
|
</div>
|
|
17
29
|
|
|
18
|
-
<p align="center"
|
|
19
|
-
Caption: <i>example of feature extraction using the transformerjs version of dino v3, see examples at <a href="https://docs.geobase.app/geoai-live/" style="font-style:normal;">docs.geobase.app/geoai-live</a></i>
|
|
20
|
-
</p>
|
|
30
|
+
<p align="center"><em>DINOv3 image feature extraction in the browser — try it at <a href="https://docs.geobase.app/geoai-live">docs.geobase.app/geoai-live</a></em></p>
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
---
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
## Why GeoAI.js?
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
| | |
|
|
37
|
+
|---|---|
|
|
38
|
+
| **Client-side** | Models run in the browser (WebGPU / WASM) via Transformers.js + ONNX Runtime |
|
|
39
|
+
| **Draw → detect** | Pass a GeoJSON polygon; get detections back as GeoJSON |
|
|
40
|
+
| **Your imagery** | ESRI, Mapbox, Geobase COGs, TMS, WMS, OpenAerialMap, Google Map Tiles |
|
|
41
|
+
| **Pipeline-ready** | Chain tasks; run inference off the main thread with workers |
|
|
28
42
|
|
|
29
43
|
```bash
|
|
30
44
|
npm i geoai
|
|
45
|
+
# peers
|
|
46
|
+
npm i @huggingface/transformers onnxruntime-web
|
|
31
47
|
```
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- [jsDelivr CDN](https://www.jsdelivr.com/package/npm/geoai)
|
|
36
|
-
|
|
37
|
-
### CDN (Browser)
|
|
49
|
+
```javascript
|
|
50
|
+
import { geoai } from "geoai";
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
const pipeline = await geoai.pipeline([{ task: "building-detection" }], {
|
|
53
|
+
provider: "esri", // no API key
|
|
54
|
+
});
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
const result = await pipeline.inference({
|
|
57
|
+
inputs: { polygon: myGeoJsonPolygon },
|
|
58
|
+
mapSourceParams: { zoomLevel: 18 },
|
|
59
|
+
});
|
|
60
|
+
// result.detections → GeoJSON FeatureCollection
|
|
44
61
|
```
|
|
45
62
|
|
|
46
|
-
|
|
63
|
+
CDN:
|
|
64
|
+
|
|
47
65
|
```html
|
|
48
|
-
<script src="https://
|
|
66
|
+
<script src="https://unpkg.com/geoai@1.0.8/geoai.js"></script>
|
|
67
|
+
<!-- or -->
|
|
68
|
+
<script src="https://cdn.jsdelivr.net/npm/geoai@1.0.8/geoai.min.js"></script>
|
|
49
69
|
```
|
|
50
70
|
|
|
51
|
-
|
|
71
|
+
---
|
|
52
72
|
|
|
53
|
-
|
|
73
|
+
## Use cases
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
git init
|
|
57
|
-
touch README.md
|
|
58
|
-
git add .
|
|
59
|
-
git commit -m "Initial commit"
|
|
60
|
-
git subtree add --prefix=examples/01-quickstart https://github.com/decision-labs/geoai.js main --squash
|
|
61
|
-
```
|
|
75
|
+
GeoAI.js is built for product teams and researchers who need **interactive** geospatial AI — not batch jobs on a remote GPU cluster.
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
- **Urban inventory** — building footprints & boxes for planning, insurance, or change tracking
|
|
78
|
+
- **Energy & infrastructure** — solar panels, oil storage tanks, utility assets
|
|
79
|
+
- **Transport & logistics** — cars, trucks, ships in ports and yards
|
|
80
|
+
- **Environment** — land cover and wetland segmentation over AOIs
|
|
81
|
+
- **Exploration** — zero-shot detection, DINOv3 embeddings, and image geolocation (GeoCLIP)
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
Draw an AOI on a map, run a model, store or style the GeoJSON — all in one frontend session.
|
|
66
84
|
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
import { geoai } from "geoai";
|
|
70
|
-
|
|
71
|
-
// Initialize the pipeline with ESRI provider (no API key required)
|
|
72
|
-
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
|
|
73
|
-
provider: "esri",
|
|
74
|
-
});
|
|
85
|
+
### Image geolocation
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
```javascript
|
|
88
|
+
const pipeline = await geoai.pipeline(
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
task: "image-geolocation",
|
|
92
|
+
modelParams: { dtype: "q4", device: "webgpu" },
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
{ provider: "esri" }
|
|
96
|
+
);
|
|
77
97
|
const result = await pipeline.inference({
|
|
78
|
-
inputs: {
|
|
79
|
-
polygon: geoJsonFeature,
|
|
80
|
-
},
|
|
81
|
-
mapSourceParams: {
|
|
82
|
-
zoomLevel: 18,
|
|
83
|
-
},
|
|
98
|
+
inputs: { image: uploadedFile, topK: 5 },
|
|
84
99
|
});
|
|
100
|
+
// result.predictions → ranked [lat, lon] pairs
|
|
85
101
|
```
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
// When using CDN, geoai is available globally
|
|
90
|
-
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
|
|
91
|
-
provider: "esri",
|
|
92
|
-
});
|
|
103
|
+
See the [image geolocation docs](https://docs.geobase.app/geoai/supported-tasks/image-geolocation).
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
inputs: {
|
|
96
|
-
polygon: geoJsonFeature,
|
|
97
|
-
},
|
|
98
|
-
mapSourceParams: {
|
|
99
|
-
zoomLevel: 18,
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
```
|
|
105
|
+
---
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
## Integrations
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
import { useGeoAIWorker } from "geoai/react";
|
|
108
|
-
|
|
109
|
-
function MyComponent() {
|
|
110
|
-
const { inference, isLoading, error } = useGeoAIWorker({
|
|
111
|
-
tasks: ["object-detection"],
|
|
112
|
-
provider: "esri", // No API key required for ESRI
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const handleInference = async () => {
|
|
116
|
-
const result = await inference({
|
|
117
|
-
inputs: { polygon: geoJsonFeature },
|
|
118
|
-
mapSourceParams: { zoomLevel: 18 },
|
|
119
|
-
});
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<div>
|
|
124
|
-
<button onClick={handleInference} disabled={isLoading}>
|
|
125
|
-
{isLoading ? "Processing..." : "Run Detection"}
|
|
126
|
-
</button>
|
|
127
|
-
{error && <div>Error: {error.message}</div>}
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
```
|
|
109
|
+
Persist detections, sync in real time, and serve results as vector tiles.
|
|
132
110
|
|
|
133
|
-
|
|
111
|
+
<div align="center">
|
|
112
|
+
<img
|
|
113
|
+
src="examples/04-geoai-supabase-geobase-integration/docs/screenshot.jpg"
|
|
114
|
+
alt="GeoAI.js + Supabase + Geobase — map, detections, and workflow panel"
|
|
115
|
+
width="85%"
|
|
116
|
+
>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<p align="center"><em>Full-stack demo: draw → detect → save to PostGIS (Supabase or Geobase) with live map layers</em></p>
|
|
134
120
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
- **
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
121
|
+
| Stack | What you get |
|
|
122
|
+
|-------|----------------|
|
|
123
|
+
| **[Supabase](examples/04-geoai-supabase-geobase-integration)** | Auth, PostGIS storage, realtime subscriptions for detection history |
|
|
124
|
+
| **[Geobase](examples/04-geoai-supabase-geobase-integration)** | Same PostGIS path **plus** vector tileserver for thousands of styled detections |
|
|
125
|
+
| **[MapLibre / React](examples/live-examples-nextjs)** | Interactive task demos ([live](https://docs.geobase.app/geoai-live)); also [`01-quickstart`](examples/01-quickstart) |
|
|
126
|
+
| **[deck.gl](examples/deckgl-demo)** | GPU-friendly overlay workflows |
|
|
127
|
+
| **Agent skill** | Cursor / Claude / Codex integration via [`skills/geoai`](skills/geoai) |
|
|
142
128
|
|
|
143
|
-
|
|
129
|
+
Quick links:
|
|
144
130
|
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
- Ship Detection
|
|
149
|
-
- Solar Panel Detection
|
|
150
|
-
- Oil Storage Tank Detection
|
|
151
|
-
- Land Cover Classification
|
|
152
|
-
- Wetland Segmentation
|
|
153
|
-
- Building Footprint Segmentation
|
|
154
|
-
- Mask Generation
|
|
155
|
-
- Zero-shot Object Detection
|
|
156
|
-
- Zero-shot Segmentation
|
|
157
|
-
- Image Feature Extraction
|
|
131
|
+
- Example app + guide: [`examples/04-geoai-supabase-geobase-integration`](examples/04-geoai-supabase-geobase-integration)
|
|
132
|
+
- Integration write-up: [`INTEGRATION_GUIDE.md`](examples/04-geoai-supabase-geobase-integration/INTEGRATION_GUIDE.md)
|
|
133
|
+
- Interactive tasks: [docs.geobase.app/geoai-live](https://docs.geobase.app/geoai-live)
|
|
158
134
|
|
|
159
|
-
|
|
135
|
+
---
|
|
160
136
|
|
|
161
|
-
## Map
|
|
137
|
+
## Map providers
|
|
162
138
|
|
|
163
|
-
|
|
139
|
+
Point the pipeline at the imagery you already use:
|
|
164
140
|
|
|
165
|
-
|
|
166
|
-
|
|
141
|
+
| Provider | Auth | Notes |
|
|
142
|
+
|----------|------|--------|
|
|
143
|
+
| **ESRI** | None | World Imagery — great default for demos |
|
|
144
|
+
| **Mapbox** | Token | Satellite styles |
|
|
145
|
+
| **Geobase** | Project + API key | Your COG / tileserver |
|
|
146
|
+
| **TMS** | Optional | Any `{z}/{x}/{y}` raster URL |
|
|
147
|
+
| **WMS** | Usually none | OGC GetMap (e.g. NRW orthophotos) |
|
|
148
|
+
| **OpenAerialMap** | None | HOT Imagery STAC + TiTiler |
|
|
149
|
+
| **Google** | API key | Map Tiles API (session + XYZ); prefer a server proxy in browsers |
|
|
167
150
|
|
|
168
151
|
```javascript
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
provider: "tms",
|
|
172
|
-
baseUrl: "https://tile.example.com/tiles/{z}/{x}/{y}.png",
|
|
173
|
-
apiKey: "your-api-key", // optional
|
|
174
|
-
tileSize: 256, // optional, defaults to 256
|
|
175
|
-
attribution: "Custom TMS Provider", // optional
|
|
176
|
-
});
|
|
152
|
+
// Free public imagery
|
|
153
|
+
{ provider: "esri" }
|
|
177
154
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
provider: "tms",
|
|
181
|
-
baseUrl: "https://tile.example.com/tiles",
|
|
182
|
-
extension: "png", // optional, defaults to "png"
|
|
183
|
-
apiKey: "your-api-key", // optional
|
|
184
|
-
tileSize: 256, // optional, defaults to 256
|
|
185
|
-
attribution: "Custom TMS Provider", // optional
|
|
186
|
-
});
|
|
187
|
-
```
|
|
155
|
+
// Community aerial (OAM)
|
|
156
|
+
{ provider: "oam" /* , itemId, mosaic: true */ }
|
|
188
157
|
|
|
189
|
-
|
|
190
|
-
|
|
158
|
+
// Your COG on Geobase
|
|
159
|
+
{
|
|
160
|
+
provider: "geobase",
|
|
161
|
+
projectRef: "...",
|
|
162
|
+
apikey: "...",
|
|
163
|
+
cogImagery: "https://path-to-your-cog.tif",
|
|
164
|
+
}
|
|
191
165
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
provider: "oam",
|
|
195
|
-
// itemId: "67826781a07cc20001818cdb", // optional pin
|
|
196
|
-
// mosaic: true, // optional: collection mosaic only
|
|
197
|
-
});
|
|
166
|
+
// Google Map Tiles (enable Map Tiles API; avoid HTTP-referrer–only keys)
|
|
167
|
+
{ provider: "google", apiKey: process.env.GOOGLE_MAPS_API_KEY }
|
|
198
168
|
```
|
|
199
169
|
|
|
200
|
-
|
|
201
|
-
ESRI World Imagery - no API key required.
|
|
170
|
+
Docs: [Map providers](https://docs.geobase.app/geoai/map-providers)
|
|
202
171
|
|
|
203
|
-
|
|
204
|
-
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
|
|
205
|
-
provider: "esri",
|
|
206
|
-
});
|
|
207
|
-
```
|
|
172
|
+
---
|
|
208
173
|
|
|
209
|
-
|
|
210
|
-
Requires a Mapbox API key.
|
|
174
|
+
## Supported tasks
|
|
211
175
|
|
|
212
|
-
|
|
213
|
-
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
|
|
214
|
-
provider: "mapbox",
|
|
215
|
-
apiKey: "your-mapbox-api-key",
|
|
216
|
-
style: "mapbox://styles/mapbox/satellite-v9",
|
|
217
|
-
});
|
|
218
|
-
```
|
|
176
|
+
Object detection · Building detection · Building footprint segmentation (ChangeStar) · Car · Ship · Solar panel · Oil storage tank · Oriented object detection · Land cover · Wetland · Mask generation · Zero-shot detection & segmentation · Image feature extraction (DINOv3)
|
|
219
177
|
|
|
220
|
-
|
|
221
|
-
Requires Geobase project credentials.
|
|
178
|
+
→ [Full task reference](https://docs.geobase.app/geoai/supported-tasks)
|
|
222
179
|
|
|
223
|
-
|
|
224
|
-
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
|
|
225
|
-
provider: "geobase",
|
|
226
|
-
projectRef: "your-project-ref",
|
|
227
|
-
apikey: "your-api-key",
|
|
228
|
-
cogImagery: "https://path-to-your-cog.tif",
|
|
229
|
-
});
|
|
230
|
-
```
|
|
180
|
+
---
|
|
231
181
|
|
|
232
|
-
##
|
|
182
|
+
## Workers & React
|
|
233
183
|
|
|
234
|
-
**
|
|
184
|
+
Keep the UI smooth — run inference in a **Web Worker**. Patterns for vanilla JS and React live in the skill and docs (there is no separate `geoai/react` package export; use the worker + hook examples).
|
|
235
185
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
</a>
|
|
240
|
-
</div>
|
|
186
|
+
- [Workers guide](https://docs.geobase.app/geoai/workers)
|
|
187
|
+
- [React quickstart](https://docs.geobase.app/geoai/frontend-frameworks/react-quickstart)
|
|
188
|
+
- Example: [`examples/02-quickstart-with-workers`](examples/02-quickstart-with-workers)
|
|
241
189
|
|
|
242
|
-
|
|
243
|
-
- [Slide source (Marp)](talks/lightning-talk-webgpu-geoai.md)
|
|
190
|
+
---
|
|
244
191
|
|
|
245
|
-
##
|
|
192
|
+
## Quickstart clone
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
git init
|
|
196
|
+
git subtree add --prefix=examples/01-quickstart \
|
|
197
|
+
https://github.com/decision-labs/geoai.js main --squash
|
|
198
|
+
cd examples/01-quickstart && npm install && npm run dev
|
|
199
|
+
```
|
|
246
200
|
|
|
247
|
-
|
|
201
|
+
---
|
|
248
202
|
|
|
249
|
-
|
|
203
|
+
## Agents & LLMs
|
|
204
|
+
|
|
205
|
+
Teach coding agents how to integrate GeoAI.js:
|
|
250
206
|
|
|
251
207
|
```bash
|
|
252
|
-
# Install into the current project (recommended)
|
|
253
208
|
npx skills add decision-labs/geoai.js --skill geoai -y
|
|
254
|
-
|
|
255
|
-
# Or install globally for all projects
|
|
209
|
+
# or globally
|
|
256
210
|
npx skills add decision-labs/geoai.js --skill geoai -g -y
|
|
257
211
|
```
|
|
258
212
|
|
|
259
|
-
|
|
213
|
+
- Skill source: [`skills/geoai`](skills/geoai)
|
|
214
|
+
- LLM docs index: [llms.txt](https://docs.geobase.app/geoai/llms.txt)
|
|
215
|
+
- Full corpus: [llms-full.txt](https://docs.geobase.app/geoai/llms-full.txt) (`pnpm docs:llms`)
|
|
260
216
|
|
|
261
|
-
|
|
262
|
-
npx skills list
|
|
263
|
-
```
|
|
217
|
+
---
|
|
264
218
|
|
|
265
|
-
|
|
219
|
+
## Talk
|
|
266
220
|
|
|
267
|
-
|
|
268
|
-
npx skills use decision-labs/geoai.js@geoai
|
|
269
|
-
```
|
|
221
|
+
**[Bringing Earth Observation AI to the Browser with WebGPU](talks/lightning-talk-webgpu-geoai.md)** — Big Data from Space 2025 (Decision Labs).
|
|
270
222
|
|
|
271
|
-
|
|
223
|
+
<div align="center">
|
|
224
|
+
<a href="https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf">
|
|
225
|
+
<img src="https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/geoaijs.png" alt="GeoAI.js talk preview" width="480">
|
|
226
|
+
</a>
|
|
227
|
+
</div>
|
|
272
228
|
|
|
273
|
-
|
|
229
|
+
- [Slides (PDF)](https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf)
|
|
274
230
|
|
|
275
|
-
|
|
276
|
-
- **Full corpus**: [docs.geobase.app/geoai/llms-full.txt](https://docs.geobase.app/geoai/llms-full.txt) — regenerate with `pnpm docs:llms`
|
|
231
|
+
---
|
|
277
232
|
|
|
278
233
|
## Links
|
|
279
234
|
|
|
280
|
-
- **
|
|
281
|
-
- **Live
|
|
282
|
-
- **
|
|
283
|
-
- **
|
|
284
|
-
- **
|
|
285
|
-
- **Issues
|
|
235
|
+
- **Docs** — [docs.geobase.app/geoai](https://docs.geobase.app/geoai)
|
|
236
|
+
- **Live examples** — [docs.geobase.app/geoai-live](https://docs.geobase.app/geoai-live)
|
|
237
|
+
- **npm** — [geoai](https://www.npmjs.com/package/geoai)
|
|
238
|
+
- **Roadmap** — [_docs4devs/ROADMAP.md](_docs4devs/ROADMAP.md)
|
|
239
|
+
- **Discussions** — [GitHub Discussions](https://github.com/decision-labs/geoai.js/discussions)
|
|
240
|
+
- **Issues** — [GitHub Issues](https://github.com/decision-labs/geoai.js/issues)
|
|
286
241
|
|
|
287
242
|
## Contributing
|
|
288
243
|
|
|
289
|
-
|
|
244
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
290
245
|
|
|
291
246
|
## License
|
|
292
247
|
|
|
293
|
-
MIT
|
|
248
|
+
MIT — [LICENSE.md](LICENSE.md)
|
|
294
249
|
|
|
295
250
|
[//]: <> (Toggle CI off)
|
package/geoai.js
CHANGED
|
@@ -390,7 +390,7 @@ const postProcessYoloOutput = (model_outputs, pixel_values, rawImage, id2label)
|
|
|
390
390
|
return model_output;
|
|
391
391
|
};
|
|
392
392
|
const parametersChanged = (instance, model_id, providerParams, modelParams) => {
|
|
393
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
|
|
393
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
|
|
394
394
|
if (instance.model_id !== model_id) {
|
|
395
395
|
return true;
|
|
396
396
|
}
|
|
@@ -430,6 +430,11 @@ const parametersChanged = (instance, model_id, providerParams, modelParams) => {
|
|
|
430
430
|
return true;
|
|
431
431
|
}
|
|
432
432
|
break;
|
|
433
|
+
case "google":
|
|
434
|
+
if (((_s = instance.providerParams) == null ? void 0 : _s.apiKey) !== (providerParams == null ? void 0 : providerParams.apiKey) || ((_t = instance.providerParams) == null ? void 0 : _t.mapType) !== (providerParams == null ? void 0 : providerParams.mapType) || ((_u = instance.providerParams) == null ? void 0 : _u.sessionToken) !== (providerParams == null ? void 0 : providerParams.sessionToken)) {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
433
438
|
}
|
|
434
439
|
if (modelParams) {
|
|
435
440
|
const instanceModelParams = instance.modelParams || {};
|
|
@@ -1395,7 +1400,7 @@ function buildOamMosaicTileUrlTemplate(rasterUrl = DEFAULT_OAM_RASTER_URL, colle
|
|
|
1395
1400
|
}
|
|
1396
1401
|
function buildOamItemTileUrlTemplate(itemId, rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
|
|
1397
1402
|
const base = rasterUrl.replace(/\/$/, "");
|
|
1398
|
-
return `${base}/collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(itemId)}/tiles/WebMercatorQuad/{z}/{x}/{y}
|
|
1403
|
+
return `${base}/collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(itemId)}/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=${encodeURIComponent(asset)}`;
|
|
1399
1404
|
}
|
|
1400
1405
|
function pickBestOamItem(features) {
|
|
1401
1406
|
if (!features.length) return void 0;
|
|
@@ -1554,6 +1559,154 @@ class Oam extends MapSource {
|
|
|
1554
1559
|
return instance.tileUrlTemplate.replaceAll("{z}", z.toString()).replaceAll("{x}", x.toString()).replaceAll("{y}", y.toString());
|
|
1555
1560
|
}
|
|
1556
1561
|
}
|
|
1562
|
+
const DEFAULT_GOOGLE_TILE_API_URL = "https://tile.googleapis.com";
|
|
1563
|
+
const DEFAULT_GOOGLE_MAP_TYPE = "satellite";
|
|
1564
|
+
function buildGoogleCreateSessionUrl(apiKey, tileApiUrl = DEFAULT_GOOGLE_TILE_API_URL, includeApiKey = true) {
|
|
1565
|
+
const base = tileApiUrl.replace(/\/$/, "");
|
|
1566
|
+
if (!includeApiKey) {
|
|
1567
|
+
return `${base}/v1/createSession`;
|
|
1568
|
+
}
|
|
1569
|
+
return `${base}/v1/createSession?key=${encodeURIComponent(apiKey)}`;
|
|
1570
|
+
}
|
|
1571
|
+
function buildGoogleTileUrl(z, x, y, sessionToken, apiKey, tileApiUrl = DEFAULT_GOOGLE_TILE_API_URL, includeApiKey = true) {
|
|
1572
|
+
const base = tileApiUrl.replace(/\/$/, "");
|
|
1573
|
+
let url = `${base}/v1/2dtiles/${z}/${x}/${y}?session=${encodeURIComponent(sessionToken)}`;
|
|
1574
|
+
if (includeApiKey) {
|
|
1575
|
+
url += `&key=${encodeURIComponent(apiKey)}`;
|
|
1576
|
+
}
|
|
1577
|
+
return url;
|
|
1578
|
+
}
|
|
1579
|
+
async function createGoogleSession(options) {
|
|
1580
|
+
const apiKey = options.apiKey;
|
|
1581
|
+
const includeApiKey = options.includeApiKey !== false;
|
|
1582
|
+
if (includeApiKey && !apiKey) {
|
|
1583
|
+
throw new Error("Google Maps provider requires an apiKey");
|
|
1584
|
+
}
|
|
1585
|
+
const fetchImpl = options.fetchImpl || fetch;
|
|
1586
|
+
const url = buildGoogleCreateSessionUrl(
|
|
1587
|
+
apiKey,
|
|
1588
|
+
options.tileApiUrl || DEFAULT_GOOGLE_TILE_API_URL,
|
|
1589
|
+
includeApiKey
|
|
1590
|
+
);
|
|
1591
|
+
const response = await fetchImpl(url, {
|
|
1592
|
+
method: "POST",
|
|
1593
|
+
headers: {
|
|
1594
|
+
"Content-Type": "application/json",
|
|
1595
|
+
...options.headers || {}
|
|
1596
|
+
},
|
|
1597
|
+
body: JSON.stringify({
|
|
1598
|
+
mapType: options.mapType || DEFAULT_GOOGLE_MAP_TYPE,
|
|
1599
|
+
language: options.language || "en-US",
|
|
1600
|
+
region: options.region || "US"
|
|
1601
|
+
})
|
|
1602
|
+
});
|
|
1603
|
+
if (!response.ok) {
|
|
1604
|
+
const body = await response.text().catch(() => "");
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
`Google Map Tiles createSession failed (${response.status}): ${body || response.statusText}`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
const data = await response.json();
|
|
1610
|
+
if (!data.session) {
|
|
1611
|
+
throw new Error("Google Map Tiles createSession returned no session token");
|
|
1612
|
+
}
|
|
1613
|
+
return data;
|
|
1614
|
+
}
|
|
1615
|
+
class GoogleMaps extends MapSource {
|
|
1616
|
+
constructor(config) {
|
|
1617
|
+
super();
|
|
1618
|
+
__publicField(this, "apiKey");
|
|
1619
|
+
__publicField(this, "mapType");
|
|
1620
|
+
__publicField(this, "language");
|
|
1621
|
+
__publicField(this, "region");
|
|
1622
|
+
__publicField(this, "tileApiUrl");
|
|
1623
|
+
__publicField(this, "includeApiKey");
|
|
1624
|
+
__publicField(this, "attribution");
|
|
1625
|
+
__publicField(this, "tileSize");
|
|
1626
|
+
__publicField(this, "headers");
|
|
1627
|
+
__publicField(this, "fetchImpl");
|
|
1628
|
+
__publicField(this, "sessionToken");
|
|
1629
|
+
/** Unix epoch seconds when the session expires. */
|
|
1630
|
+
__publicField(this, "sessionExpiry");
|
|
1631
|
+
this.includeApiKey = config.includeApiKey !== false;
|
|
1632
|
+
if (this.includeApiKey && !config.apiKey) {
|
|
1633
|
+
throw new Error("Google Maps provider requires an apiKey");
|
|
1634
|
+
}
|
|
1635
|
+
this.apiKey = config.apiKey || "";
|
|
1636
|
+
this.mapType = config.mapType || DEFAULT_GOOGLE_MAP_TYPE;
|
|
1637
|
+
this.language = config.language || "en-US";
|
|
1638
|
+
this.region = config.region || "US";
|
|
1639
|
+
this.tileApiUrl = (config.tileApiUrl || DEFAULT_GOOGLE_TILE_API_URL).replace(/\/$/, "");
|
|
1640
|
+
this.attribution = config.attribution || "© Google Maps / Map Tiles API";
|
|
1641
|
+
this.tileSize = config.tileSize || 256;
|
|
1642
|
+
this.headers = config.headers;
|
|
1643
|
+
this.fetchImpl = config.fetchImpl || fetch;
|
|
1644
|
+
this.sessionToken = config.sessionToken;
|
|
1645
|
+
}
|
|
1646
|
+
isSessionValid(skewSeconds = 60) {
|
|
1647
|
+
if (!this.sessionToken) {
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
if (this.sessionExpiry == null) {
|
|
1651
|
+
return true;
|
|
1652
|
+
}
|
|
1653
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1654
|
+
return this.sessionExpiry - skewSeconds > now;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Ensure a valid Map Tiles session exists (create or refresh).
|
|
1658
|
+
*/
|
|
1659
|
+
async ensureSession() {
|
|
1660
|
+
if (this.isSessionValid()) {
|
|
1661
|
+
return this.sessionToken;
|
|
1662
|
+
}
|
|
1663
|
+
const data = await createGoogleSession({
|
|
1664
|
+
apiKey: this.apiKey,
|
|
1665
|
+
mapType: this.mapType,
|
|
1666
|
+
language: this.language,
|
|
1667
|
+
region: this.region,
|
|
1668
|
+
tileApiUrl: this.tileApiUrl,
|
|
1669
|
+
includeApiKey: this.includeApiKey,
|
|
1670
|
+
headers: this.headers,
|
|
1671
|
+
fetchImpl: this.fetchImpl
|
|
1672
|
+
});
|
|
1673
|
+
this.sessionToken = data.session;
|
|
1674
|
+
const expiry = Number(data.expiry);
|
|
1675
|
+
this.sessionExpiry = Number.isFinite(expiry) ? expiry : void 0;
|
|
1676
|
+
if (typeof data.tileWidth === "number") {
|
|
1677
|
+
this.tileSize = data.tileWidth;
|
|
1678
|
+
}
|
|
1679
|
+
return this.sessionToken;
|
|
1680
|
+
}
|
|
1681
|
+
async getImage(polygon2, bands, expression, zoomLevel, requiresSquare = false, stitch = true) {
|
|
1682
|
+
await this.ensureSession();
|
|
1683
|
+
return super.getImage(
|
|
1684
|
+
polygon2,
|
|
1685
|
+
bands,
|
|
1686
|
+
expression,
|
|
1687
|
+
zoomLevel,
|
|
1688
|
+
requiresSquare,
|
|
1689
|
+
stitch
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
getTileUrlFromTileCoords(tileCoords, instance) {
|
|
1693
|
+
if (!instance.sessionToken) {
|
|
1694
|
+
throw new Error(
|
|
1695
|
+
"Google Maps session is not ready. Call ensureSession() or getImage() first."
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
const [x, y, z] = tileCoords;
|
|
1699
|
+
return buildGoogleTileUrl(
|
|
1700
|
+
z,
|
|
1701
|
+
x,
|
|
1702
|
+
y,
|
|
1703
|
+
instance.sessionToken,
|
|
1704
|
+
instance.apiKey,
|
|
1705
|
+
instance.tileApiUrl,
|
|
1706
|
+
instance.includeApiKey
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1557
1710
|
class BaseModel {
|
|
1558
1711
|
constructor(model_id, providerParams, modelParams) {
|
|
1559
1712
|
__publicField(this, "providerParams");
|
|
@@ -1636,6 +1789,20 @@ class BaseModel {
|
|
|
1636
1789
|
headers: this.providerParams.headers
|
|
1637
1790
|
});
|
|
1638
1791
|
break;
|
|
1792
|
+
case "google":
|
|
1793
|
+
this.dataProvider = new GoogleMaps({
|
|
1794
|
+
apiKey: this.providerParams.apiKey,
|
|
1795
|
+
mapType: this.providerParams.mapType,
|
|
1796
|
+
language: this.providerParams.language,
|
|
1797
|
+
region: this.providerParams.region,
|
|
1798
|
+
sessionToken: this.providerParams.sessionToken,
|
|
1799
|
+
tileApiUrl: this.providerParams.tileApiUrl,
|
|
1800
|
+
includeApiKey: this.providerParams.includeApiKey,
|
|
1801
|
+
attribution: this.providerParams.attribution,
|
|
1802
|
+
tileSize: this.providerParams.tileSize,
|
|
1803
|
+
headers: this.providerParams.headers
|
|
1804
|
+
});
|
|
1805
|
+
break;
|
|
1639
1806
|
case "sentinel":
|
|
1640
1807
|
throw new Error("Sentinel provider not implemented yet");
|
|
1641
1808
|
default:
|
|
@@ -3872,7 +4039,147 @@ const BuildingSegmentationFactory = {
|
|
|
3872
4039
|
return create(params, resolvedId, modelParams);
|
|
3873
4040
|
}
|
|
3874
4041
|
};
|
|
4042
|
+
const GPS_GALLERY_URL = "https://huggingface.co/Xenova/geoclip-large-patch14/resolve/main/gps_gallery/coordinates_100K.json";
|
|
4043
|
+
const DEFAULT_TOP_K = 5;
|
|
4044
|
+
const COORDINATE_BATCH_SIZE = 512;
|
|
4045
|
+
const LOGIT_SCALE = Math.exp(3.681034803390503);
|
|
4046
|
+
const _ImageGeolocation = class _ImageGeolocation extends BaseModel {
|
|
4047
|
+
constructor(modelId, providerParams, modelParams) {
|
|
4048
|
+
super(modelId, providerParams, modelParams);
|
|
4049
|
+
__publicField(this, "visionModel");
|
|
4050
|
+
__publicField(this, "locationModel");
|
|
4051
|
+
__publicField(this, "processor");
|
|
4052
|
+
__publicField(this, "gpsGallery");
|
|
4053
|
+
}
|
|
4054
|
+
static async getInstance(modelId, providerParams, modelParams) {
|
|
4055
|
+
if (!_ImageGeolocation.instance || parametersChanged(
|
|
4056
|
+
_ImageGeolocation.instance,
|
|
4057
|
+
modelId,
|
|
4058
|
+
providerParams,
|
|
4059
|
+
modelParams
|
|
4060
|
+
)) {
|
|
4061
|
+
_ImageGeolocation.instance = new _ImageGeolocation(
|
|
4062
|
+
modelId,
|
|
4063
|
+
providerParams,
|
|
4064
|
+
modelParams
|
|
4065
|
+
);
|
|
4066
|
+
await _ImageGeolocation.instance.initialize();
|
|
4067
|
+
}
|
|
4068
|
+
return { instance: _ImageGeolocation.instance };
|
|
4069
|
+
}
|
|
4070
|
+
async initializeModel() {
|
|
4071
|
+
var _a;
|
|
4072
|
+
this.visionModel = await AutoModel.from_pretrained(this.model_id, {
|
|
4073
|
+
...this.modelParams,
|
|
4074
|
+
model_file_name: "vision_model"
|
|
4075
|
+
});
|
|
4076
|
+
this.locationModel = await AutoModel.from_pretrained(this.model_id, {
|
|
4077
|
+
model_file_name: "location_model",
|
|
4078
|
+
dtype: "fp32",
|
|
4079
|
+
device: (_a = this.modelParams) == null ? void 0 : _a.device
|
|
4080
|
+
});
|
|
4081
|
+
this.processor = await AutoProcessor.from_pretrained(
|
|
4082
|
+
"openai/clip-vit-large-patch14"
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
async getGpsGallery() {
|
|
4086
|
+
if (!this.gpsGallery) {
|
|
4087
|
+
const response = await fetch(GPS_GALLERY_URL);
|
|
4088
|
+
if (!response.ok)
|
|
4089
|
+
throw new Error(
|
|
4090
|
+
`Unable to load GeoCLIP's GPS gallery (${response.status})`
|
|
4091
|
+
);
|
|
4092
|
+
this.gpsGallery = await response.json();
|
|
4093
|
+
}
|
|
4094
|
+
return this.gpsGallery;
|
|
4095
|
+
}
|
|
4096
|
+
async inference(params) {
|
|
4097
|
+
var _a, _b, _c;
|
|
4098
|
+
const { polygon: polygon2, image, topK = DEFAULT_TOP_K } = params.inputs;
|
|
4099
|
+
if (!(image instanceof Blob) && (!polygon2 || !polygon2.geometry || polygon2.geometry.type !== "Polygon")) {
|
|
4100
|
+
throw new Error(
|
|
4101
|
+
"Provide an uploaded image or a valid GeoJSON Polygon feature for image geolocation"
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
if (!this.initialized) await this.initialize();
|
|
4105
|
+
if (!this.visionModel || !this.locationModel || !this.processor)
|
|
4106
|
+
throw new Error("GeoCLIP model was not initialized");
|
|
4107
|
+
const geoRawImage = image instanceof Blob ? void 0 : await this.polygonToImage(
|
|
4108
|
+
polygon2,
|
|
4109
|
+
(_a = params.mapSourceParams) == null ? void 0 : _a.zoomLevel,
|
|
4110
|
+
(_b = params.mapSourceParams) == null ? void 0 : _b.bands,
|
|
4111
|
+
(_c = params.mapSourceParams) == null ? void 0 : _c.expression
|
|
4112
|
+
);
|
|
4113
|
+
const inputImage = image instanceof Blob ? await RawImage.fromBlob(image) : geoRawImage;
|
|
4114
|
+
const visionOutput = await this.visionModel(
|
|
4115
|
+
await this.processor(inputImage)
|
|
4116
|
+
);
|
|
4117
|
+
const imageEmbeds = visionOutput.image_embeds;
|
|
4118
|
+
if (!imageEmbeds)
|
|
4119
|
+
throw new Error("GeoCLIP vision model did not return image embeddings");
|
|
4120
|
+
const normalizedImage = imageEmbeds.normalize().data;
|
|
4121
|
+
const gallery = await this.getGpsGallery();
|
|
4122
|
+
const candidates = [];
|
|
4123
|
+
for (let offset = 0; offset < gallery.length; offset += COORDINATE_BATCH_SIZE) {
|
|
4124
|
+
const coordinates = gallery.slice(offset, offset + COORDINATE_BATCH_SIZE);
|
|
4125
|
+
const locationOutput = await this.locationModel({
|
|
4126
|
+
location: new Tensor("float32", coordinates.flat(), [
|
|
4127
|
+
coordinates.length,
|
|
4128
|
+
2
|
|
4129
|
+
])
|
|
4130
|
+
});
|
|
4131
|
+
const locationEmbeds = locationOutput.location_embeds;
|
|
4132
|
+
if (!locationEmbeds)
|
|
4133
|
+
throw new Error(
|
|
4134
|
+
"GeoCLIP location model did not return location embeddings"
|
|
4135
|
+
);
|
|
4136
|
+
const normalizedLocations = locationEmbeds.normalize().data;
|
|
4137
|
+
for (let coordinateIndex = 0; coordinateIndex < coordinates.length; coordinateIndex++) {
|
|
4138
|
+
let dotProduct = 0;
|
|
4139
|
+
for (let dimension = 0; dimension < normalizedImage.length; dimension++) {
|
|
4140
|
+
dotProduct += normalizedImage[dimension] * normalizedLocations[coordinateIndex * normalizedImage.length + dimension];
|
|
4141
|
+
}
|
|
4142
|
+
candidates.push({
|
|
4143
|
+
index: offset + coordinateIndex,
|
|
4144
|
+
gps: coordinates[coordinateIndex],
|
|
4145
|
+
score: LOGIT_SCALE * dotProduct
|
|
4146
|
+
});
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
const maxScore = candidates.reduce(
|
|
4150
|
+
(maximum, candidate) => Math.max(maximum, candidate.score),
|
|
4151
|
+
Number.NEGATIVE_INFINITY
|
|
4152
|
+
);
|
|
4153
|
+
const scoreTotal = candidates.reduce(
|
|
4154
|
+
(total, candidate) => total + Math.exp(candidate.score - maxScore),
|
|
4155
|
+
0
|
|
4156
|
+
);
|
|
4157
|
+
const predictions = candidates.map((candidate) => ({
|
|
4158
|
+
...candidate,
|
|
4159
|
+
score: Math.exp(candidate.score - maxScore) / scoreTotal
|
|
4160
|
+
})).sort((a, b) => b.score - a.score).slice(0, Math.max(1, Math.floor(Number(topK) || DEFAULT_TOP_K)));
|
|
4161
|
+
return {
|
|
4162
|
+
predictions,
|
|
4163
|
+
geoRawImage,
|
|
4164
|
+
metadata: { modelId: this.model_id, gallerySize: gallery.length }
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
};
|
|
4168
|
+
__publicField(_ImageGeolocation, "instance", null);
|
|
4169
|
+
let ImageGeolocation = _ImageGeolocation;
|
|
3875
4170
|
const modelRegistry = [
|
|
4171
|
+
{
|
|
4172
|
+
task: "image-geolocation",
|
|
4173
|
+
library: "@huggingface/transformers",
|
|
4174
|
+
description: "Estimates where an image was taken by matching visual features against GeoCLIP's worldwide GPS gallery.",
|
|
4175
|
+
examples: [
|
|
4176
|
+
"Estimate where this landscape image was taken.",
|
|
4177
|
+
"Find likely locations for this aerial image.",
|
|
4178
|
+
"Geolocate this landmark photograph."
|
|
4179
|
+
],
|
|
4180
|
+
ioConfig: {},
|
|
4181
|
+
geobase_ai_pipeline: (params, modelId = "Xenova/geoclip-large-patch14", modelParams = { dtype: "q4" }) => ImageGeolocation.getInstance(modelId, params, modelParams)
|
|
4182
|
+
},
|
|
3876
4183
|
{
|
|
3877
4184
|
task: "zero-shot-object-detection",
|
|
3878
4185
|
library: "@huggingface/transformers",
|
package/index.d.ts
CHANGED
|
@@ -247,10 +247,63 @@ declare class Oam extends MapSource {
|
|
|
247
247
|
number
|
|
248
248
|
], instance: Oam): string;
|
|
249
249
|
}
|
|
250
|
+
export type GoogleMapType = "satellite" | "roadmap" | "terrain";
|
|
251
|
+
export interface GoogleConfig {
|
|
252
|
+
/** Google Maps Platform API key with Map Tiles API enabled. */
|
|
253
|
+
apiKey: string;
|
|
254
|
+
/** 2D map type for the session (default `satellite`). */
|
|
255
|
+
mapType?: GoogleMapType;
|
|
256
|
+
language?: string;
|
|
257
|
+
region?: string;
|
|
258
|
+
/**
|
|
259
|
+
* Optional pre-created session token. When omitted, the provider creates
|
|
260
|
+
* one via `POST /v1/createSession` and refreshes near expiry.
|
|
261
|
+
*/
|
|
262
|
+
sessionToken?: string;
|
|
263
|
+
/** Session/tile API root (default https://tile.googleapis.com). */
|
|
264
|
+
tileApiUrl?: string;
|
|
265
|
+
/**
|
|
266
|
+
* When false, omit `key=` from createSession/tile URLs (use with a same-origin
|
|
267
|
+
* proxy that injects the server-side API key). Default true.
|
|
268
|
+
*/
|
|
269
|
+
includeApiKey?: boolean;
|
|
270
|
+
attribution?: string;
|
|
271
|
+
tileSize?: number;
|
|
272
|
+
headers?: Record<string, string>;
|
|
273
|
+
/** Injected for tests. */
|
|
274
|
+
fetchImpl?: typeof fetch;
|
|
275
|
+
}
|
|
276
|
+
declare class GoogleMaps extends MapSource {
|
|
277
|
+
apiKey: string;
|
|
278
|
+
mapType: GoogleMapType;
|
|
279
|
+
language: string;
|
|
280
|
+
region: string;
|
|
281
|
+
tileApiUrl: string;
|
|
282
|
+
includeApiKey: boolean;
|
|
283
|
+
attribution: string;
|
|
284
|
+
tileSize: number;
|
|
285
|
+
headers?: Record<string, string>;
|
|
286
|
+
fetchImpl: typeof fetch;
|
|
287
|
+
sessionToken?: string;
|
|
288
|
+
/** Unix epoch seconds when the session expires. */
|
|
289
|
+
sessionExpiry?: number;
|
|
290
|
+
constructor(config: GoogleConfig);
|
|
291
|
+
private isSessionValid;
|
|
292
|
+
/**
|
|
293
|
+
* Ensure a valid Map Tiles session exists (create or refresh).
|
|
294
|
+
*/
|
|
295
|
+
ensureSession(): Promise<string>;
|
|
296
|
+
getImage(polygon: any, bands?: number[], expression?: string, zoomLevel?: number, requiresSquare?: boolean, stitch?: boolean): Promise<GeoRawImage | GeoRawImage[][]>;
|
|
297
|
+
protected getTileUrlFromTileCoords(tileCoords: [
|
|
298
|
+
number,
|
|
299
|
+
number,
|
|
300
|
+
number
|
|
301
|
+
], instance: GoogleMaps): string;
|
|
302
|
+
}
|
|
250
303
|
declare abstract class BaseModel {
|
|
251
304
|
protected static instance: BaseModel | null;
|
|
252
305
|
protected providerParams: ProviderParams;
|
|
253
|
-
protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | Oam | undefined;
|
|
306
|
+
protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | Oam | GoogleMaps | undefined;
|
|
254
307
|
protected model_id: string;
|
|
255
308
|
protected initialized: boolean;
|
|
256
309
|
protected modelParams?: PretrainedModelOptions;
|
|
@@ -571,6 +624,20 @@ declare class ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel
|
|
|
571
624
|
instance: ChangeStarBuildingSegmentation;
|
|
572
625
|
}>;
|
|
573
626
|
}
|
|
627
|
+
declare class ImageGeolocation extends BaseModel {
|
|
628
|
+
protected static instance: ImageGeolocation | null;
|
|
629
|
+
private visionModel;
|
|
630
|
+
private locationModel;
|
|
631
|
+
private processor;
|
|
632
|
+
private gpsGallery;
|
|
633
|
+
private constructor();
|
|
634
|
+
static getInstance(modelId: string, providerParams: ProviderParams, modelParams?: PretrainedModelOptions): Promise<{
|
|
635
|
+
instance: ImageGeolocation;
|
|
636
|
+
}>;
|
|
637
|
+
protected initializeModel(): Promise<void>;
|
|
638
|
+
private getGpsGallery;
|
|
639
|
+
inference(params: InferenceParams): Promise<ImageGeolocationResults>;
|
|
640
|
+
}
|
|
574
641
|
export type MapboxParams = {
|
|
575
642
|
provider: "mapbox";
|
|
576
643
|
apiKey: string;
|
|
@@ -635,8 +702,29 @@ export type OamParams = {
|
|
|
635
702
|
tileSize?: number;
|
|
636
703
|
headers?: Record<string, string>;
|
|
637
704
|
};
|
|
705
|
+
export type GoogleMapsParams = {
|
|
706
|
+
provider: "google";
|
|
707
|
+
/** Google Maps Platform API key with Map Tiles API enabled. */
|
|
708
|
+
apiKey: string;
|
|
709
|
+
/** 2D map type for the session (default satellite). */
|
|
710
|
+
mapType?: "satellite" | "roadmap" | "terrain";
|
|
711
|
+
language?: string;
|
|
712
|
+
region?: string;
|
|
713
|
+
/** Optional pre-created session token (skips createSession until expiry). */
|
|
714
|
+
sessionToken?: string;
|
|
715
|
+
/** Override API root (e.g. same-origin proxy). Default https://tile.googleapis.com */
|
|
716
|
+
tileApiUrl?: string;
|
|
717
|
+
/**
|
|
718
|
+
* When false, omit `key=` from requests (proxy injects the server key).
|
|
719
|
+
* Default true.
|
|
720
|
+
*/
|
|
721
|
+
includeApiKey?: boolean;
|
|
722
|
+
attribution?: string;
|
|
723
|
+
tileSize?: number;
|
|
724
|
+
headers?: Record<string, string>;
|
|
725
|
+
};
|
|
638
726
|
export interface InferenceInputs {
|
|
639
|
-
polygon
|
|
727
|
+
polygon?: GeoJSON.Feature;
|
|
640
728
|
classLabel?: string;
|
|
641
729
|
[key: string]: unknown;
|
|
642
730
|
}
|
|
@@ -653,10 +741,10 @@ export interface InferenceParams {
|
|
|
653
741
|
postProcessingParams?: PostProcessingParams;
|
|
654
742
|
mapSourceParams?: MapSourceParams;
|
|
655
743
|
}
|
|
656
|
-
export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams | OamParams;
|
|
657
|
-
export type HuggingFaceModelTask = "mask-generation" | "zero-shot-object-detection" | "zero-shot-image-classification" | "object-detection" | "oriented-object-detection" | "image-feature-extraction";
|
|
744
|
+
export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams | OamParams | GoogleMapsParams;
|
|
745
|
+
export type HuggingFaceModelTask = "mask-generation" | "zero-shot-object-detection" | "zero-shot-image-classification" | "object-detection" | "oriented-object-detection" | "image-feature-extraction" | "image-geolocation";
|
|
658
746
|
export type GeobaseAiModelTask = "damage-assessment" | "vegetation-classification" | "land-cover-classification" | "land-use-classification" | "land-cover-change-detection" | "land-use-change-detection" | "solar-panel-detection" | "ship-detection" | "car-detection" | "wetland-segmentation" | "building-detection" | "oil-storage-tank-detection" | "building-footprint-segmentation";
|
|
659
|
-
export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | ChangeStarBuildingSegmentation | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction;
|
|
747
|
+
export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | ChangeStarBuildingSegmentation | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction | ImageGeolocation;
|
|
660
748
|
export type ModelConfig = {
|
|
661
749
|
task: HuggingFaceModelTask | GeobaseAiModelTask;
|
|
662
750
|
library: string;
|
|
@@ -694,6 +782,21 @@ export interface ImageFeatureExtractionResults {
|
|
|
694
782
|
modelId: string;
|
|
695
783
|
};
|
|
696
784
|
}
|
|
785
|
+
export interface ImageGeolocationResults {
|
|
786
|
+
predictions: Array<{
|
|
787
|
+
index: number;
|
|
788
|
+
gps: [
|
|
789
|
+
number,
|
|
790
|
+
number
|
|
791
|
+
];
|
|
792
|
+
score: number;
|
|
793
|
+
}>;
|
|
794
|
+
geoRawImage?: GeoRawImage;
|
|
795
|
+
metadata: {
|
|
796
|
+
modelId: string;
|
|
797
|
+
gallerySize: number;
|
|
798
|
+
};
|
|
799
|
+
}
|
|
697
800
|
export interface ChainInstance {
|
|
698
801
|
inference: (inputs: InferenceParams) => Promise<ObjectDetectionResults | SegmentationResults>;
|
|
699
802
|
}
|