@kibeo/loaders.gl-geopackage
Version:
GeoPackage data loaders
303 lines (257 loc) • 7.12 kB
JavaScript
import initSqlJs from 'sql.js';
import { WKBLoader } from '@kibeo/loaders.gl-wkt';
import { Schema, Field, Bool, Utf8, Float64, Int32, Int8, Int16, Float32, Binary } from '@kibeo/loaders.gl-schema';
import { binaryToGeometry, transformGeoJsonCoords } from '@kibeo/loaders.gl-gis';
import { Proj4Projection } from '@math.gl/proj4';
const ENVELOPE_BYTE_LENGTHS = {
0: 0,
1: 32,
2: 48,
3: 48,
4: 64,
5: 0,
6: 0,
7: 0
};
const SQL_TYPE_MAPPING = {
BOOLEAN: Bool,
TINYINT: Int8,
SMALLINT: Int16,
MEDIUMINT: Int32,
INT: Int32,
INTEGER: Int32,
FLOAT: Float32,
DOUBLE: Float64,
REAL: Float64,
TEXT: Utf8,
BLOB: Binary,
DATE: Utf8,
DATETIME: Utf8,
GEOMETRY: Binary
};
export default async function parseGeoPackage(arrayBuffer, options) {
const {
sqlJsCDN = 'https://sql.js.org/dist/'
} = (options === null || options === void 0 ? void 0 : options.geopackage) || {};
const {
reproject = false,
_targetCrs = 'WGS84'
} = (options === null || options === void 0 ? void 0 : options.gis) || {};
const db = await loadDatabase(arrayBuffer, sqlJsCDN);
const tables = listVectorTables(db);
const projections = getProjections(db);
const result = {};
for (const table of tables) {
const {
table_name: tableName
} = table;
result[tableName] = getVectorTable(db, tableName, projections, {
reproject,
_targetCrs
});
}
return result;
}
async function loadDatabase(arrayBuffer, sqlJsCDN) {
let SQL;
if (sqlJsCDN) {
SQL = await initSqlJs({
locateFile: file => "".concat(sqlJsCDN).concat(file)
});
} else {
SQL = await initSqlJs();
}
return new SQL.Database(new Uint8Array(arrayBuffer));
}
function listVectorTables(db) {
const stmt = db.prepare("SELECT * FROM gpkg_contents WHERE data_type='features';");
const vectorTablesInfo = [];
while (stmt.step()) {
const vectorTableInfo = stmt.getAsObject();
vectorTablesInfo.push(vectorTableInfo);
}
return vectorTablesInfo;
}
function getVectorTable(db, tableName, projections, {
reproject,
_targetCrs
}) {
const dataColumns = getDataColumns(db, tableName);
const geomColumn = getGeometryColumn(db, tableName);
const featureIdColumn = getFeatureIdName(db, tableName);
const {
columns,
values
} = db.exec("SELECT * FROM `".concat(tableName, "`;"))[0];
let projection;
if (reproject) {
const geomColumnProjStr = projections[geomColumn.srs_id];
projection = new Proj4Projection({
from: geomColumnProjStr,
to: _targetCrs
});
}
const geojsonFeatures = [];
for (const row of values) {
const geojsonFeature = constructGeoJsonFeature(columns, row, geomColumn, dataColumns, featureIdColumn);
geojsonFeatures.push(geojsonFeature);
}
const schema = getArrowSchema(db, tableName);
if (projection) {
return {
geojsonFeatures: transformGeoJsonCoords(geojsonFeatures, projection.project),
schema
};
}
return {
geojsonFeatures,
schema
};
}
function getProjections(db) {
const stmt = db.prepare('SELECT * FROM gpkg_spatial_ref_sys;');
const projectionMapping = {};
while (stmt.step()) {
const srsInfo = stmt.getAsObject();
const {
srs_id,
definition
} = srsInfo;
projectionMapping[srs_id] = definition;
}
return projectionMapping;
}
function constructGeoJsonFeature(columns, row, geomColumn, dataColumns, featureIdColumn) {
const idIdx = columns.indexOf(featureIdColumn);
const id = row[idIdx];
const geomColumnIdx = columns.indexOf(geomColumn.column_name);
const geometry = parseGeometry(row[geomColumnIdx].buffer);
const properties = {};
if (dataColumns) {
for (const [key, value] of Object.entries(dataColumns)) {
const idx = columns.indexOf(key);
properties[value] = row[idx];
}
} else {
for (let i = 0; i < columns.length; i++) {
if (i === idIdx || i === geomColumnIdx) {
continue;
}
const columnName = columns[i];
properties[columnName] = row[i];
}
}
return {
id,
type: 'Feature',
geometry,
properties
};
}
function getGeopackageVersion(db) {
const textDecoder = new TextDecoder();
const applicationIdQuery = db.exec('PRAGMA application_id;')[0];
const applicationId = applicationIdQuery.values[0][0];
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, Number(applicationId));
const versionString = textDecoder.decode(buffer);
if (versionString === 'GP10') {
return '1.0';
}
if (versionString === 'GP11') {
return '1.1';
}
const userVersionQuery = db.exec('PRAGMA user_version;')[0];
const userVersionInt = userVersionQuery.values[0][0];
if (userVersionInt && userVersionInt < 10300) {
return '1.2';
}
return null;
}
function getFeatureIdName(db, tableName) {
const stmt = db.prepare("PRAGMA table_info(`".concat(tableName, "`)"));
while (stmt.step()) {
const pragmaTableInfo = stmt.getAsObject();
const {
name,
pk
} = pragmaTableInfo;
if (pk) {
return name;
}
}
return null;
}
function parseGeometry(arrayBuffer) {
const view = new DataView(arrayBuffer);
const {
envelopeLength,
emptyGeometry
} = parseGeometryBitFlags(view.getUint8(3));
if (emptyGeometry) {
return null;
}
const wkbOffset = 8 + envelopeLength;
const binaryGeometry = WKBLoader.parseSync(arrayBuffer.slice(wkbOffset));
return binaryToGeometry(binaryGeometry);
}
function parseGeometryBitFlags(byte) {
const envelopeValue = (byte & 0b00001110) / 2;
const envelopeLength = ENVELOPE_BYTE_LENGTHS[envelopeValue];
return {
littleEndian: Boolean(byte & 0b00000001),
envelopeLength,
emptyGeometry: Boolean(byte & 0b00010000),
extendedGeometryType: Boolean(byte & 0b00100000)
};
}
function getGeometryColumn(db, tableName) {
const stmt = db.prepare('SELECT * FROM gpkg_geometry_columns WHERE table_name=:tableName;');
stmt.bind({
':tableName': tableName
});
stmt.step();
const geometryColumn = stmt.getAsObject();
return geometryColumn;
}
function getDataColumns(db, tableName) {
let stmt;
try {
stmt = db.prepare('SELECT * FROM gpkg_data_columns WHERE table_name=:tableName;');
} catch (error) {
if (error.message.includes('no such table')) {
return null;
}
throw error;
}
stmt.bind({
':tableName': tableName
});
const result = {};
while (stmt.step()) {
const column = stmt.getAsObject();
const {
column_name,
name
} = column;
result[column_name] = name || null;
}
return result;
}
function getArrowSchema(db, tableName) {
const stmt = db.prepare("PRAGMA table_info(`".concat(tableName, "`)"));
const fields = [];
while (stmt.step()) {
const pragmaTableInfo = stmt.getAsObject();
const {
name,
type,
notnull
} = pragmaTableInfo;
const field = new Field(name, new SQL_TYPE_MAPPING[type](), !notnull);
fields.push(field);
}
return new Schema(fields);
}
//# sourceMappingURL=parse-geopackage.js.map