GeoPackage JS is an implementation of the OGC GeoPackage spec. This library works in both the browser and Node 12+.
Cloning this repository and opening the docs/index.html in your browser will run the demo locally.
$ npm install @ngageoint/geopackage
The GeoPackage Libraries were developed at the National Geospatial-Intelligence Agency (NGA) in collaboration with BIT Systems. The government has "unlimited rights" and is releasing this software to increase the impact of government investments by providing developers with the opportunity to take things in new directions. The software use, modification, and distribution rights are stipulated within the MIT license.
If you'd like to contribute to this project, please make a pull request. We'll review the pull request and discuss the changes. All pull request contributions to this project will be released under the MIT license.
Software source code previously released under an open source license and then modified by NGA staff is considered a "joint work" (see 17 USC § 101); it is partially copyrighted, partially public domain, and as a whole is protected by the copyrights of the non-government authors and must be released according to the terms of the original open source license.
GeoPackage JS is a GeoPackage Library JavaScript implementation of the Open Geospatial Consortium GeoPackage spec. It is listed as an OGC GeoPackage Implementation by the National Geospatial-Intelligence Agency.
The GeoPackage JavaScript library currently provides the ability to read GeoPackage files. This library works both in the browser and in Node. In the browser tiles are rendered using HTML5 Canvas and GeoPackages are read using sql.js. In Node tiles are rendered PureImage and GeoPackages are read using node-sqlite3.
View the latest docs.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="/path/to/geopackage.min.js"></script>
</head>
<body>
<input id="fileInput" type="file"/>
<script src="./index.js"></script>
</body>
</html>
// Specify folder containing the sql-wasm.wasm file.
// By default, geopackage loads from https://server/public/sql-wasm.wasm
const {
GeoPackageManager,
Canvas,
TileUtils,
GeoPackageTileRetriever,
FeatureTiles,
FeatureIndexManager,
BoundingBox,
setSqljsWasmLocateFile
} = window.GeoPackage
setSqljsWasmLocateFile(file => 'public/' + file);
// attach an event listener onto a file input
document.getElementById('fileInput').addEventListener('change', function () {
const file =this.files[0];
const fileReader = new FileReader();
fileReader.onload = function() {
loadByteArray(new Uint8Array(fileReader.result));
}
fileReader.readAsArrayBuffer(file);
}, false);
function loadByteArray(array) {
GeoPackageManager.open(array).then(async (geoPackage) => {
// get the tile table names
const tileTables = geoPackage.getTileTables();
for (let i = 0; i < tileTables.length; i++) {
const table = tileTables[i];
// get tile dao
const tileDao = geoPackage.getTileDao(table);
// get table info
const tableInfo = geoPackage.getInfoForTable(tileDao);
// Get a GeoPackageTile and then draw it into a canvas.
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const context = canvas.getContext('2d');
const gpr = new GeoPackageTileRetriever(tileDao);
const x = 0;
const y = 0;
const zoom = 0;
// Get the GeoPackageTile for a particular web mercator tile
const geoPackageTile = await gpr.getTile(x, y, zoom)
// get the tile data as a Buffer
let tileData = geoPackageTile.getData();
// Get the GeoPackageImage from the GeoPackageTile
const geoPackageImage = await geoPackageTile.getGeoPackageImage()
// draw the tile and use the canvas to get the Data URL
context.drawImage(geoPackageImage.getImage(), 0, 0);
const base64String = canvas.toDataURL('image/png');
// In node.js, users must dispose of any GeoPackageImage and Canvas created to prevent memory leaks
Canvas.disposeImage(geoPackageImage);
Canvas.disposeCanvas(canvas);
// Query tile table directly.
const tileRow = tileDao.queryForTile(x, y, zoom);
tileData = tileRow.getTileData(); // the raw bytes from the GeoPackage
}
// get the feature table names
const featureTables = geoPackage.getFeatureTables();
for (let i = 0; i < featureTables.length; i++) {
const table = featureTables[i];
// get the feature dao
const featureDao = geoPackage.getFeatureDao(table);
// get the info for the table
const tableInfo = geoPackage.getInfoForTable(featureDao);
// draw tiles using features
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const context = canvas.getContext('2d');
const ft = new FeatureTiles(geoPackage, featureDao);
var x = 0;
var y = 0;
var zoom = 0;
const geoPackageImage = await ft.drawTile(x, y, zoom);
context.drawImage(geoPackageImage.getImage(), 0, 0);
const base64String = canvas.toDataURL('image/png');
Canvas.disposeImage(geoPackageImage);
Canvas.disposeCanvas(canvas);
// iterate over indexed features that intersect the bounding box
const featureIndexManager = new FeatureIndexManager(geoPackage, table);
const resultSet = featureIndexManager.query();
for (const featureRow of resultSet) {
// ...
}
resultSet.close();
// iterate over all features in a table in the geojson format
const geoJSONResultSet = geoPackage.queryForGeoJSONFeatures(table);
for (const feature of geoJSONResultSet) {
// ...
}
geoJSONResultSet.close();
}
}).catch(function(error) {
console.error(error);
});
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="/path/to/geopackage.min.js"></script>
</head>
<body>
<input id="fileInput" type="file" onchange="openConnection(this.files)"/>
<canvas id="canvas" width="256px" height="256px"></canvas>
<br>
<input id="table" type="text">Table:</input>
<br>
<input id="column" type="number">X:</input>
<br>
<input id="row" type="number">Y:</input>
<br>
<input id="zoom" type="number">Zoom:</input>
<br>
<button onclick="drawFeatureTileInCanvas(document.getElementById('table').value, document.getElementById('column').value, document.getElementById('row').value, document.getElementById('zoom').value)">GetFeatureTile</button>
<br>
<button onclick="drawTileInCanvas(document.getElementById('table').value, document.getElementById('column').value, document.getElementById('row').value, document.getElementById('zoom').value)">DrawTileInCanvas</button>
<br>
<button onclick="getFeatureTableAsGeoJSON(document.getElementById('table').value)">GetFeatureTableAsGeoJSON</button>
<br>
<button onclick="closeConnection()">Close</button>
<br>
<script src="./index.js"></script>
</body>
</html>
// canvas for drawing, will need to be defined in html file.
var canvas = document.getElementById('canvas');
// setup worker
var geopackageWorker;
if (window.Worker) {
geopackageWorker = new Worker("worker.js");
// handle responses from the geopackage web worker
geopackageWorker.onmessage = function(e) {
// draw tile
if (e.data.type === 'tile') {
const ctx = canvas.getContext('2d');
ctx.putImageData(e.data.tileImageData, 0, 0);
// print geojson
} else if (e.data.type === 'geojson') {
console.log(e.data.geojson)
}
}
}
/**
* Attach this method to a file input onchange event to open a geopackage connection
* files[0] should be a valid .gpkg file
* @param files - event.target.files
*/
openConnection = function(files) {
var f = files[0];
var r = new FileReader();
r.onload = function() {
var array = new Uint8Array(r.result);
// create a connection to the geopackage
geopackageWorker.postMessage({
type: 'load',
file: array
});
}
r.readAsArrayBuffer(f);
}
/**
* Closes the geopackage connection inside the worker
*/
closeConnection = function () {
geopackageWorker.postMessage({
type: 'close',
});
}
/**
* Will request the x,y,z feature tile to be drawn
* @param table
* @param x
* @param y
* @param z
*/
drawFeatureTileInCanvas = function (table, x, y, z) {
console.log([table, x, y, z]);
geopackageWorker.postMessage({
type: 'get-feature-tile',
table: table,
x: x,
y: y,
z: z,
width: canvas.width,
height: canvas.height
});
}
/**
* Will request the x,y,z file to be drawn
* @param table
* @param x
* @param y
* @param z
*/
drawTileInCanvas = function (table, x, y, z) {
geopackageWorker.postMessage({
type: 'get-tile',
table: table,
x: x,
y: y,
z: z,
width: canvas.width,
height: canvas.height
});
}
/**
* Will request the features of a table in geojson format
* @param table
*/
getFeatureTableAsGeoJSON = function (table) {
geopackageWorker.postMessage({
type: 'get-geojson',
table: table
});
}
// import the geopackage browser library
self.importScripts('/path/to/geopackage.min.js');
const {
GeoPackageManager,
Canvas,
TileUtils,
GeoPackageTileRetriever,
FeatureTiles,
BoundingBox,
setSqljsWasmLocateFile
} = GeoPackage;
// specify the location of the sql.wasm file
setSqljsWasmLocateFile(file => 'public/' + file);
// message listener
onmessage = function(e) {
// open geopackage connection to fileData provided in message
if (e.data.type === 'load') {
GeoPackageManager.open(e.data.file).then(gp => {
self.gp = gp;
});
// close the geopackage connection
} else if (e.data.type === 'close') {
self.gp.close();
// request a tile from a feature table
} else if (e.data.type === 'get-feature-tile') {
const table = e.data.table;
const x = parseInt(e.data.x);
const y = parseInt(e.data.y);
const z = parseInt(e.data.z);
const width = parseInt(e.data.width);
const height = parseInt(e.data.height);
const featureDao = self.gp.getFeatureDao(table);
const ft = new FeatureTiles(self.gp, featureDao, width, height);
ft.drawTile(x, y, z).then(tile => {
postMessage({
type: 'tile',
tileImageData: tile.getImageData()
});
});
// request a tile from a tile table
} else if (e.data.type === 'get-tile') {
const table = e.data.table;
const x = parseInt(e.data.x);
const y = parseInt(e.data.y);
const z = parseInt(e.data.z);
const width = parseInt(e.data.width);
const height = parseInt(e.data.height);
self.gp.xyzTile(table, x, y, z, width, height).then(gpTile => {
gpTile.getGeoPackageImage().then(gpImage => {
postMessage({
type: 'tile',
tileImageData: gpImage.getImageData()
});
})
})
// request the features from a feature table in geojson format
} else if (e.data.type === 'get-geojson') {
const table = e.data.table;
const features = [];
const featureResultSet = self.gp.queryForGeoJSONFeatures(table);
for(const feature of featureResultSet) {
features.push(feature);
}
featureResultSet.close();
const featureCollection = {
type: 'FeatureCollection',
features: features
}
postMessage({
type: 'geojson',
geojson: featureCollection
});
}
}
const {
setCanvasKitWasmLocateFile,
GeoPackageManager,
GeoPackageTileRetriever,
FeatureTiles,
Canvas,
FeatureIndexManager,
BoundingBox,
TileUtils
} = require('@ngageoint/geopackage');
const { Projections } = require('@ngageoint/projections-js')
const { GeometryType } = require('@ngageoint/simple-features-js')
// specify the CanvasKit WebAssembly module's location.
setCanvasKitWasmLocateFile(file => '/path/to/node_modules/@ngageoint/geopackage/dist/canvaskit/' + file);
// open the .gpkg file
GeoPackageManager.open('/path/to/file.gpkg').then(async (geoPackage) => {
// get the tile table names
const tileTables = geoPackage.getTileTables();
for (let i = 0; i < tileTables.length; i++) {
const table = tileTables[i];
// get tile dao
const tileDao = geoPackage.getTileDao(table);
// get table info
const tableInfo = geoPackage.getInfoForTable(tileDao);
// Get a GeoPackageTile and then draw it into a canvas.
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const context = canvas.getContext('2d');
const gpr = new GeoPackageTileRetriever(tileDao);
const x = 0;
const y = 0;
const zoom = 0;
// Get the GeoPackageTile for a particular web mercator tile
const geoPackageTile = await gpr.getTile(x, y, zoom)
// get the tile data as a Buffer
let tileData = geoPackageTile.getData();
// Get the GeoPackageImage from the GeoPackageTile
const geoPackageImage = await geoPackageTile.getGeoPackageImage()
// draw the tile and use the canvas to get the Data URL
context.drawImage(geoPackageImage.getImage(), 0, 0);
const base64String = canvas.toDataURL('image/png');
// In node.js, users must dispose of any GeoPackageImage and Canvas created to prevent memory leaks
Canvas.disposeImage(geoPackageImage);
Canvas.disposeCanvas(canvas);
// Query tile table directly.
const tileRow = tileDao.queryForTile(x, y, zoom);
tileData = tileRow.getTileData(); // the raw bytes from the GeoPackage
}
// get the feature table names
const featureTables = geoPackage.getFeatureTables();
for (let i = 0; i < featureTables.length; i++) {
const table = featureTables[i];
// get the feature dao
const featureDao = geoPackage.getFeatureDao(table);
// get the info for the table
const tableInfo = geoPackage.getInfoForTable(featureDao);
// draw tiles using features
const canvas = Canvas.create(TileUtils.TILE_PIXELS_DEFAULT, TileUtils.TILE_PIXELS_DEFAULT);
const context = canvas.getContext('2d');
const ft = new FeatureTiles(geoPackage, featureDao);
var x = 0;
var y = 0;
var zoom = 0;
const geoPackageImage = await ft.drawTile(x, y, zoom);
context.drawImage(geoPackageImage.getImage(), 0, 0);
// canvas.toDataURL('image/png'));
Canvas.disposeImage(geoPackageImage);
Canvas.disposeCanvas(canvas);
// iterate over indexed features that intersect the bounding box
const featureIndexManager = new FeatureIndexManager(geoPackage, table);
const resultSet = featureIndexManager.queryWithBoundingBoxAndProjection(new BoundingBox(0, -90, 180, 90), Projections.getWGS84Projection());
for (const featureRow of resultSet) {
// ...
}
resultSet.close();
// iterate over all features in a table in the geojson format
const geoJSONResultSet = geoPackage.queryForGeoJSONFeatures(table);
for (const feature of geoJSONResultSet) {
// ...
}
geoJSONResultSet.close();
}
});