Skip to main content
FeatureIndexManager builds and queries spatial indexes on a feature table. Indexed queries are significantly faster than full-table scans when filtering by bounding box or geometry envelope. Two index backends are supported: RTree (SQLite virtual table, recommended) and GeoPackage (NGA extension tables).
import { FeatureIndexManager, FeatureIndexType } from '@ngageoint/geopackage';

const indexManager = new FeatureIndexManager(geoPackage, 'my_features');
Always call close() on the FeatureIndexManager when you are done with it to release internal connections.

Constructor

new FeatureIndexManager(geoPackage: GeoPackage, featureTableNameOrDao: string | FeatureDao)
geoPackage
GeoPackage
required
An open GeoPackage instance.
featureTableNameOrDao
string | FeatureDao
required
Either the name of the feature table as a string, or an existing FeatureDao for that table.

Index lifecycle

setIndexLocation()

Sets the preferred index backend to use for index() calls and subsequent write operations.
setIndexLocation(indexLocation: FeatureIndexType): void
indexLocation
FeatureIndexType
required
The index backend to target. See FeatureIndexType below.
indexManager.setIndexLocation(FeatureIndexType.RTREE);

index()

Builds the spatial index (at the configured index location) if it does not already exist. Returns the number of features indexed.
index(): number
return
number
Count of indexed features. Returns 0 if the index already exists and force was not set.
indexManager.setIndexLocation(FeatureIndexType.RTREE);
const count = indexManager.index();
console.log(`Indexed ${count} features`);

indexType()

Builds the index at the explicitly specified type, optionally forcing a re-index.
indexType(type?: FeatureIndexType, force?: boolean): number
type
FeatureIndexType
Index backend to use. Defaults to the configured index location.
force
boolean
When true, drops and rebuilds the index even if one already exists. Defaults to false.

isIndexed()

Returns true if any supported index exists for this feature table.
isIndexed(): boolean
if (!indexManager.isIndexed()) {
  indexManager.setIndexLocation(FeatureIndexType.RTREE);
  indexManager.index();
}

isIndexedForType()

Returns true if an index of the specified type exists.
isIndexedForType(type: FeatureIndexType): boolean

deleteIndexType()

Deletes the index at the specified (or configured) location.
deleteIndexType(type?: FeatureIndexType): boolean

close()

Closes internal index connections. Call this when the FeatureIndexManager is no longer needed.
close(): void

Querying

All query methods return a FeatureIndexResults that must be closed after iteration.
Close every FeatureIndexResults after use to avoid leaking database cursors.

query()

Returns indexed features, optionally filtered by a raw SQL WHERE clause.
query(where?: string, whereArgs?: any[]): FeatureIndexResults
where
string
SQL WHERE expression without the WHERE keyword. Omit to return all indexed features.
whereArgs
any[]
Bind arguments that replace ? placeholders in where.
const results = indexManager.query();
try {
  for (const row of results) {
    console.log(row.getId(), row.getGeometryValue());
  }
} finally {
  results.close();
}

queryAll()

Returns all indexed features without a filter. Equivalent to calling query() with no arguments.
queryAll(): FeatureIndexResults

queryWithBoundingBox()

Returns features whose geometries intersect the given bounding box. The bounding box is expected to be in the same projection as the feature table.
queryWithBoundingBox(boundingBox: BoundingBox, where?: string, whereArgs?: any[]): FeatureIndexResults
boundingBox
BoundingBox
required
Bounding box filter in the feature table’s native projection.
where
string
Optional additional SQL WHERE clause.
whereArgs
any[]
Bind arguments for the where clause.
import { BoundingBox } from '@ngageoint/geopackage';

const bbox = new BoundingBox(-180, -90, 180, 90);
const results = indexManager.queryWithBoundingBox(bbox);
try {
  while (results.moveToNext()) {
    const row = results.getFeatureRow();
  }
} finally {
  results.close();
}

queryWithBoundingBoxAndProjection()

Returns features whose geometries intersect the given bounding box, converting coordinates from the supplied projection to the feature table’s native projection before querying.
queryWithBoundingBoxAndProjection(
  boundingBox: BoundingBox,
  projection: Projection,
  where?: string,
  whereArgs?: any[]
): FeatureIndexResults
boundingBox
BoundingBox
required
Bounding box in the coordinate system of projection.
projection
Projection
required
The projection of the bounding box coordinates (e.g. Projections.getWGS84Projection()).
where
string
Optional additional SQL WHERE clause.
whereArgs
any[]
Bind arguments for the where clause.
import { Projections } from '@ngageoint/projections-js';

const bbox = new BoundingBox(0, -90, 180, 90);
const results = indexManager.queryWithBoundingBoxAndProjection(
  bbox,
  Projections.getWGS84Projection(),
);
try {
  for (const row of results) {
    console.log(row.getValue('name'));
  }
} finally {
  results.close();
}

queryWithGeometryEnvelope()

Returns features whose geometries intersect the given GeometryEnvelope.
queryWithGeometryEnvelope(envelope: GeometryEnvelope, where?: string, whereArgs?: any[]): FeatureIndexResults
envelope
GeometryEnvelope
required
Geometry envelope to filter by, from @ngageoint/simple-features-js.
where
string
Optional additional SQL WHERE clause.

queryWithFieldValues()

Returns features where all provided field values match, backed by the spatial index.
queryWithFieldValues(fieldValues: FieldValues): FeatureIndexResults

Counting

countAll()

Returns the total number of indexed features.
countAll(): number

count()

Returns the count of features matching the optional WHERE clause.
count(where: string, whereArgs: any[]): number
where
string
required
SQL WHERE expression.
whereArgs
any[]
required
Bind arguments.

countWithBoundingBox()

Returns the count of features intersecting the given bounding box.
countWithBoundingBox(boundingBox: BoundingBox, where?: string, whereArgs?: any[]): number

countWithGeometryEnvelope()

Returns the count of features intersecting the given geometry envelope.
countWithGeometryEnvelope(envelope: GeometryEnvelope, where?: string, whereArgs?: any[]): number

FeatureIndexType

FeatureIndexType is an enum that identifies which index backend to use.
enum FeatureIndexType {
  GEOPACKAGE, // NGA geometry index extension tables
  RTREE,      // SQLite RTree virtual table (recommended)
  NONE,       // No index
}
ValueDescription
RTREESQLite RTree virtual table. Fastest and zero-copy; updated automatically by database triggers.
GEOPACKAGENGA GeoPackage extension tables. More portable but slower to build.
NONENo index. Queries fall back to a full-table scan via ManualFeatureQuery.
Use FeatureIndexType.RTREE unless you have a specific requirement for the GeoPackage extension format.

Full example

import {
  GeoPackageManager,
  FeatureIndexManager,
  FeatureIndexType,
  BoundingBox,
} from '@ngageoint/geopackage';

const geoPackage = await GeoPackageManager.open('./data.gpkg');
const indexManager = new FeatureIndexManager(geoPackage, 'cities');

// Build RTree index if not already present
if (!indexManager.isIndexed()) {
  indexManager.setIndexLocation(FeatureIndexType.RTREE);
  const count = indexManager.index();
  console.log(`Indexed ${count} features`);
}

// Query by bounding box (WGS 84)
const europe = new BoundingBox(-25, 34, 45, 72);
const results = indexManager.queryWithBoundingBox(europe);
try {
  while (results.moveToNext()) {
    const row = results.getFeatureRow();
    console.log(row.getValue('name'));
  }
} finally {
  results.close();
}

indexManager.close();
geoPackage.close();

Build docs developers (and LLMs) love