geoai 1.0.6 → 1.0.7

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,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.7] - 2026-07-17
11
+
12
+ ### Added
13
+ - OpenAerialMap (OAM) map provider — HOT Imagery STAC + TiTiler (`provider: "oam"`)
14
+ - `examples/07-oam-quickstart` — Rome orthophoto draw-to-detect demo
15
+ - OAM option in `live-examples-nextjs` (flies to Rome; mosaic tiles)
16
+
17
+ ### Fixed
18
+ - 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/`
19
+
10
20
  ## [1.0.6] - 2026-07-16
11
21
 
12
22
  ### Added
package/README.md CHANGED
@@ -40,12 +40,12 @@ You can also use GeoAI directly in the browser via CDN:
40
40
 
41
41
  #### Unpkg CDN
42
42
  ```html
43
- <script src="https://unpkg.com/geoai@1.0.6/geoai.js"></script>
43
+ <script src="https://unpkg.com/geoai@1.0.7/geoai.js"></script>
44
44
  ```
45
45
 
46
46
  #### jsDelivr CDN
47
47
  ```html
48
- <script src="https://cdn.jsdelivr.net/npm/geoai@1.0.6/geoai.min.js"></script>
48
+ <script src="https://cdn.jsdelivr.net/npm/geoai@1.0.7/geoai.min.js"></script>
49
49
  ```
50
50
 
51
51
  When using CDN, the library is available globally as `geoai`.
@@ -186,6 +186,17 @@ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
186
186
  });
187
187
  ```
188
188
 
189
+ ### OpenAerialMap (OAM)
190
+ Community aerial imagery via HOT Imagery STAC + TiTiler — no API key.
191
+
192
+ ```javascript
193
+ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
194
+ provider: "oam",
195
+ // itemId: "67826781a07cc20001818cdb", // optional pin
196
+ // mosaic: true, // optional: collection mosaic only
197
+ });
198
+ ```
199
+
189
200
  ### ESRI
190
201
  ESRI World Imagery - no API key required.
191
202
 
