geoai 1.0.6 → 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 CHANGED
@@ -7,6 +7,26 @@ 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
+
20
+ ## [1.0.7] - 2026-07-17
21
+
22
+ ### Added
23
+ - OpenAerialMap (OAM) map provider — HOT Imagery STAC + TiTiler (`provider: "oam"`)
24
+ - `examples/07-oam-quickstart` — Rome orthophoto draw-to-detect demo
25
+ - OAM option in `live-examples-nextjs` (flies to Rome; mosaic tiles)
26
+
27
+ ### Fixed
28
+ - Docs Vercel prebuild: ship `remark` deps with docs and run `generate-llms-txt` from `docs/scripts/` so module resolution works when the Vercel root is `docs/`
29
+
10
30
  ## [1.0.6] - 2026-07-16
11
31
 
12
32
  ### Added
package/README.md CHANGED
@@ -1,226 +1,224 @@
1
1
  <h1 align="center">
2
- GeoAI <img src="https://cdn-icons-png.flaticon.com/256/5968/5968292.png" alt="JavaScript logo" height="24" style="vertical-align:middle" />
2
+ GeoAI.js
3
3
  </h1>
4
4
 
5
- <p align="center" style="margin-top:8px;">A lightweight JavaScript library for running Geo AI models in frontend applications.</p>
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 &amp; aerial imagery — without a GPU backend.
8
+ </p>
6
9
 
7
- <p align="center" style="margin:12px 0;">
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" style="margin-left:6px"><img alt="main" src="https://github.com/decision-labs/geoai.js/actions/workflows/main.yml/badge.svg"></a>
10
- <a href="https://www.npmjs.com/package/geoai" style="margin-left:6px"><img alt="npm downloads" src="https://img.shields.io/npm/dm/geoai.svg"></a>
11
- <a href="https://docs.geobase.app/geoai" style="margin-left:6px"><img alt="docs" src="https://img.shields.io/badge/docs-online-blue"></a>
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 Demo" width="80%" style="border-radius: 16px;">
27
+ <img src="_docs4devs/geoai.gif" alt="GeoAI.js DINOv3 feature extraction demo" width="85%">
16
28
  </div>
17
29
 
18
- <p align="center" style="font-style:italic; margin:2px;">
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
- ### Quick install
32
+ ---
23
33
 
24
- ### NPM Package
34
+ ## Why GeoAI.js?
25
35
 
