The RTree Spatial Index extension (gpkg_rtree_index) stores a SQLite R*Tree virtual table alongside a feature table. The index maps each feature’s row ID to the minimum bounding rectangle (MBR) of its geometry, enabling the database engine to eliminate non-matching rows without reading or parsing geometry blobs.
How It Works
When the extension is created for a feature table, the library:
- Creates a virtual table
rtree_<table>_<geometry_column> using SQLite’s rtree module.
- Populates it with
(id, minx, maxx, miny, maxy) rows derived from each geometry.
- Installs triggers on the feature table to keep the index synchronized on insert, update, and delete.
Spatial queries join the R*Tree virtual table against the feature table, letting SQLite prune entire subtrees of the index before touching the main table.
Key Classes
RTreeIndexExtension
Low-level extension class. Manages creation, deletion, and trigger wiring for the R*Tree virtual table.
import { RTreeIndexExtension } from '@ngageoint/geopackage';
const rTree = new RTreeIndexExtension(geoPackage);
const featureDao = geoPackage.getFeatureDao('my_features');
const rTreeDao = rTree.getTableDao(featureDao);
// Check whether the index exists
const isIndexed = rTreeDao.has();
// Create the index
rTreeDao.createExtension();
// Drop the index
rTreeDao.delete();
FeatureIndexManager (recommended)
FeatureIndexManager is the preferred high-level API. It abstracts over multiple index backends and picks the best available one automatically.
import { FeatureIndexManager } from '@ngageoint/geopackage';
const indexManager = new FeatureIndexManager(geoPackage, 'my_features');
FeatureIndexType Enum
| Value | Description |
|---|
RTREE | SQLite R*Tree virtual table (fastest; preferred) |
GEOPACKAGE | NGA geometry index stored in GeoPackage extension tables |
NONE | No index; falls back to a full table scan |
The default query order is RTREE → GEOPACKAGE. The manager falls back to the next type automatically when a higher-priority index is not present.
Creating an Index
Open the GeoPackage
import { GeoPackageAPI, FeatureIndexManager, FeatureIndexType } from '@ngageoint/geopackage';
const geoPackage = await GeoPackageAPI.open('my.gpkg');
Instantiate the index manager
const indexManager = new FeatureIndexManager(geoPackage, 'my_features');
Set the index location and build the index
indexManager.setIndexLocation(FeatureIndexType.RTREE);
// Index the table (no-op if already indexed, unless force=true)
const count = indexManager.index();
console.log(`Indexed ${count} features`);
To force a full re-index:
const count = indexManager.indexType(FeatureIndexType.RTREE, /* force */ true);
Querying with a Bounding Box
import { BoundingBox } from '@ngageoint/geopackage';
import { Projections } from '@ngageoint/projections-js';
const bbox = new BoundingBox(-180, -90, 180, 90); // minLon, minLat, maxLon, maxLat
const wgs84 = Projections.getWGS84Projection();
const results = indexManager.queryWithBoundingBoxAndProjection(bbox, wgs84);
for (const featureRow of results) {
console.log(featureRow.getId(), featureRow.getGeometry());
}
results.close();
Always call results.close() when you are done iterating to release the underlying SQLite statement.
Checking Index Status
// Is any index available?
const isIndexed = indexManager.isIndexed();
// Which index type is being used?
const location = indexManager.getIndexedType();
console.log(FeatureIndexType[location]); // "RTREE"
// Count features in the index
const total = indexManager.count();
Controlling Query Order
You can control which index types are tried and in what order:
import { FeatureIndexType } from '@ngageoint/geopackage';
// Always prefer RTREE
indexManager.prioritizeQueryLocation([FeatureIndexType.RTREE]);
// Override entirely
indexManager.setIndexLocationOrder([FeatureIndexType.RTREE, FeatureIndexType.GEOPACKAGE]);
Deleting an Index
// Delete through the extension directly
const rTree = new RTreeIndexExtension(geoPackage);
const featureDao = geoPackage.getFeatureDao('my_features');
const rTreeDao = rTree.getTableDao(featureDao);
rTreeDao.delete();
// Or via ExtensionManager
geoPackage.getExtensionManager().deleteRTreeSpatialIndex('my_features');