@loaders.gl/geopackage 3.1.3 → 4.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,361 +1,303 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const sql_js_1 = __importDefault(require("sql.js"));
7
- const wkt_1 = require("@loaders.gl/wkt");
8
- const schema_1 = require("@loaders.gl/schema");
9
- const gis_1 = require("@loaders.gl/gis");
10
- const proj4_1 = require("@math.gl/proj4");
11
- // https://www.geopackage.org/spec121/#flags_layout
1
+ import initSqlJs from 'sql.js';
2
+ import { WKBLoader } from '@loaders.gl/wkt';
3
+ import { Schema, Field, Bool, Utf8, Float64, Int32, Int8, Int16, Float32, Binary } from '@loaders.gl/schema';
4
+ import { binaryToGeometry, transformGeoJsonCoords } from '@loaders.gl/gis';
5
+ import { Proj4Projection } from '@math.gl/proj4';
12
6
  const ENVELOPE_BYTE_LENGTHS = {
13
- 0: 0,
14
- 1: 32,
15
- 2: 48,
16
- 3: 48,
17
- 4: 64,
18
- // values 5-7 are invalid and _should_ never show up
19
- 5: 0,
20
- 6: 0,
21
- 7: 0
7
+ 0: 0,
8
+ 1: 32,
9
+ 2: 48,
10
+ 3: 48,
11
+ 4: 64,
12
+ 5: 0,
13
+ 6: 0,
14
+ 7: 0
22
15
  };
23
- // Documentation: https://www.geopackage.org/spec130/index.html#table_column_data_types
24
16
  const SQL_TYPE_MAPPING = {
25
- BOOLEAN: schema_1.Bool,
26
- TINYINT: schema_1.Int8,
27
- SMALLINT: schema_1.Int16,
28
- MEDIUMINT: schema_1.Int32,
29
- INT: schema_1.Int32,
30
- INTEGER: schema_1.Int32,
31
- FLOAT: schema_1.Float32,
32
- DOUBLE: schema_1.Float64,
33
- REAL: schema_1.Float64,
34
- TEXT: schema_1.Utf8,
35
- BLOB: schema_1.Binary,
36
- DATE: schema_1.Utf8,
37
- DATETIME: schema_1.Utf8,
38
- GEOMETRY: schema_1.Binary
17
+ BOOLEAN: Bool,
18
+ TINYINT: Int8,
19
+ SMALLINT: Int16,
20
+ MEDIUMINT: Int32,
21
+ INT: Int32,
22
+ INTEGER: Int32,
23
+ FLOAT: Float32,
24
+ DOUBLE: Float64,
25
+ REAL: Float64,
26
+ TEXT: Utf8,
27
+ BLOB: Binary,
28
+ DATE: Utf8,
29
+ DATETIME: Utf8,
30
+ GEOMETRY: Binary
39
31
  };