26
- [![npm version](https://img.shields.io/npm/v/geoai.svg)](https://www.npmjs.com/package/geoai)
27
- [![npm downloads](https://img.shields.io/npm/dm/geoai.svg)](https://www.npmjs.com/package/geoai)
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
- 📦 **Package Links:**
34
- - [npm package](https://www.npmjs.com/package/geoai)
35
- - [jsDelivr CDN](https://www.jsdelivr.com/package/npm/geoai)
36
-
37
- ### CDN (Browser)
49
+ ```javascript
50
+ import { geoai } from "geoai";
38
51
 
39
- You can also use GeoAI directly in the browser via CDN:
52
+ const pipeline = await geoai.pipeline([{ task: "building-detection" }], {
53
+ provider: "esri", // no API key
54
+ });
40
55
 
41
- #### Unpkg CDN
42
- ```html
43
- <script src="https://unpkg.com/geoai@1.0.6/geoai.js"></script>
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
- #### jsDelivr CDN
63
+ CDN:
64
+
47
65
  ```html
48
- <script src="https://cdn.jsdelivr.net/npm/geoai@1.0.6/geoai.min.js"></script>
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
- When using CDN, the library is available globally as `geoai`.
71
+ ---
52
72
 
53
- Get Started: Follow the [Quickstart Guide](https://docs.geobase.app/geoai) or clone the quick start example.
73
+ ## Use cases
54
74
 
55
- ```bash
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
- ## Usage
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
- ### Core Library (Node.js and Browser)
83
+ Draw an AOI on a map, run a model, store or style the GeoJSON — all in one frontend session.
66
84
 
67
- #### NPM Installation
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
- // Run inference
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
- #### CDN Usage
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
- const result = await pipeline.inference({
95
- inputs: {
96
- polygon: geoJsonFeature,
97
- },
98
- mapSourceParams: {
99
- zoomLevel: 18,
100
- },
101
- });
102
- ```
105
+ ---
106
+
107
+ ## Integrations
108
+
109
+ Persist detections, sync in real time, and serve results as vector tiles.
110
+
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>
103
120
 
104
- ### React Hooks
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) |
128
+
129
+ Quick links:
130
+
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)
134
+
135
+ ---
136
+
137
+ ## Map providers
138
+
139
+ Point the pipeline at the imagery you already use:
140
+
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 |
105
150
 
106
151
  ```javascript
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
- );
152
+ // Free public imagery
153
+ { provider: "esri" }
154
+
155
+ // Community aerial (OAM)
156
+ { provider: "oam" /* , itemId, mosaic: true */ }
157
+
158
+ // Your COG on Geobase
159
+ {
160
+ provider: "geobase",
161
+ projectRef: "...",
162
+ apikey: "...",
163
+ cogImagery: "https://path-to-your-cog.tif",
130
164
  }
165
+
166
+ // Google Map Tiles (enable Map Tiles API; avoid HTTP-referrer–only keys)
167
+ { provider: "google", apiKey: process.env.GOOGLE_MAPS_API_KEY }
131
168
  ```
132
169
 
133
- ## Features
170
+ Docs: [Map providers](https://docs.geobase.app/geoai/map-providers)
134
171
 
135
- - **Multiple AI Tasks**: Object detection, segmentation, classification, and more
136
- - **Map Provider Support**: Geobase, Mapbox, ESRI, TMS, and Google Maps
137
- - **React Integration**: Hooks for easy React integration
138
- - **TypeScript Support**: Full TypeScript definitions
139
- - **Web Worker Support**: Run AI models in background threads
140
- - **Optimized Performance**: Efficient model loading and inference
141
- - **CDN Ready**: Available via npm and popular CDNs (unpkg, jsDelivr)
172
+ ---
142
173
 
143
- ## Supported Tasks
174
+ ## Supported tasks
144
175
 
145
- - Object Detection
146
- - Building Detection
147
- - Car Detection
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
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)
158
177
 
159
- For more see the [supported tasks](https://docs.geobase.app/geoai/supported-tasks)
178
+ → [Full task reference](https://docs.geobase.app/geoai/supported-tasks)
160
179
 
161
- ## Map Providers
180
+ ---
162
181
 
163
- GeoAI.js supports multiple map tile providers:
182
+ ## Workers & React
164
183
 
165
- ### TMS (Tile Map Service)
166
- TMS is a tile-based map specification that uses a bottom-left origin coordinate system. Perfect for custom tile servers and OpenAerialMap.
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).
167
185
 
168
- ```javascript
169
- // Option 1: Using URL template with placeholders (recommended)
170
- const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
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
- });
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)
177
189
 
178
- // Option 2: Using base URL (legacy format, still supported)
179
- const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
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
- ```
190
+ ---
188
191
 
189
- ### ESRI
190
- ESRI World Imagery - no API key required.
192
+ ## Quickstart clone
191
193
 
192
- ```javascript
193
- const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
194
- provider: "esri",
195
- });
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
196
199
  ```
197
200
 
198
- ### Mapbox
199
- Requires a Mapbox API key.
201
+ ---
200
202
 
201
- ```javascript
202
- const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
203
- provider: "mapbox",
204
- apiKey: "your-mapbox-api-key",
205
- style: "mapbox://styles/mapbox/satellite-v9",
206
- });
207
- ```
203
+ ## Agents & LLMs
208
204
 
209
- ### Geobase
210
- Requires Geobase project credentials.
205
+ Teach coding agents how to integrate GeoAI.js:
211
206
 
212
- ```javascript
213
- const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
214
- provider: "geobase",
215
- projectRef: "your-project-ref",
216
- apikey: "your-api-key",
217
- cogImagery: "https://path-to-your-cog.tif",
218
- });
207
+ ```bash
208
+ npx skills add decision-labs/geoai.js --skill geoai -y
209
+ # or globally
210
+ npx skills add decision-labs/geoai.js --skill geoai -g -y
219
211
  ```
