geopackage-ts
    Preparing search index...

    geopackage-ts

    geopackage-ts

    A modern, backend-only TypeScript implementation of the OGC GeoPackage Encoding Standard. Read, write, and query .gpkg files with a clean synchronous API, zero browser dependencies, and full geometry serialization built from scratch.

    • Full GeoPackage support: features, tiles, and attributes with complete OGC spec compliance
    • Synchronous API: powered by better-sqlite3, no Promises needed
    • Zero geometry dependencies: WKB, WKT, and GeoJSON serialization implemented from scratch (no @ngageoint/simple-features-*)
    • R-tree spatial index: create and query spatial indexes using SQLite's built-in R-tree module
    • Lazy iteration: query results returned as IterableIterator<T> for memory-efficient streaming
    • Dual CJS/ESM: ships both CommonJS and ES module builds with full type declarations
    • Minimal footprint: only 2 runtime dependencies (better-sqlite3, proj4)
    • Node.js >= 22
    • A C++ compiler toolchain (required by better-sqlite3 native addon)
    npm install geopackage-ts
    
    import { GeoPackageManager } from 'geopackage-ts';

    // Open a GeoPackage file
    const gp = GeoPackageManager.open('countries.gpkg');

    // List tables
    console.log('Feature tables:', gp.getFeatureTables());
    console.log('Tile tables:', gp.getTileTables());

    // Query features as GeoJSON
    for (const feature of gp.queryForGeoJSONFeatures('countries')) {
    console.log(feature.properties.name, feature.geometry.type);
    }

    gp.close();
    import { GeoPackageManager, GeoPackageDataType, buildGeometryData, writeGeometryData } from 'geopackage-ts';
    import type { Point, UserColumn } from 'geopackage-ts';

    const gp = GeoPackageManager.create('cities.gpkg');

    // Define additional columns
    const columns: UserColumn[] = [
    {
    index: 2, name: 'name', dataType: GeoPackageDataType.TEXT,
    notNull: false, defaultValue: null, primaryKey: false,
    autoincrement: false, unique: false,
    },
    {
    index: 3, name: 'population', dataType: GeoPackageDataType.INTEGER,
    notNull: false, defaultValue: null, primaryKey: false,
    autoincrement: false, unique: false,
    },
    ];

    // Create a feature table with EPSG:4326
    gp.createFeatureTable('cities', 'geom', 'POINT', 4326, columns);

    // Insert a feature
    const dao = gp.getFeatureDao('cities');
    const point: Point = { type: 'Point', hasZ: false, hasM: false, coordinates: [13.405, 52.52] };
    const geomBuffer = writeGeometryData(buildGeometryData(point, 4326));

    dao.insert({
    table: dao.getTable(),
    values: { geom: geomBuffer, name: 'Berlin', population: 3645000 },
    });

    // Create a spatial index for fast bounding box queries
    gp.indexFeatureTable('cities');

    gp.close();
    const gp = GeoPackageManager.open('cities.gpkg');
    const dao = gp.getFeatureDao('cities');

    // Query features within a bounding box (uses R-tree if available)
    for (const row of dao.queryWithBoundingBox({ minX: 5, maxX: 15, minY: 47, maxY: 55 })) {
    console.log(row.name);
    }

    // Or get GeoJSON directly
    for (const feature of dao.queryForGeoJSONWithBoundingBox({ minX: 5, maxX: 15, minY: 47, maxY: 55 })) {
    console.log(feature.properties, feature.geometry);
    }

    gp.close();
    const gp = GeoPackageManager.create('map.gpkg');

    // Create a tile table
    gp.createTileTable('world', 3857, {
    minX: -20037508.34, minY: -20037508.34,
    maxX: 20037508.34, maxY: 20037508.34,
    });

    // Insert tiles
    const dao = gp.getTileDao('world');
    const pngData = fs.readFileSync('tile_0_0_0.png');
    dao.insert({ zoom_level: 0, tile_column: 0, tile_row: 0, tile_data: pngData });

    // Query tiles
    const tile = dao.queryForTile(0, 0, 0);
    if (tile) {
    fs.writeFileSync('output.png', tile.tile_data);
    }

    // Get available zoom levels
    console.log('Zoom levels:', dao.getZoomLevels());

    gp.close();
    const gp = GeoPackageManager.create('data.gpkg');

    const cols: UserColumn[] = [
    { index: 1, name: 'key', dataType: GeoPackageDataType.TEXT, notNull: true, defaultValue: null, primaryKey: false, autoincrement: false, unique: false },
    { index: 2, name: 'value', dataType: GeoPackageDataType.TEXT, notNull: false, defaultValue: null, primaryKey: false, autoincrement: false, unique: false },
    ];

    gp.createAttributeTable('config', cols);
    const dao = gp.getAttributeDao('config');

    dao.insert({ table: dao.getTable(), values: { key: 'version', value: '1.0' } });

    for (const row of dao.queryForAll()) {
    console.log(row);
    }

    gp.close();
    import { readWKB, writeWKB, readWKT, writeWKT, fromGeoJSON, toGeoJSON, ByteOrder } from 'geopackage-ts';

    // WKB round-trip
    const point = { type: 'Point' as const, hasZ: false, hasM: false, coordinates: [1.5, 2.5] };
    const wkb = writeWKB(point, ByteOrder.LITTLE_ENDIAN);
    const parsed = readWKB(wkb); // { type: 'Point', coordinates: [1.5, 2.5], ... }

    // WKT round-trip
    const geom = readWKT('POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))');
    const wkt = writeWKT(geom); // 'POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))'

    // GeoJSON conversion
    const gjson = toGeoJSON(geom); // standard GeoJSON geometry object
    const back = fromGeoJSON(gjson); // internal Geometry type

    // GeoPackage Binary (GP header + envelope + WKB)
    import { buildGeometryData, writeGeometryData, readGeometryData } from 'geopackage-ts';
    const gd = buildGeometryData(geom, 4326);
    const blob = writeGeometryData(gd); // ready to store in a feature table
    const decoded = readGeometryData(blob);
    console.log(decoded.srsId, decoded.envelope, decoded.geometry);
    import { createTransformFromEPSG, transformBoundingBox } from 'geopackage-ts';

    const toWebMercator = createTransformFromEPSG(4326, 3857);
    const [x, y] = toWebMercator(13.405, 52.52);

    const bbox = transformBoundingBox(
    { minX: -180, minY: -85, maxX: 180, maxY: 85 },
    toWebMercator,
    );
    import { GeoPackageManager } from 'geopackage-ts';

    const result = GeoPackageManager.validate('data.gpkg');
    if (!result.valid) {
    console.error('Validation errors:', result.errors);
    }
    Class Description
    GeoPackageManager Factory for opening, creating, and validating GeoPackage files
    GeoPackage Main entry point: table listing, DAO access, feature/tile/attribute creation
    GeoPackageConnection Low-level better-sqlite3 wrapper (query, exec, pragmas, custom functions)
    DAO Description
    FeatureDao Query, insert, update, delete features; GeoJSON iteration; bounding box queries
    TileDao Query, insert, delete tiles by zoom/column/row; zoom level management
    AttributeDao CRUD for non-spatial attribute tables
    UserDao Base DAO with generic query/insert/update/delete operations
    Function Description
    readWKB / writeWKB Parse and write OGC Well-Known Binary (all types, Z/M, both byte orders)
    readWKT / writeWKT Parse and write Well-Known Text
    fromGeoJSON / toGeoJSON Convert between GeoJSON and internal geometry types
    readGeometryData / writeGeometryData Decode/encode GeoPackage Binary format (GP header + envelope + WKB)
    computeEnvelope Compute bounding box for any geometry
    createPoint / createLineString / createPolygon Factory helpers
    Function Description
    createRTreeIndex Create an R-tree spatial index with auto-sync triggers
    isRTreeIndexed Check if a feature table has an R-tree index
    ensureDataColumnsTables Set up the schema extension tables
    ensureMetadataTables Set up the metadata extension tables
    Function Description
    createTransform Create a transform function from SRS definitions
    createTransformFromEPSG Create a transform function from EPSG codes
    transformBoundingBox Reproject a bounding box
    registerProjection Register a custom projection with proj4

    All GeoPackage spec table structures are available as TypeScript interfaces:

    BoundingBox, SpatialReferenceSystem, Contents, GeometryColumns, TileMatrixSet, TileMatrix, Extension, ValidationResult, ColumnDefinition, TableDefinition

    Geometry types use a discriminated union:

    Geometry = Point | LineString | Polygon | MultiPoint | MultiLineString | MultiPolygon | GeometryCollection

    src/
    ├── index.ts # Public API exports
    ├── geopackage.ts # Main GeoPackage class
    ├── geopackage-manager.ts # Open / create / validate
    ├── types.ts # Shared types, enums, constants, errors
    ├── db/
    │ ├── connection.ts # better-sqlite3 wrapper
    │ └── table-creator.ts # DDL for all GeoPackage system tables
    ├── core/
    │ ├── srs.ts # gpkg_spatial_ref_sys operations
    │ ├── contents.ts # gpkg_contents operations
    │ └── extensions.ts # gpkg_extensions operations
    ├── geom/
    │ ├── geometry.ts # Geometry types + envelope utilities
    │ ├── geometry-data.ts # GeoPackage Binary header encode/decode
    │ ├── wkb/ # WKB reader/writer
    │ ├── wkt/ # WKT reader/writer
    │ └── geojson/ # GeoJSON reader/writer
    ├── features/ # Feature table, DAO, geometry columns
    ├── tiles/ # Tile table, DAO, matrix, utilities
    ├── attributes/ # Attribute table and DAO
    ├── user/ # Base table/row/column/DAO abstractions
    ├── extension/
    │ ├── rtree-index.ts # R-tree spatial index
    │ ├── schema.ts # Data columns extension
    │ └── metadata.ts # Metadata extension
    ├── projection/ # proj4 wrapper for coordinate transforms
    └── io/ # File copy, export, validation

    API documentation is generated with TypeDoc. All public APIs have comprehensive TSDoc comments with @param, @returns, @throws, and @example tags.

    npm run docs
    

    The generated documentation will be in the docs/ directory.

    npm run build          # Build dual CJS/ESM output to dist/
    npm test # Run all tests
    npm run test:watch # Run tests in watch mode
    npm run test:coverage # Run tests with coverage
    npm run lint # Lint with Biome
    npm run format # Format with Biome
    npm run docs # Generate API docs with TypeDoc

    This section covers the key differences when migrating server-side code from @ngageoint/geopackage (v4.x) to geopackage-ts.

    - npm install @ngageoint/geopackage
    + npm install geopackage-ts
    

    No WASM setup required. No setSqljsWasmLocateFile() or setCanvasKitWasmLocateFile() calls.

    - import { GeoPackageManager } from '@ngageoint/geopackage';
    - const gp = await GeoPackageManager.open(filePath);
    + import { GeoPackageManager } from 'geopackage-ts';
    + const gp = GeoPackageManager.open(filePath);
    

    The entire API is synchronous. Remove all await keywords and .then() chains when calling GeoPackage methods. This is a direct consequence of using better-sqlite3 instead of sql.js.

    - const gp = await GeoPackageManager.open(buffer);
    + const gp = GeoPackageManager.open(buffer);
    

    Both string (file path) and Buffer are accepted by the same open() method.

    - const gp = await GeoPackageManager.create(filePath);
    + const gp = GeoPackageManager.create(filePath);
    

    The created GeoPackage is automatically initialized with the required system tables and 4 default SRS entries (undefined cartesian, undefined geographic, EPSG:4326, EPSG:3857).

    - // Old: callback or array-based
    - const rows = gp.queryForGeoJSONFeaturesInTable('rivers');
    - rows.forEach(feature => { ... });
    
    + // New: lazy iterator
    + for (const feature of gp.queryForGeoJSONFeatures('rivers')) {
    +   // feature is a standard GeoJSON Feature
    +   console.log(feature.properties, feature.geometry);
    + }
    

    Result sets are returned as IterableIterator<T>, not arrays. Use for...of to iterate lazily, or [...iterator] to collect into an array. No .close() call needed on result sets: better-sqlite3 handles cleanup automatically.

    - const dao = gp.getFeatureDao('my_table');
    - const resultSet = dao.queryForAll();
    - while (resultSet.moveToNext()) {
    -   const row = resultSet.getRow();
    -   const geometry = row.getGeometry().getGeometry();
    - }
    - resultSet.close();
    
    + const dao = gp.getFeatureDao('my_table');
    + for (const feature of dao.queryForGeoJSON()) {
    +   // Standard GeoJSON Feature:  no manual geometry decoding needed
    +   console.log(feature.geometry.type, feature.properties);
    + }
    
    - import { GeoPackageGeometryData } from '@ngageoint/geopackage';
    - import { Point } from '@ngageoint/simple-features-js';
    - const geomData = new GeoPackageGeometryData();
    - geomData.setGeometry(new Point(13.405, 52.52));
    - geomData.setSrsId(4326);
    - const featureRow = dao.newRow();
    - featureRow.setGeometry(geomData);
    - featureRow.setValue('name', 'Berlin');
    - dao.create(featureRow);
    
    + import { buildGeometryData, writeGeometryData } from 'geopackage-ts';
    + import type { Point } from 'geopackage-ts';
    + const point: Point = { type: 'Point', hasZ: false, hasM: false, coordinates: [13.405, 52.52] };
    + const geomBuffer = writeGeometryData(buildGeometryData(point, 4326));
    + dao.insert({ table: dao.getTable(), values: { geom: geomBuffer, name: 'Berlin' } });
    
    - // Old: class-based NGA geometry hierarchy
    - import { Point, LineString, Polygon } from '@ngageoint/simple-features-js';
    - const point = new Point(1, 2);
    - point.hasZ = true;
    - point.z = 100;
    
    + // New: plain objects with discriminated union
    + import type { Point } from 'geopackage-ts';
    + const point: Point = { type: 'Point', hasZ: true, hasM: false, coordinates: [1, 2, 100] };
    

    No @ngageoint/simple-features-js, @ngageoint/simple-features-wkb-js, @ngageoint/simple-features-geojson-js, or @ngageoint/simple-features-proj-js packages needed. All geometry serialization is built in.

    - import { RTreeIndexExtension } from '@ngageoint/geopackage';
    - const rtree = new RTreeIndexExtension(gp);
    - rtree.createWithFeatureTable(featureTable);
    - const featureIndexManager = new FeatureIndexManager(gp, 'my_table');
    - featureIndexManager.setIndexLocation(FeatureIndexType.RTREE);
    - const results = featureIndexManager.queryWithBoundingBox(bbox, projection);
    
    + // Create the index
    + gp.indexFeatureTable('my_table');
    +
    + // Query:  automatically uses R-tree if available
    + const dao = gp.getFeatureDao('my_table');
    + for (const row of dao.queryWithBoundingBox({ minX: 0, maxX: 10, minY: 0, maxY: 10 })) {
    +   console.log(row);
    + }
    
    - const tileDao = gp.getTileDao('my_tiles');
    - const resultSet = tileDao.queryForTile(col, row, zoom);
    - if (resultSet.getCount() > 0) {
    -   resultSet.moveToNext();
    -   const tileRow = resultSet.getRow();
    -   const tileData = tileRow.getTileData();
    - }
    - resultSet.close();
    
    + const tileDao = gp.getTileDao('my_tiles');
    + const tile = tileDao.queryForTile(col, row, zoom);
    + if (tile) {
    +   const tileData = tile.tile_data; // Buffer
    + }
    
    - import { ProjectionFactory, Projections } from '@ngageoint/projections-js';
    - const proj = ProjectionFactory.getProjection(Projections.EPSG_4326);
    
    + import { createTransformFromEPSG } from 'geopackage-ts';
    + const transform = createTransformFromEPSG(4326, 3857);
    + const [x, y] = transform(lon, lat);
    

    The following features from @ngageoint/geopackage are intentionally not included:

    Removed Reason
    Canvas / CanvasKit rendering Backend-only: no image generation
    FeatureTiles / drawTile() Tile rendering is out of scope
    sql.js / WASM SQLite Replaced by native better-sqlite3
    Browser support Node.js only
    setCanvasKitWasmLocateFile() No WASM dependencies
    setSqljsWasmLocateFile() No WASM dependencies
    Web Worker support Not needed server-side
    Leaflet integration No map framework dependencies
    Feature simplification No simplify-js dependency
    NGA geometry class hierarchy Replaced with discriminated unions
    6-level DAO inheritance Simplified to flat DAO classes
    Related Tables extension Not yet implemented
    NGA Contents ID extension Not yet implemented
    NGA Feature Tile Link Not yet implemented
    @ngageoint/geopackage geopackage-ts
    await GeoPackageManager.open(path) GeoPackageManager.open(path)
    await GeoPackageManager.create(path) GeoPackageManager.create(path)
    new Point(x, y) { type: 'Point', hasZ: false, hasM: false, coordinates: [x, y] }
    createPoint(x, y) createPoint(x, y)
    resultSet.moveToNext() / .close() for (const row of dao.queryForAll())
    featureRow.getGeometry() readGeometryData(row.geom)
    geoPackageGeometryData.setGeometry(pt) writeGeometryData(buildGeometryData(pt, srsId))
    dao.create(row) dao.insert({ table, values })
    new RTreeIndexExtension(gp) gp.indexFeatureTable(tableName)
    featureIndexManager.queryWithBoundingBox() dao.queryWithBoundingBox(bbox)

    MIT