40
- async function parseGeoPackage(arrayBuffer, options) {
41
- const { sqlJsCDN = 'https://sql.js.org/dist/' } = options?.geopackage || {};
42
- const { reproject = false, _targetCrs = 'WGS84' } = options?.gis || {};
43
- const db = await loadDatabase(arrayBuffer, sqlJsCDN);
44
- const tables = listVectorTables(db);
45
- const projections = getProjections(db);
46
- // Mapping from tableName to geojson feature collection
47
- const result = {};
48
- for (const table of tables) {
49
- const { table_name: tableName } = table;
50
- result[tableName] = getVectorTable(db, tableName, projections, {
51
- reproject,
52
- _targetCrs
53
- });
54
- }
55
- return result;
32
+ export default async function parseGeoPackage(arrayBuffer, options) {
33
+ const {
34
+ sqlJsCDN = 'https://sql.js.org/dist/'
35
+ } = (options === null || options === void 0 ? void 0 : options.geopackage) || {};
36
+ const {
37
+ reproject = false,
38
+ _targetCrs = 'WGS84'
39
+ } = (options === null || options === void 0 ? void 0 : options.gis) || {};
40
+ const db = await loadDatabase(arrayBuffer, sqlJsCDN);
41
+ const tables = listVectorTables(db);
42
+ const projections = getProjections(db);
43
+ const result = {};
44
+
45
+ for (const table of tables) {
46
+ const {
47
+ table_name: tableName
48
+ } = table;
49
+ result[tableName] = getVectorTable(db, tableName, projections, {
50
+ reproject,
51
+ _targetCrs
52
+ });
53
+ }
54
+
55
+ return result;
56
56
  }
57
- exports.default = parseGeoPackage;
58
- /**
59
- * Initialize SQL.js and create database
60
- *
61
- * @param arrayBuffer input bytes
62
- * @return SQL.js database object
63
- */
57
+
64
58
  async function loadDatabase(arrayBuffer, sqlJsCDN) {
65
- // In Node, `locateFile` must not be passed
66
- let SQL;
67
- if (sqlJsCDN) {
68
- SQL = await (0, sql_js_1.default)({
69
- locateFile: (file) => `${sqlJsCDN}${file}`
70
- });
71
- }
72
- else {
73
- SQL = await (0, sql_js_1.default)();
74
- }
75
- return new SQL.Database(new Uint8Array(arrayBuffer));
59
+ let SQL;
60
+
61
+ if (sqlJsCDN) {
62
+ SQL = await initSqlJs({
63
+ locateFile: file => "".concat(sqlJsCDN).concat(file)
64
+ });
65
+ } else {
66
+ SQL = await initSqlJs();
67
+ }
68
+
69
+ return new SQL.Database(new Uint8Array(arrayBuffer));
76
70
  }
77
- /**
78
- * Find all vector tables in GeoPackage
79
- * This queries the `gpkg_contents` table to find a list of vector tables
80
- *
81
- * @param db GeoPackage to query
82
- * @return list of table references
83
- */
71
+
84
72
  function listVectorTables(db) {
85
- // The gpkg_contents table can have at least three categorical values for
86
- // data_type.
87
- // - 'features' refers to a vector geometry table
88
- // (https://www.geopackage.org/spec121/#_contents_2)
89
- // - 'tiles' refers to a raster table
90
- // (https://www.geopackage.org/spec121/#_contents_3)
91
- // - 'attributes' refers to a data table with no geometry
92
- // (https://www.geopackage.org/spec121/#_contents_4).
93
- // We hard code 'features' because for now we don't support raster data or pure attribute data
94
- // eslint-disable-next-line quotes
95
- const stmt = db.prepare("SELECT * FROM gpkg_contents WHERE data_type='features';");
96
- const vectorTablesInfo = [];
97
- while (stmt.step()) {
98
- const vectorTableInfo = stmt.getAsObject();
99
- vectorTablesInfo.push(vectorTableInfo);
100
- }
101
- return vectorTablesInfo;
73
+ const stmt = db.prepare("SELECT * FROM gpkg_contents WHERE data_type='features';");
74
+ const vectorTablesInfo = [];
75
+
76
+ while (stmt.step()) {
77
+ const vectorTableInfo = stmt.getAsObject();
78
+ vectorTablesInfo.push(vectorTableInfo);
79
+ }
80
+
81
+ return vectorTablesInfo;
102
82
  }
103
- /**
104
- * Load geometries from vector table
105
- *
106
- * @param db GeoPackage object
107
- * @param tableName name of vector table to query
108
- * @param projections keys are srs_id values, values are WKT strings
109
- * @returns Array of GeoJSON Feature objects
110
- */
111
- function getVectorTable(db, tableName, projections, { reproject, _targetCrs }) {
112
- const dataColumns = getDataColumns(db, tableName);
113
- const geomColumn = getGeometryColumn(db, tableName);
114
- const featureIdColumn = getFeatureIdName(db, tableName);
115
- // Get vector features from table
116
- // Don't think it's possible to parameterize the table name in SQLite?
117
- const { columns, values } = db.exec(`SELECT * FROM \`${tableName}\`;`)[0];
118
- let projection;
119
- if (reproject) {
120
- const geomColumnProjStr = projections[geomColumn.srs_id];
121
- projection = new proj4_1.Proj4Projection({
122
- from: geomColumnProjStr,
123
- to: _targetCrs
124
- });
125
- }
126
- const geojsonFeatures = [];
127
- for (const row of values) {
128
- const geojsonFeature = constructGeoJsonFeature(columns, row, geomColumn,
129
- // @ts-ignore
130
- dataColumns, featureIdColumn);
131
- geojsonFeatures.push(geojsonFeature);
132
- }
133
- const schema = getArrowSchema(db, tableName);
134
- if (projection) {
135
- return { geojsonFeatures: (0, gis_1.transformGeoJsonCoords)(geojsonFeatures, projection.project), schema };
136
- }
137
- return { geojsonFeatures, schema };
83
+
84
+ function getVectorTable(db, tableName, projections, {
85
+ reproject,
86
+ _targetCrs
87
+ }) {
88
+ const dataColumns = getDataColumns(db, tableName);
89
+ const geomColumn = getGeometryColumn(db, tableName);
90
+ const featureIdColumn = getFeatureIdName(db, tableName);
91
+ const {
92
+ columns,
93
+ values
94
+ } = db.exec("SELECT * FROM `".concat(tableName, "`;"))[0];
95
+ let projection;
96
+
97
+ if (reproject) {
98
+ const geomColumnProjStr = projections[geomColumn.srs_id];
99
+ projection = new Proj4Projection({
100
+ from: geomColumnProjStr,
101
+ to: _targetCrs
102
+ });
103
+ }
104
+
105
+ const geojsonFeatures = [];
106
+
107
+ for (const row of values) {
108
+ const geojsonFeature = constructGeoJsonFeature(columns, row, geomColumn, dataColumns, featureIdColumn);
109
+ geojsonFeatures.push(geojsonFeature);
110
+ }
111
+
112
+ const schema = getArrowSchema(db, tableName);
113
+
114
+ if (projection) {
115
+ return {
116
+ geojsonFeatures: transformGeoJsonCoords(geojsonFeatures, projection.project),
117
+ schema
118
+ };
119
+ }
120
+
121
+ return {
122
+ geojsonFeatures,
123
+ schema
124
+ };
138
125
  }
139
- /**
140
- * Find all projections defined in GeoPackage
141
- * This queries the gpkg_spatial_ref_sys table
142
- * @param db GeoPackage object
143
- * @returns mapping from srid to WKT projection string
144
- */
126
+
145
127
  function getProjections(db) {
146
- // Query gpkg_spatial_ref_sys to get srid: srtext mappings
147
- const stmt = db.prepare('SELECT * FROM gpkg_spatial_ref_sys;');
148
- const projectionMapping = {};
149
- while (stmt.step()) {
150
- const srsInfo = stmt.getAsObject();
151
- const { srs_id, definition } = srsInfo;
152
- projectionMapping[srs_id] = definition;
153
- }
154
- return projectionMapping;
128
+ const stmt = db.prepare('SELECT * FROM gpkg_spatial_ref_sys;');
129
+ const projectionMapping = {};
130
+
131
+ while (stmt.step()) {
132
+ const srsInfo = stmt.getAsObject();
133
+ const {
134
+ srs_id,
135
+ definition
136
+ } = srsInfo;
137
+ projectionMapping[srs_id] = definition;
138
+ }
139
+
140
+ return projectionMapping;
155
141
  }
156
- /**
157
- * Construct single GeoJSON feature given row's data
158
- * @param columns array of ordered column identifiers
159
- * @param row array of ordered values representing row's data
160
- * @param geomColumn geometry column metadata
161
- * @param dataColumns mapping from table column names to property name
162
- * @returns GeoJSON Feature object
163
- */
142
+
164
143
  function constructGeoJsonFeature(columns, row, geomColumn, dataColumns, featureIdColumn) {
165
- // Find feature id
166
- const idIdx = columns.indexOf(featureIdColumn);
167
- const id = row[idIdx];
168
- // Parse geometry columns to geojson
169
- const geomColumnIdx = columns.indexOf(geomColumn.column_name);
170
- const geometry = parseGeometry(row[geomColumnIdx].buffer);
171
- const properties = {};
172
- if (dataColumns) {
173
- for (const [key, value] of Object.entries(dataColumns)) {
174
- const idx = columns.indexOf(key);
175
- // @ts-ignore TODO - Check what happens if null?
176
- properties[value] = row[idx];
177
- }
144
+ const idIdx = columns.indexOf(featureIdColumn);
145
+ const id = row[idIdx];
146
+ const geomColumnIdx = columns.indexOf(geomColumn.column_name);
147
+ const geometry = parseGeometry(row[geomColumnIdx].buffer);
148
+ const properties = {};
149
+
150
+ if (dataColumns) {
151
+ for (const [key, value] of Object.entries(dataColumns)) {
152
+ const idx = columns.indexOf(key);
153
+ properties[value] = row[idx];
178
154
  }
179
- else {
180
- // Put all columns except for the feature id and geometry in properties
181
- for (let i = 0; i < columns.length; i++) {
182
- if (i === idIdx || i === geomColumnIdx) {
183
- // eslint-disable-next-line no-continue
184
- continue;
185
- }
186
- const columnName = columns[i];
187
- properties[columnName] = row[i];
188
- }
155
+ } else {
156
+ for (let i = 0; i < columns.length; i++) {
157
+ if (i === idIdx || i === geomColumnIdx) {
158
+ continue;
159
+ }
160
+
161
+ const columnName = columns[i];
162
+ properties[columnName] = row[i];
189
163
  }
190
- return {
191
- id,
192
- type: 'Feature',
193
- geometry,
194
- properties
195
- };
164
+ }
165
+
166
+ return {
167
+ id,
168
+ type: 'Feature',
169
+ geometry,
170
+ properties
171
+ };
196
172
  }
197
- /**
198
- * Get GeoPackage version from database
199
- * @param db database
200
- * @returns version string. One of '1.0', '1.1', '1.2'
201
- */
202
- // @ts-ignore
203
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
173
+
204
174
  function getGeopackageVersion(db) {
205
- const textDecoder = new TextDecoder();
206
- // Read application id from SQLite metadata
207
- const applicationIdQuery = db.exec('PRAGMA application_id;')[0];
208
- const applicationId = applicationIdQuery.values[0][0];
209
- // Convert 4-byte signed int32 application id to text
210
- const buffer = new ArrayBuffer(4);
211
- const view = new DataView(buffer);
212
- view.setInt32(0, Number(applicationId));
213
- const versionString = textDecoder.decode(buffer);
214
- if (versionString === 'GP10') {
215
- return '1.0';
216
- }
217
- if (versionString === 'GP11') {
218
- return '1.1';
219
- }
220
- // If versionString is GPKG, then read user_version
221
- const userVersionQuery = db.exec('PRAGMA user_version;')[0];
222
- const userVersionInt = userVersionQuery.values[0][0];
223
- if (userVersionInt && userVersionInt < 10300) {
224
- return '1.2';
225
- }
226
- return null;
175
+ const textDecoder = new TextDecoder();
176
+ const applicationIdQuery = db.exec('PRAGMA application_id;')[0];
177
+ const applicationId = applicationIdQuery.values[0][0];
178
+ const buffer = new ArrayBuffer(4);
179
+ const view = new DataView(buffer);
180
+ view.setInt32(0, Number(applicationId));
181
+ const versionString = textDecoder.decode(buffer);
182
+
183
+ if (versionString === 'GP10') {
184
+ return '1.0';
185
+ }
186
+
187
+ if (versionString === 'GP11') {
188
+ return '1.1';
189
+ }
190
+
191
+ const userVersionQuery = db.exec('PRAGMA user_version;')[0];
192
+ const userVersionInt = userVersionQuery.values[0][0];
193
+
194
+ if (userVersionInt && userVersionInt < 10300) {
195
+ return '1.2';
196
+ }
197
+
198
+ return null;
227
199
  }
228
- /**
229
- * Find name of feature id column in table
230
- * The feature ID is the primary key of the table.
231
- * http://www.geopackage.org/spec121/#feature_user_tables
232
- *
233
- * @param db database
234
- * @param tableName name of table
235
- * @return name of feature id column
236
- */
200
+
237
201
  function getFeatureIdName(db, tableName) {
238
- // Again, not possible to parameterize table name?
239
- const stmt = db.prepare(`PRAGMA table_info(\`${tableName}\`)`);
240
- while (stmt.step()) {
241
- const pragmaTableInfo = stmt.getAsObject();
242
- const { name, pk } = pragmaTableInfo;
243
- if (pk) {
244
- return name;
245
- }
202
+ const stmt = db.prepare("PRAGMA table_info(`".concat(tableName, "`)"));
203
+
204
+ while (stmt.step()) {
205
+ const pragmaTableInfo = stmt.getAsObject();
206
+ const {
207
+ name,
208
+ pk
209
+ } = pragmaTableInfo;
210
+
211
+ if (pk) {
212
+ return name;
246
213
  }
247
- // Is it guaranteed for there always to be at least one primary key column in the table?
248
- return null;
214
+ }
215
+
216
+ return null;
249
217
  }
250
- /**
251
- * Parse geometry buffer
252
- * GeoPackage vector geometries are slightly extended past the WKB standard
253
- * See: https://www.geopackage.org/spec121/#gpb_format
254
- *
255
- * @param arrayBuffer geometry buffer
256
- * @return {object} GeoJSON geometry (in original CRS)
257
- */
218
+
258
219
  function parseGeometry(arrayBuffer) {
259
- const view = new DataView(arrayBuffer);
260
- const { envelopeLength, emptyGeometry } = parseGeometryBitFlags(view.getUint8(3));
261
- // A Feature object has a member with the name "geometry". The value of the
262
- // geometry member SHALL be either a Geometry object as defined above or, in
263
- // the case that the Feature is unlocated, a JSON null value.
264
- /** @see https://tools.ietf.org/html/rfc7946#section-3.2 */
265
- if (emptyGeometry) {
266
- return null;
267
- }
268
- // Do I need to find the srid here? Is it necessarily the same for every
269
- // geometry in a table?
270
- // const srid = view.getInt32(4, littleEndian);
271
- // 2 byte magic, 1 byte version, 1 byte flags, 4 byte int32 srid
272
- const wkbOffset = 8 + envelopeLength;
273
- // Loaders should not depend on `core` and the context passed to the main loader doesn't include a
274
- // `parseSync` option, so instead we call parseSync directly on WKBLoader
275
- const binaryGeometry = wkt_1.WKBLoader.parseSync(arrayBuffer.slice(wkbOffset));
276
- return (0, gis_1.binaryToGeometry)(binaryGeometry);
220
+ const view = new DataView(arrayBuffer);
221
+ const {
222
+ envelopeLength,
223
+ emptyGeometry
224
+ } = parseGeometryBitFlags(view.getUint8(3));
225
+
226
+ if (emptyGeometry) {
227
+ return null;
228
+ }
229
+
230
+ const wkbOffset = 8 + envelopeLength;
231
+ const binaryGeometry = WKBLoader.parseSync(arrayBuffer.slice(wkbOffset));
232
+ return binaryToGeometry(binaryGeometry);
277
233
  }
278
- /**
279
- * Parse geometry header flags
280
- * https://www.geopackage.org/spec121/#flags_layout
281
- *
282
- * @param byte uint8 number representing flags
283
- * @return object representing information from bit flags
284
- */
234
+
285
235
  function parseGeometryBitFlags(byte) {
286
- // Are header values little endian?
287
- const envelopeValue = (byte & 0b00001110) / 2;
288
- // TODO: Not sure the best way to handle this. Throw an error if envelopeValue outside 0-7?
289
- const envelopeLength = ENVELOPE_BYTE_LENGTHS[envelopeValue];
290
- return {
291
- littleEndian: Boolean(byte & 0b00000001),
292
- envelopeLength,
293
- emptyGeometry: Boolean(byte & 0b00010000),
294
- extendedGeometryType: Boolean(byte & 0b00100000)
295
- };
236
+ const envelopeValue = (byte & 0b00001110) / 2;
237
+ const envelopeLength = ENVELOPE_BYTE_LENGTHS[envelopeValue];
238
+ return {
239
+ littleEndian: Boolean(byte & 0b00000001),
240
+ envelopeLength,
241
+ emptyGeometry: Boolean(byte & 0b00010000),
242
+ extendedGeometryType: Boolean(byte & 0b00100000)
243
+ };
296
244
  }
297
- /**
298
- * Find geometry column in given vector table
299
- *
300
- * @param db GeoPackage object
301
- * @param tableName Name of vector table
302
- * @returns Array of geometry column definitions
303
- */
245
+
304
246
  function getGeometryColumn(db, tableName) {
305
- const stmt = db.prepare('SELECT * FROM gpkg_geometry_columns WHERE table_name=:tableName;');
306
- stmt.bind({ ':tableName': tableName });
307
- // > Requirement 30
308
- // > A feature table SHALL have only one geometry column.
309
- // https://www.geopackage.org/spec121/#feature_user_tables
310
- // So we should need one and only one step, given that we use the WHERE clause in the SQL query
311
- // above
312
- stmt.step();
313
- const geometryColumn = stmt.getAsObject();
314
- return geometryColumn;
247
+ const stmt = db.prepare('SELECT * FROM gpkg_geometry_columns WHERE table_name=:tableName;');
248
+ stmt.bind({
249
+ ':tableName': tableName
250
+ });
251
+ stmt.step();
252
+ const geometryColumn = stmt.getAsObject();
253
+ return geometryColumn;
315
254
  }
316
- /**
317
- * Find property columns in given vector table
318
- * @param db GeoPackage object
319
- * @param tableName Name of vector table
320
- * @returns Mapping from table column names to property name
321
- */
255
+
322
256
  function getDataColumns(db, tableName) {
323
- // gpkg_data_columns is not required to exist
324
- // https://www.geopackage.org/spec121/#extension_schema
325
- let stmt;
326
- try {
327
- stmt = db.prepare('SELECT * FROM gpkg_data_columns WHERE table_name=:tableName;');
257
+ let stmt;
258
+
259
+ try {
260
+ stmt = db.prepare('SELECT * FROM gpkg_data_columns WHERE table_name=:tableName;');
261
+ } catch (error) {
262
+ if (error.message.includes('no such table')) {
263
+ return null;
328
264
  }
329
- catch (error) {
330
- if (error.message.includes('no such table')) {
331
- return null;
332
- }
333
- throw error;
334
- }
335
- stmt.bind({ ':tableName': tableName });
336
- // Convert DataColumnsRow object this to a key-value {column_name: name}
337
- const result = {};
338
- while (stmt.step()) {
339
- const column = stmt.getAsObject();
340
- const { column_name, name } = column;
341
- result[column_name] = name || null;
342
- }
343
- return result;
265
+
266
+ throw error;
267
+ }
268
+
269
+ stmt.bind({
270
+ ':tableName': tableName
271
+ });
272
+ const result = {};
273
+
274
+ while (stmt.step()) {
275
+ const column = stmt.getAsObject();
276
+ const {
277
+ column_name,
278
+ name
279
+ } = column;
280
+ result[column_name] = name || null;
281
+ }
282
+
283
+ return result;
344
284
  }
345
- /**
346
- * Get arrow schema
347
- * @param db GeoPackage object
348
- * @param tableName table name
349
- * @returns Arrow-like Schema
350
- */
285
+
351
286
  function getArrowSchema(db, tableName) {
352
- const stmt = db.prepare(`PRAGMA table_info(\`${tableName}\`)`);
353
- const fields = [];
354
- while (stmt.step()) {
355
- const pragmaTableInfo = stmt.getAsObject();
356
- const { name, type, notnull } = pragmaTableInfo;
357
- const field = new schema_1.Field(name, new SQL_TYPE_MAPPING[type](), !notnull);
358
- fields.push(field);
359
- }
360
- return new schema_1.Schema(fields);
287
+ const stmt = db.prepare("PRAGMA table_info(`".concat(tableName, "`)"));
288
+ const fields = [];
289
+
290
+ while (stmt.step()) {
291
+ const pragmaTableInfo = stmt.getAsObject();
292
+ const {
293
+ name,
294
+ type,
295
+ notnull
296
+ } = pragmaTableInfo;
297
+ const field = new Field(name, new SQL_TYPE_MAPPING[type](), !notnull);
298
+ fields.push(field);
299
+ }
300
+
301
+ return new Schema(fields);
361
302
  }
303
+ //# sourceMappingURL=parse-geopackage.js.map