GeoPackage JS can serve tiles in two ways: reading pre-stored raster tiles from a tile table, and dynamically rendering feature data as raster tiles using FeatureTiles.
Getting tiles from a tile table
Use GeoPackageTileRetriever to fetch a web-mercator (XYZ) tile reprojected and scaled to the requested size.
import {
GeoPackageManager,
GeoPackageTileRetriever,
Canvas,
TileUtils,
setCanvasKitWasmLocateFile,
} from '@ngageoint/geopackage';
setCanvasKitWasmLocateFile(
file => `/path/to/node_modules/@ngageoint/geopackage/dist/canvaskit/${file}`
);
const geoPackage = await GeoPackageManager.open('/path/to/file.gpkg');
const tileDao = geoPackage.getTileDao('my_tiles');
const retriever = new GeoPackageTileRetriever(
tileDao,
TileUtils.TILE_PIXELS_DEFAULT, // width = 256
TileUtils.TILE_PIXELS_DEFAULT, // height = 256
);
const x = 0, y = 0, zoom = 0;
const gpTile = await retriever.getTile(x, y, zoom);
if (gpTile) {
// Raw bytes (PNG/JPEG/etc.) stored in the GeoPackage
const rawBytes = gpTile.getData();
// Decoded image ready for canvas
const gpImage = await gpTile.getGeoPackageImage();
// Draw into a canvas
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const ctx = canvas.getContext('2d');
ctx.drawImage(gpImage.getImage(), 0, 0);
const dataURL = canvas.toDataURL('image/png');
console.log(dataURL);
// In Node.js, always dispose to prevent memory leaks
Canvas.disposeImage(gpImage);
Canvas.disposeCanvas(canvas);
}
XYZ tile shortcut
geoPackage.xyzTile() combines getTileDao and GeoPackageTileRetriever into a single convenience method.
const gpTile = await geoPackage.xyzTile(
'my_tiles', // table name
x, // tile column
y, // tile row
z, // zoom level
256, // width (optional, defaults to 256)
256, // height (optional, defaults to 256)
);
if (gpTile) {
const gpImage = await gpTile.getGeoPackageImage();
// use gpImage.getImageData() for transferable pixel data (e.g. Web Workers)
const imageData = gpImage.getImageData();
Canvas.disposeImage(gpImage);
}
Raw tile bytes
Query the tile table directly with tileDao.queryForTile() when you need the original stored bytes without reprojection:
const tileRow = tileDao.queryForTile(
/* tileColumn */ 0,
/* tileRow */ 0,
/* zoom */ 0,
);
if (tileRow) {
const rawBytes = tileRow.getTileData(); // Buffer in Node.js, Uint8Array in browser
}
Feature tiles: rendering vector data as raster
FeatureTiles renders feature geometries (points, lines, polygons) into a raster tile image on the fly. This is useful for displaying feature tables on a slippy map without pre-generating tiles.
import {
FeatureTiles,
Canvas,
TileUtils,
} from '@ngageoint/geopackage';
const featureDao = geoPackage.getFeatureDao('my_features');
// Width and height default to TileUtils.TILE_PIXELS_DEFAULT (256)
const ft = new FeatureTiles(geoPackage, featureDao, 256, 256);
const x = 0, y = 0, zoom = 0;
const gpTile = await ft.drawTile(x, y, zoom);
if (gpTile) {
// Decode the tile bytes into a GeoPackageImage
const gpImage = await gpTile.getGeoPackageImage();
// Draw into a canvas
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const ctx = canvas.getContext('2d');
ctx.drawImage(gpImage.getImage(), 0, 0);
const dataURL = canvas.toDataURL('image/png');
// Dispose resources in Node.js
Canvas.disposeImage(gpImage);
Canvas.disposeCanvas(canvas);
}
FeatureTiles uses FeatureIndexManager internally to retrieve only the features that intersect the requested tile bounds, making rendering efficient for indexed tables.
GeoPackageImage and ImageData
A GeoPackageImage wraps the platform-specific image object:
| Method | Returns | Use case |
|---|
getImage() | Native image element / CanvasKit image | ctx.drawImage() |
getImageData() | ImageData | ctx.putImageData(), Web Workers (postMessage) |
Use getImageData() when you need to transfer pixel data across a Web Worker boundary (see the Web Worker guide).
Memory management in Node.js
In Node.js, the library uses CanvasKit (a WebAssembly build of Skia). CanvasKit allocates memory outside the V8 heap, so the garbage collector will not automatically free it.
Always call Canvas.disposeImage() and Canvas.disposeCanvas() after you are finished with an image or canvas.
// Pattern: always dispose in a finally block
let gpImage;
try {
const gpTile = await retriever.getTile(x, y, zoom);
gpImage = await gpTile.getGeoPackageImage();
// ... use gpImage ...
} finally {
if (gpImage) Canvas.disposeImage(gpImage);
}
Tile reprojection
By default, GeoPackageTileRetriever.getTile() reprojects tiles to web mercator (EPSG:3857). To request tiles in WGS84 (EPSG:4326) instead, use getTileWGS84():
const gpTile = await retriever.getTileWGS84(x, y, zoom);
For arbitrary projections, use getTileWithBounds() and pass a BoundingBox plus a Projection from @ngageoint/projections-js. The TileReprojection class in the library also supports bulk reprojection of tile tables between any two supported projections.