A fully working demo is available at the GeoPackage Viewer, which is built on top of this library.
Prerequisites
npm install leaflet @ngageoint/geopackage
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
Add a GeoPackage tile layer
Leaflet’sL.TileLayer fetches tiles by calling a URL template. To serve tiles from a GeoPackage, create a custom tile layer that calls GeoPackageTileRetriever.getTile() for each tile request.
import L from 'leaflet';
import {
GeoPackageManager,
GeoPackageTileRetriever,
setSqljsWasmLocateFile,
} from '@ngageoint/geopackage';
sqljsWasmLocateFile(file => 'public/' + file);
const map = L.map('map').setView([0, 0], 2);
// Open the GeoPackage
const geoPackage = await GeoPackageManager.open(gpkgByteArray);
const tileDao = geoPackage.getTileDao('my_tiles');
const retriever = new GeoPackageTileRetriever(tileDao, 256, 256);
// Custom GridLayer that serves tiles from the GeoPackage
const GeoPackageLayer = L.GridLayer.extend({
createTile(coords, done) {
const tile = document.createElement('canvas');
tile.width = 256;
tile.height = 256;
retriever.getTile(coords.x, coords.y, coords.z).then(gpTile => {
if (!gpTile) {
done(null, tile); // empty tile
return;
}
gpTile.getGeoPackageImage().then(gpImage => {
const ctx = tile.getContext('2d');
ctx.drawImage(gpImage.getImage(), 0, 0);
done(null, tile);
});
}).catch(err => done(err));
return tile;
},
});
new GeoPackageLayer().addTo(map);
In the browser the canvas adapter is backed by the native HTML5 Canvas API, so there is no need to call
Canvas.disposeImage() or Canvas.disposeCanvas() — only do that in Node.js.Add features as a GeoJSON layer
UsegeoPackage.queryForGeoJSONFeatures() to read all features and pass the resulting FeatureCollection to L.geoJSON().
// Collect all features from the table
const featureResultSet = geoPackage.queryForGeoJSONFeatures('my_features');
const features = [];
try {
for (const feature of featureResultSet) {
features.push(feature);
}
} finally {
featureResultSet.close();
}
// Add as a Leaflet GeoJSON layer
const featureLayer = L.geoJSON(
{ type: 'FeatureCollection', features },
{
style: feature => ({
color: '#0055ff',
weight: 2,
opacity: 0.8,
}),
onEachFeature: (feature, layer) => {
if (feature.properties) {
layer.bindPopup(
Object.entries(feature.properties)
.map(([k, v]) => `<b>${k}</b>: ${v}`)
.join('<br>'),
);
}
},
},
).addTo(map);
// Zoom the map to the features
if (featureLayer.getLayers().length) {
map.fitBounds(featureLayer.getBounds());
}
Spatial filtering before adding to the map
For large feature tables, filter to the current map viewport before loading features to avoid transferring unnecessary data:import { BoundingBox } from '@ngageoint/geopackage';
map.on('moveend', () => {
const bounds = map.getBounds();
const bbox = new BoundingBox(
bounds.getWest(),
bounds.getSouth(),
bounds.getEast(),
bounds.getNorth(),
);
const resultSet = geoPackage.queryForGeoJSONFeatures('my_features', bbox);
const features = [];
try {
for (const feature of resultSet) {
features.push(feature);
}
} finally {
resultSet.close();
}
featureLayer.clearLayers();
featureLayer.addData({ type: 'FeatureCollection', features });
});
Complete example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>GeoPackage + Leaflet</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="/path/to/geopackage.min.js"></script>
<style>
#map { height: 100vh; }
</style>
</head>
<body>
<input id="fileInput" type="file" accept=".gpkg" />
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const {
GeoPackageManager,
GeoPackageTileRetriever,
setSqljsWasmLocateFile,
BoundingBox,
} = window.GeoPackage;
setSqljsWasmLocateFile(file => 'public/' + file);
const map = L.map('map').setView([20, 0], 2);
// Add an OpenStreetMap basemap
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(map);
document.getElementById('fileInput').addEventListener('change', function () {
const reader = new FileReader();
reader.onload = async function () {
const array = new Uint8Array(reader.result);
const geoPackage = await GeoPackageManager.open(array);
// Add tile layers
for (const tableName of geoPackage.getTileTables()) {
const tileDao = geoPackage.getTileDao(tableName);
const retriever = new GeoPackageTileRetriever(tileDao, 256, 256);
L.GridLayer.extend({
createTile(coords, done) {
const tile = document.createElement('canvas');
tile.width = tile.height = 256;
retriever.getTile(coords.x, coords.y, coords.z).then(gpTile => {
if (!gpTile) return done(null, tile);
gpTile.getGeoPackageImage().then(img => {
tile.getContext('2d').drawImage(img.getImage(), 0, 0);
done(null, tile);
});
}).catch(err => done(err));
return tile;
},
}).extend({})().addTo(map);
}
// Add feature layers as GeoJSON
for (const tableName of geoPackage.getFeatureTables()) {
const resultSet = geoPackage.queryForGeoJSONFeatures(tableName);
const features = [];
try {
for (const f of resultSet) features.push(f);
} finally {
resultSet.close();
}
L.geoJSON({ type: 'FeatureCollection', features }).addTo(map);
}
};
reader.readAsArrayBuffer(this.files[0]);
});
</script>
</body>
</html>