220
212
 
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`)
216
+
217
+ ---
218
+
221
219
  ## Talk
222
220
 
223
- **[Bringing Earth Observation AI to the Browser with WebGPU](talks/lightning-talk-webgpu-geoai.md)** — lightning talk at Big Data from Space 2025 (Decision Labs).
221
+ **[Bringing Earth Observation AI to the Browser with WebGPU](talks/lightning-talk-webgpu-geoai.md)** — Big Data from Space 2025 (Decision Labs).
224
222
 
225
223
  <div align="center">
226
224
  <a href="https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf">
@@ -229,23 +227,24 @@ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
229
227
  </div>
230
228
 
231
229
  - [Slides (PDF)](https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf)
232
- - [Slide source (Marp)](talks/lightning-talk-webgpu-geoai.md)
230
+
231
+ ---
233
232
 
234
233
  ## Links
235
234
 
236
- - **Documentation**: [docs.geobase.app/geoai](https://docs.geobase.app/geoai) - Comprehensive documentation, examples, and API reference
237
- - **Live Examples**: [docs.geobase.app/geoai-live](https://docs.geobase.app/geoai-live) - Interactive examples and demos
238
- - **Roadmap**: [_docs4devs/ROADMAP.md](_docs4devs/ROADMAP.md) - Planned docs, quantization pipeline, and related backlog
239
- - **Community**: [GitHub Discussions](https://github.com/decision-labs/geoai.js/discussions) - Ask questions, share ideas, and connect with other developers
240
- - **Code**: [GitHub Repository](https://github.com/decision-labs/geoai.js) - Source code and contributions
241
- - **Issues**: [GitHub Issues](https://github.com/decision-labs/geoai.js/issues) - Report bugs and request features
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)
242
241
 
243
242
  ## Contributing
244
243
 
245
- We welcome contributions! Please see our [contributing guide](CONTRIBUTING.md) for details.
244
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
246
245
 
247
246
  ## License
248
247
 
249
- MIT License - see [LICENSE.md](LICENSE.md) for details.
248
+ MIT — [LICENSE.md](LICENSE.md)
250
249
 
251
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;
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
  }
@@ -425,6 +425,16 @@ const parametersChanged = (instance, model_id, providerParams, modelParams) => {
425
425
  return true;
426
426
  }
427
427
  break;
428
+ case "oam":
429
+ if (((_m = instance.providerParams) == null ? void 0 : _m.stacUrl) !== (providerParams == null ? void 0 : providerParams.stacUrl) || ((_n = instance.providerParams) == null ? void 0 : _n.rasterUrl) !== (providerParams == null ? void 0 : providerParams.rasterUrl) || ((_o = instance.providerParams) == null ? void 0 : _o.collection) !== (providerParams == null ? void 0 : providerParams.collection) || ((_p = instance.providerParams) == null ? void 0 : _p.asset) !== (providerParams == null ? void 0 : providerParams.asset) || ((_q = instance.providerParams) == null ? void 0 : _q.itemId) !== (providerParams == null ? void 0 : providerParams.itemId) || ((_r = instance.providerParams) == null ? void 0 : _r.mosaic) !== (providerParams == null ? void 0 : providerParams.mosaic)) {
430
+ return true;
431
+ }
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;
428
438
  }
429
439
  if (modelParams) {
430
440
  const instanceModelParams = instance.modelParams || {};
@@ -1380,6 +1390,323 @@ class Wms extends MapSource {
1380
1390
  );
1381
1391
  }
1382
1392
  }
1393
+ const DEFAULT_OAM_STAC_URL = "https://api.imagery.hotosm.org/stac";
1394
+ const DEFAULT_OAM_RASTER_URL = "https://api.imagery.hotosm.org/raster";
1395
+ const DEFAULT_OAM_COLLECTION = "openaerialmap";
1396
+ const DEFAULT_OAM_ASSET = "visual";
1397
+ function buildOamMosaicTileUrlTemplate(rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1398
+ const base = rasterUrl.replace(/\/$/, "");
1399
+ return `${base}/collections/${encodeURIComponent(collection)}/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=${encodeURIComponent(asset)}`;
1400
+ }
1401
+ function buildOamItemTileUrlTemplate(itemId, rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1402
+ const base = rasterUrl.replace(/\/$/, "");
1403
+ return `${base}/collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(itemId)}/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=${encodeURIComponent(asset)}`;
1404
+ }
1405
+ function pickBestOamItem(features) {
1406
+ if (!features.length) return void 0;
1407
+ return features.slice().sort((a, b) => {
1408
+ var _a, _b, _c, _d, _e, _f;
1409
+ const gsdA = typeof ((_a = a.properties) == null ? void 0 : _a.gsd) === "number" ? a.properties.gsd : Number.POSITIVE_INFINITY;
1410
+ const gsdB = typeof ((_b = b.properties) == null ? void 0 : _b.gsd) === "number" ? b.properties.gsd : Number.POSITIVE_INFINITY;
1411
+ if (gsdA !== gsdB) return gsdA - gsdB;
1412
+ const dateA = Date.parse(
1413
+ String(((_c = a.properties) == null ? void 0 : _c.created) || ((_d = a.properties) == null ? void 0 : _d.datetime) || 0)
1414
+ );
1415
+ const dateB = Date.parse(
1416
+ String(((_e = b.properties) == null ? void 0 : _e.created) || ((_f = b.properties) == null ? void 0 : _f.datetime) || 0)
1417
+ );
1418
+ return dateB - dateA;
1419
+ })[0];
1420
+ }
1421
+ async function searchOamItems(bbox2, options = {}) {
1422
+ const stacUrl = (options.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1423
+ const collection = options.collection || DEFAULT_OAM_COLLECTION;
1424
+ const limit = options.limit ?? 10;
1425
+ const fetchImpl = options.fetchImpl || fetch;
1426
+ const response = await fetchImpl(`${stacUrl}/search`, {
1427
+ method: "POST",
1428
+ headers: {
1429
+ "Content-Type": "application/json",
1430
+ Accept: "application/geo+json",
1431
+ ...options.headers || {}
1432
+ },
1433
+ body: JSON.stringify({
1434
+ collections: [collection],
1435
+ bbox: bbox2,
1436
+ limit
1437
+ })
1438
+ });
1439
+ if (!response.ok) {
1440
+ const body = await response.text().catch(() => "");
1441
+ throw new Error(
1442
+ `OAM STAC search failed (${response.status}): ${body || response.statusText}`
1443
+ );
1444
+ }
1445
+ const data = await response.json();
1446
+ return data.features || [];
1447
+ }
1448
+ class Oam extends MapSource {
1449
+ constructor(config = {}) {
1450
+ super();
1451
+ __publicField(this, "stacUrl");
1452
+ __publicField(this, "rasterUrl");
1453
+ __publicField(this, "collection");
1454
+ __publicField(this, "asset");
1455
+ __publicField(this, "itemId");
1456
+ __publicField(this, "mosaic");
1457
+ __publicField(this, "attribution");
1458
+ __publicField(this, "tileSize");
1459
+ __publicField(this, "headers");
1460
+ /** Resolved XYZ template with `{z}/{x}/{y}` placeholders. */
1461
+ __publicField(this, "tileUrlTemplate");
1462
+ __publicField(this, "resolvedForBboxKey");
1463
+ this.stacUrl = (config.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1464
+ this.rasterUrl = (config.rasterUrl || DEFAULT_OAM_RASTER_URL).replace(
1465
+ /\/$/,
1466
+ ""
1467
+ );
1468
+ this.collection = config.collection || DEFAULT_OAM_COLLECTION;
1469
+ this.asset = config.asset || DEFAULT_OAM_ASSET;
1470
+ this.itemId = config.itemId;
1471
+ this.mosaic = config.mosaic === true;
1472
+ this.attribution = config.attribution || "OpenAerialMap / HOT Imagery";
1473
+ this.tileSize = config.tileSize || 256;
1474
+ this.headers = config.headers;
1475
+ if (this.itemId) {
1476
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1477
+ this.itemId,
1478
+ this.rasterUrl,
1479
+ this.collection,
1480
+ this.asset
1481
+ );
1482
+ } else if (this.mosaic) {
1483
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1484
+ this.rasterUrl,
1485
+ this.collection,
1486
+ this.asset
1487
+ );
1488
+ } else {
1489
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1490
+ this.rasterUrl,
1491
+ this.collection,
1492
+ this.asset
1493
+ );
1494
+ }
1495
+ }
1496
+ /**
1497
+ * Resolve tile URL template for an AOI via STAC (unless mosaic/itemId pin).
1498
+ */
1499
+ async resolveTileTemplate(polygon2) {
1500
+ if (this.itemId) {
1501
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1502
+ this.itemId,
1503
+ this.rasterUrl,
1504
+ this.collection,
1505
+ this.asset
1506
+ );
1507
+ return this.tileUrlTemplate;
1508
+ }
1509
+ if (this.mosaic) {
1510
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1511
+ this.rasterUrl,
1512
+ this.collection,
1513
+ this.asset
1514
+ );
1515
+ return this.tileUrlTemplate;
1516
+ }
1517
+ const bbox$1 = bbox(polygon2);
1518
+ const bboxKey = bbox$1.map((n) => n.toFixed(6)).join(",");
1519
+ if (this.resolvedForBboxKey === bboxKey && this.tileUrlTemplate) {
1520
+ return this.tileUrlTemplate;
1521
+ }
1522
+ const features = await searchOamItems(bbox$1, {
1523
+ stacUrl: this.stacUrl,
1524
+ collection: this.collection,
1525
+ headers: this.headers
1526
+ });
1527
+ const best = pickBestOamItem(features);
1528
+ if (best == null ? void 0 : best.id) {
1529
+ this.itemId = best.id;
1530
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1531
+ best.id,
1532
+ this.rasterUrl,
1533
+ this.collection,
1534
+ this.asset
1535
+ );
1536
+ } else {
1537
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1538
+ this.rasterUrl,
1539
+ this.collection,
1540
+ this.asset
1541
+ );
1542
+ }
1543
+ this.resolvedForBboxKey = bboxKey;
1544
+ return this.tileUrlTemplate;
1545
+ }
1546
+ async getImage(polygon2, bands, expression, zoomLevel, requiresSquare = false, stitch = true) {
1547
+ await this.resolveTileTemplate(polygon2);
1548
+ return super.getImage(
1549
+ polygon2,
1550
+ bands,
1551
+ expression,
1552
+ zoomLevel,
1553
+ requiresSquare,
1554
+ stitch
1555
+ );
1556
+ }
1557
+ getTileUrlFromTileCoords(tileCoords, instance) {
1558
+ const [x, y, z] = tileCoords;
1559
+ return instance.tileUrlTemplate.replaceAll("{z}", z.toString()).replaceAll("{x}", x.toString()).replaceAll("{y}", y.toString());
1560
+ }
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
+ }
1383
1710
  class BaseModel {
1384
1711
  constructor(model_id, providerParams, modelParams) {
1385
1712
  __publicField(this, "providerParams");
@@ -1449,6 +1776,33 @@ class BaseModel {
1449
1776
  extraParams: this.providerParams.extraParams
1450
1777
  });
1451
1778
  break;
1779
+ case "oam":
1780
+ this.dataProvider = new Oam({
1781
+ stacUrl: this.providerParams.stacUrl,
1782
+ rasterUrl: this.providerParams.rasterUrl,
1783
+ collection: this.providerParams.collection,
1784
+ asset: this.providerParams.asset,
1785
+ itemId: this.providerParams.itemId,
1786
+ mosaic: this.providerParams.mosaic,
1787
+ attribution: this.providerParams.attribution,
1788
+ tileSize: this.providerParams.tileSize,
1789
+ headers: this.providerParams.headers
1790
+ });
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;
1452
1806
  case "sentinel":
1453
1807
  throw new Error("Sentinel provider not implemented yet");
1454
1808
  default:
@@ -3685,7 +4039,147 @@ const BuildingSegmentationFactory = {
3685
4039
  return create(params, resolvedId, modelParams);
3686
4040
  }
3687
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;
3688
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
+ },
3689
4183
  {
3690
4184
  task: "zero-shot-object-detection",
3691
4185
  library: "@huggingface/transformers",
package/index.d.ts CHANGED
@@ -198,10 +198,112 @@ declare class Wms extends MapSource {
198
198
  number
199
199
  ], instance: Wms): string;
200
200
  }
201
+ export interface OamConfig {
202
+ /** STAC API root (default HOT Imagery STAC). */
203
+ stacUrl?: string;
204
+ /** TiTiler / raster API root. */
205
+ rasterUrl?: string;
206
+ /** STAC collection id. */
207
+ collection?: string;
208
+ /** Asset key used for RGB tiles (default `visual`). */
209
+ asset?: string;
210
+ /**
211
+ * Pin to a specific STAC item id. Skips search.
212
+ * Item tiles: `/collections/{collection}/items/{itemId}/tiles/...`
213
+ */
214
+ itemId?: string;
215
+ /**
216
+ * When true, use the collection mosaic tile endpoint (no STAC search).
217
+ * When false (default) and `itemId` is unset, search STAC for the AOI and
218
+ * use the best matching item's tiles (fallback to mosaic if none found).
219
+ */
220
+ mosaic?: boolean;
221
+ attribution?: string;
222
+ tileSize?: number;
223
+ headers?: Record<string, string>;
224
+ }
225
+ declare class Oam extends MapSource {
226
+ stacUrl: string;
227
+ rasterUrl: string;
228
+ collection: string;
229
+ asset: string;
230
+ itemId?: string;
231
+ mosaic: boolean;
232
+ attribution: string;
233
+ tileSize: number;
234
+ headers?: Record<string, string>;
235
+ /** Resolved XYZ template with `{z}/{x}/{y}` placeholders. */
236
+ tileUrlTemplate: string;
237
+ private resolvedForBboxKey?;
238
+ constructor(config?: OamConfig);
239
+ /**
240
+ * Resolve tile URL template for an AOI via STAC (unless mosaic/itemId pin).
241
+ */
242
+ resolveTileTemplate(polygon: GeoJSON.Feature): Promise<string>;
243
+ getImage(polygon: any, bands?: number[], expression?: string, zoomLevel?: number, requiresSquare?: boolean, stitch?: boolean): Promise<GeoRawImage | GeoRawImage[][]>;
244
+ protected getTileUrlFromTileCoords(tileCoords: [
245
+ number,
246
+ number,
247
+ number
248
+ ], instance: Oam): string;
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
+ }
201
303
  declare abstract class BaseModel {
202
304
  protected static instance: BaseModel | null;
203
305
  protected providerParams: ProviderParams;
204
- protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | undefined;
306
+ protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | Oam | GoogleMaps | undefined;
205
307
  protected model_id: string;
206
308
  protected initialized: boolean;
207
309
  protected modelParams?: PretrainedModelOptions;
@@ -522,6 +624,20 @@ declare class ChangeStarBuildingSegmentation extends BaseDenseSegmentationModel
522
624
  instance: ChangeStarBuildingSegmentation;
523
625
  }>;
524
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
+ }
525
641
  export type MapboxParams = {
526
642
  provider: "mapbox";
527
643
  apiKey: string;
@@ -568,8 +684,47 @@ export type WmsParams = {
568
684
  headers?: Record<string, string>;
569
685
  extraParams?: Record<string, string>;
570
686
  };
687
+ export type OamParams = {
688
+ provider: "oam";
689
+ /** STAC API root (default https://api.imagery.hotosm.org/stac). */
690
+ stacUrl?: string;
691
+ /** Raster/TiTiler API root (default https://api.imagery.hotosm.org/raster). */
692
+ rasterUrl?: string;
693
+ /** STAC collection id (default openaerialmap). */
694
+ collection?: string;
695
+ /** Asset key for RGB tiles (default visual). */
696
+ asset?: string;
697
+ /** Pin to a specific STAC item id (skips search). */
698
+ itemId?: string;
699
+ /** Use collection mosaic tiles instead of per-item STAC search. */
700
+ mosaic?: boolean;
701
+ attribution?: string;
702
+ tileSize?: number;
703
+ headers?: Record<string, string>;
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
+ };
571
726
  export interface InferenceInputs {
572
- polygon: GeoJSON.Feature;
727
+ polygon?: GeoJSON.Feature;
573
728
  classLabel?: string;
574
729
  [key: string]: unknown;
575
730
  }
@@ -586,10 +741,10 @@ export interface InferenceParams {
586
741
  postProcessingParams?: PostProcessingParams;
587
742
  mapSourceParams?: MapSourceParams;
588
743
  }
589
- export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams;
590
- 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";
591
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";
592
- 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;
593
748
  export type ModelConfig = {
594
749
  task: HuggingFaceModelTask | GeobaseAiModelTask;
595
750
  library: string;
@@ -627,6 +782,21 @@ export interface ImageFeatureExtractionResults {
627
782
  modelId: string;
628
783
  };
629
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
+ }
630
800
  export interface ChainInstance {
631
801
  inference: (inputs: InferenceParams) => Promise<ObjectDetectionResults | SegmentationResults>;
632
802
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geoai",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A JavaScript library for running Geo AI models in frontend applications",
5
5
  "keywords": [
6
6
  "geoai",
@@ -63,6 +63,7 @@
63
63
  "test": "vitest",
64
64
  "test:coverage": "vitest --coverage",
65
65
  "test:build": "pnpm run build && vitest run test/build.test.ts",
66
+ "docs:llms": "node ./docs/scripts/generate-llms-txt.mjs",
66
67
  "lint:scripts": "eslint . --ext .ts",
67
68
  "lint:styles": "stylelint ./**/*.{css,scss}",
68
69
  "format:scripts": "prettier . --write",