@@ -231,6 +242,39 @@ const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
231
242
  - [Slides (PDF)](https://geobase-docs.s3.amazonaws.com/geobase-ai-assets/talks/lightning-talk-webgpu-geoai.pdf)
232
243
  - [Slide source (Marp)](talks/lightning-talk-webgpu-geoai.md)
233
244
 
245
+ ## Agents & LLMs
246
+
247
+ ### Install the GeoAI agent skill
248
+
249
+ Use the [Skills CLI](https://skills.sh/) so coding agents (Cursor, Claude Code, Codex, etc.) load GeoAI.js integration guidance automatically:
250
+
251
+ ```bash
252
+ # Install into the current project (recommended)
253
+ npx skills add decision-labs/geoai.js --skill geoai -y
254
+
255
+ # Or install globally for all projects
256
+ npx skills add decision-labs/geoai.js --skill geoai -g -y
257
+ ```
258
+
259
+ List what you have installed:
260
+
261
+ ```bash
262
+ npx skills list
263
+ ```
264
+
265
+ Use the skill once without installing (pipes a prompt into an agent):
266
+
267
+ ```bash
268
+ npx skills use decision-labs/geoai.js@geoai
269
+ ```
270
+
271
+ Source: [`skills/geoai`](skills/geoai) — pipeline API, task picker, providers, and Web Worker patterns for background inference.
272
+
273
+ ### LLM-readable docs
274
+
275
+ - **Index**: [docs.geobase.app/geoai/llms.txt](https://docs.geobase.app/geoai/llms.txt)
276
+ - **Full corpus**: [docs.geobase.app/geoai/llms-full.txt](https://docs.geobase.app/geoai/llms-full.txt) — regenerate with `pnpm docs:llms`
277
+
234
278
  ## Links
235
279
 
236
280
  - **Documentation**: [docs.geobase.app/geoai](https://docs.geobase.app/geoai) - Comprehensive documentation, examples, and API reference
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;
394
394
  if (instance.model_id !== model_id) {
395
395
  return true;
396
396
  }
@@ -425,6 +425,11 @@ 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;
428
433
  }
429
434
  if (modelParams) {
430
435
  const instanceModelParams = instance.modelParams || {};
@@ -1380,6 +1385,175 @@ class Wms extends MapSource {
1380
1385
  );
1381
1386
  }
1382
1387
  }
1388
+ const DEFAULT_OAM_STAC_URL = "https://api.imagery.hotosm.org/stac";
1389
+ const DEFAULT_OAM_RASTER_URL = "https://api.imagery.hotosm.org/raster";
1390
+ const DEFAULT_OAM_COLLECTION = "openaerialmap";
1391
+ const DEFAULT_OAM_ASSET = "visual";
1392
+ function buildOamMosaicTileUrlTemplate(rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1393
+ const base = rasterUrl.replace(/\/$/, "");
1394
+ return `${base}/collections/${encodeURIComponent(collection)}/tiles/WebMercatorQuad/{z}/{x}/{y}?assets=${encodeURIComponent(asset)}`;
1395
+ }
1396
+ function buildOamItemTileUrlTemplate(itemId, rasterUrl = DEFAULT_OAM_RASTER_URL, collection = DEFAULT_OAM_COLLECTION, asset = DEFAULT_OAM_ASSET) {
1397
+ const base = rasterUrl.replace(/\/$/, "");
1398
+ return `${base}/collections/${encodeURIComponent(collection)}/items/${encodeURIComponent(itemId)}/tiles/WebMercatorQuad/{z}/{x}/{y}@1x?assets=${encodeURIComponent(asset)}`;
1399
+ }
1400
+ function pickBestOamItem(features) {
1401
+ if (!features.length) return void 0;
1402
+ return features.slice().sort((a, b) => {
1403
+ var _a, _b, _c, _d, _e, _f;
1404
+ const gsdA = typeof ((_a = a.properties) == null ? void 0 : _a.gsd) === "number" ? a.properties.gsd : Number.POSITIVE_INFINITY;
1405
+ const gsdB = typeof ((_b = b.properties) == null ? void 0 : _b.gsd) === "number" ? b.properties.gsd : Number.POSITIVE_INFINITY;
1406
+ if (gsdA !== gsdB) return gsdA - gsdB;
1407
+ const dateA = Date.parse(
1408
+ String(((_c = a.properties) == null ? void 0 : _c.created) || ((_d = a.properties) == null ? void 0 : _d.datetime) || 0)
1409
+ );
1410
+ const dateB = Date.parse(
1411
+ String(((_e = b.properties) == null ? void 0 : _e.created) || ((_f = b.properties) == null ? void 0 : _f.datetime) || 0)
1412
+ );
1413
+ return dateB - dateA;
1414
+ })[0];
1415
+ }
1416
+ async function searchOamItems(bbox2, options = {}) {
1417
+ const stacUrl = (options.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1418
+ const collection = options.collection || DEFAULT_OAM_COLLECTION;
1419
+ const limit = options.limit ?? 10;
1420
+ const fetchImpl = options.fetchImpl || fetch;
1421
+ const response = await fetchImpl(`${stacUrl}/search`, {
1422
+ method: "POST",
1423
+ headers: {
1424
+ "Content-Type": "application/json",
1425
+ Accept: "application/geo+json",
1426
+ ...options.headers || {}
1427
+ },
1428
+ body: JSON.stringify({
1429
+ collections: [collection],
1430
+ bbox: bbox2,
1431
+ limit
1432
+ })
1433
+ });
1434
+ if (!response.ok) {
1435
+ const body = await response.text().catch(() => "");
1436
+ throw new Error(
1437
+ `OAM STAC search failed (${response.status}): ${body || response.statusText}`
1438
+ );
1439
+ }
1440
+ const data = await response.json();
1441
+ return data.features || [];
1442
+ }
1443
+ class Oam extends MapSource {
1444
+ constructor(config = {}) {
1445
+ super();
1446
+ __publicField(this, "stacUrl");
1447
+ __publicField(this, "rasterUrl");
1448
+ __publicField(this, "collection");
1449
+ __publicField(this, "asset");
1450
+ __publicField(this, "itemId");
1451
+ __publicField(this, "mosaic");
1452
+ __publicField(this, "attribution");
1453
+ __publicField(this, "tileSize");
1454
+ __publicField(this, "headers");
1455
+ /** Resolved XYZ template with `{z}/{x}/{y}` placeholders. */
1456
+ __publicField(this, "tileUrlTemplate");
1457
+ __publicField(this, "resolvedForBboxKey");
1458
+ this.stacUrl = (config.stacUrl || DEFAULT_OAM_STAC_URL).replace(/\/$/, "");
1459
+ this.rasterUrl = (config.rasterUrl || DEFAULT_OAM_RASTER_URL).replace(
1460
+ /\/$/,
1461
+ ""
1462
+ );
1463
+ this.collection = config.collection || DEFAULT_OAM_COLLECTION;
1464
+ this.asset = config.asset || DEFAULT_OAM_ASSET;
1465
+ this.itemId = config.itemId;
1466
+ this.mosaic = config.mosaic === true;
1467
+ this.attribution = config.attribution || "OpenAerialMap / HOT Imagery";
1468
+ this.tileSize = config.tileSize || 256;
1469
+ this.headers = config.headers;
1470
+ if (this.itemId) {
1471
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1472
+ this.itemId,
1473
+ this.rasterUrl,
1474
+ this.collection,
1475
+ this.asset
1476
+ );
1477
+ } else if (this.mosaic) {
1478
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1479
+ this.rasterUrl,
1480
+ this.collection,
1481
+ this.asset
1482
+ );
1483
+ } else {
1484
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1485
+ this.rasterUrl,
1486
+ this.collection,
1487
+ this.asset
1488
+ );
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Resolve tile URL template for an AOI via STAC (unless mosaic/itemId pin).
1493
+ */
1494
+ async resolveTileTemplate(polygon2) {
1495
+ if (this.itemId) {
1496
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1497
+ this.itemId,
1498
+ this.rasterUrl,
1499
+ this.collection,
1500
+ this.asset
1501
+ );
1502
+ return this.tileUrlTemplate;
1503
+ }
1504
+ if (this.mosaic) {
1505
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1506
+ this.rasterUrl,
1507
+ this.collection,
1508
+ this.asset
1509
+ );
1510
+ return this.tileUrlTemplate;
1511
+ }
1512
+ const bbox$1 = bbox(polygon2);
1513
+ const bboxKey = bbox$1.map((n) => n.toFixed(6)).join(",");
1514
+ if (this.resolvedForBboxKey === bboxKey && this.tileUrlTemplate) {
1515
+ return this.tileUrlTemplate;
1516
+ }
1517
+ const features = await searchOamItems(bbox$1, {
1518
+ stacUrl: this.stacUrl,
1519
+ collection: this.collection,
1520
+ headers: this.headers
1521
+ });
1522
+ const best = pickBestOamItem(features);
1523
+ if (best == null ? void 0 : best.id) {
1524
+ this.itemId = best.id;
1525
+ this.tileUrlTemplate = buildOamItemTileUrlTemplate(
1526
+ best.id,
1527
+ this.rasterUrl,
1528
+ this.collection,
1529
+ this.asset
1530
+ );
1531
+ } else {
1532
+ this.tileUrlTemplate = buildOamMosaicTileUrlTemplate(
1533
+ this.rasterUrl,
1534
+ this.collection,
1535
+ this.asset
1536
+ );
1537
+ }
1538
+ this.resolvedForBboxKey = bboxKey;
1539
+ return this.tileUrlTemplate;
1540
+ }
1541
+ async getImage(polygon2, bands, expression, zoomLevel, requiresSquare = false, stitch = true) {
1542
+ await this.resolveTileTemplate(polygon2);
1543
+ return super.getImage(
1544
+ polygon2,
1545
+ bands,
1546
+ expression,
1547
+ zoomLevel,
1548
+ requiresSquare,
1549
+ stitch
1550
+ );
1551
+ }
1552
+ getTileUrlFromTileCoords(tileCoords, instance) {
1553
+ const [x, y, z] = tileCoords;
1554
+ return instance.tileUrlTemplate.replaceAll("{z}", z.toString()).replaceAll("{x}", x.toString()).replaceAll("{y}", y.toString());
1555
+ }
1556
+ }
1383
1557
  class BaseModel {
1384
1558
  constructor(model_id, providerParams, modelParams) {
1385
1559
  __publicField(this, "providerParams");
@@ -1449,6 +1623,19 @@ class BaseModel {
1449
1623
  extraParams: this.providerParams.extraParams
1450
1624
  });
1451
1625
  break;
1626
+ case "oam":
1627
+ this.dataProvider = new Oam({
1628
+ stacUrl: this.providerParams.stacUrl,
1629
+ rasterUrl: this.providerParams.rasterUrl,
1630
+ collection: this.providerParams.collection,
1631
+ asset: this.providerParams.asset,
1632
+ itemId: this.providerParams.itemId,
1633
+ mosaic: this.providerParams.mosaic,
1634
+ attribution: this.providerParams.attribution,
1635
+ tileSize: this.providerParams.tileSize,
1636
+ headers: this.providerParams.headers
1637
+ });
1638
+ break;
1452
1639
  case "sentinel":
1453
1640
  throw new Error("Sentinel provider not implemented yet");
1454
1641
  default:
package/index.d.ts CHANGED
@@ -198,10 +198,59 @@ 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
+ }
201
250
  declare abstract class BaseModel {
202
251
  protected static instance: BaseModel | null;
203
252
  protected providerParams: ProviderParams;
204
- protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | undefined;
253
+ protected dataProvider: Mapbox | Geobase | Esri | Tms | Wms | Oam | undefined;
205
254
  protected model_id: string;
206
255
  protected initialized: boolean;
207
256
  protected modelParams?: PretrainedModelOptions;
@@ -568,6 +617,24 @@ export type WmsParams = {
568
617
  headers?: Record<string, string>;
569
618
  extraParams?: Record<string, string>;
570
619
  };
620
+ export type OamParams = {
621
+ provider: "oam";
622
+ /** STAC API root (default https://api.imagery.hotosm.org/stac). */
623
+ stacUrl?: string;
624
+ /** Raster/TiTiler API root (default https://api.imagery.hotosm.org/raster). */
625
+ rasterUrl?: string;
626
+ /** STAC collection id (default openaerialmap). */
627
+ collection?: string;
628
+ /** Asset key for RGB tiles (default visual). */
629
+ asset?: string;
630
+ /** Pin to a specific STAC item id (skips search). */
631
+ itemId?: string;
632
+ /** Use collection mosaic tiles instead of per-item STAC search. */
633
+ mosaic?: boolean;
634
+ attribution?: string;
635
+ tileSize?: number;
636
+ headers?: Record<string, string>;
637
+ };
571
638
  export interface InferenceInputs {
572
639
  polygon: GeoJSON.Feature;
573
640
  classLabel?: string;
@@ -586,7 +653,7 @@ export interface InferenceParams {
586
653
  postProcessingParams?: PostProcessingParams;
587
654
  mapSourceParams?: MapSourceParams;
588
655
  }
589
- export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams;
656
+ export type ProviderParams = MapboxParams | SentinelParams | GeobaseParams | EsriParams | TmsParams | WmsParams | OamParams;
590
657
  export type HuggingFaceModelTask = "mask-generation" | "zero-shot-object-detection" | "zero-shot-image-classification" | "object-detection" | "oriented-object-detection" | "image-feature-extraction";
591
658
  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
659
  export type ModelInstance = MaskGeneration | ZeroShotObjectDetection | ObjectDetection | OrientedObjectDetection | LandCoverClassification | SolarPanelDetection | ShipDetection | CarDetection | WetLandSegmentation | BuildingDetection | ChangeStarBuildingSegmentation | OilStorageTankDetection | BuildingFootPrintSegmentation | ImageFeatureExtraction;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geoai",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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",