UNPKG

@ngageoint/geopackage

Version:
424 lines 19.5 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FeatureTableIndex = void 0; var rtreeIndex_1 = require("../rtree/rtreeIndex"); var baseExtension_1 = require("../baseExtension"); var extension_1 = require("../extension"); var tableIndex_1 = require("./tableIndex"); var geometryIndexDao_1 = require("./geometryIndexDao"); var rtreeIndexDao_1 = require("../rtree/rtreeIndexDao"); var envelopeBuilder_1 = require("../../geom/envelopeBuilder"); /** * This class will either use the RTree index if it exists, or the * Feature Table Index NGA Extension implementation. This extension is used to * index Geometries within a feature table by their minimum bounding box for * bounding box queries. * @class * @extends BaseExtension */ var FeatureTableIndex = /** @class */ (function (_super) { __extends(FeatureTableIndex, _super); /** * * @param geoPackage GeoPackage object * @param featureDao FeatureDao to index */ function FeatureTableIndex(geoPackage, featureDao) { var _this = _super.call(this, geoPackage) || this; _this.featureDao = featureDao; _this.extensionName = FeatureTableIndex.EXTENSION_NAME; _this.extensionDefinition = FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_DEFINITION; _this.tableName = featureDao.table_name; _this.columnName = featureDao.getGeometryColumnName(); _this.tableIndexDao = geoPackage.tableIndexDao; _this.geometryIndexDao = geoPackage.getGeometryIndexDao(featureDao); _this.rtreeIndexDao = new rtreeIndexDao_1.RTreeIndexDao(geoPackage, featureDao); _this.rtreeIndexDao.gpkgTableName = 'rtree_' + _this.tableName + '_' + _this.columnName; _this.rtreeIndex = new rtreeIndex_1.RTreeIndex(geoPackage, featureDao); /** * true if the table is indexed with an RTree * @type {Boolean} */ _this.rtreeIndexed = _this.hasExtension('gpkg_rtree_index', _this.tableName, _this.columnName); return _this; } /** * Index the table if not already indexed * @param {Function} progress function which is called with progress while indexing * @return {Promise<Boolean>} promise resolved when the indexing is complete */ FeatureTableIndex.prototype.index = function (progress) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.indexWithForce(false, progress)]; }); }); }; /** * Index the table if not already indexed or force is true * @param {Boolean} force force index even if the table is already indexed * @param {Function} progress function which is called with progress while indexing * @return {Promise<Boolean>} promise resolved when the indexing is complete */ FeatureTableIndex.prototype.indexWithForce = function (force, progress) { return __awaiter(this, void 0, void 0, function () { var indexed, rtreeIndex, tableIndex; return __generator(this, function (_a) { // eslint-disable-next-line @typescript-eslint/no-empty-function progress = progress || function () { }; this.progress = function (message) { setTimeout(progress, 0, message); }; indexed = this.isIndexed(); if (!indexed || force) { rtreeIndex = new rtreeIndex_1.RTreeIndex(this.geoPackage, this.featureDao); rtreeIndex.create(); this.rtreeIndexed = rtreeIndex.hasExtension(rtreeIndex.extensionName, rtreeIndex.tableName, rtreeIndex.columnName); indexed = this.isIndexed(); } if (!indexed) { this.getOrCreateExtension(); tableIndex = this.getOrCreateTableIndex(); this.createOrClearGeometryIndicies(); return [2 /*return*/, this.indexTable(tableIndex)]; } else { return [2 /*return*/, indexed]; } return [2 /*return*/]; }); }); }; /** * Index the table using the NGA index and Rtree if not already indexed or force is true * @param {Boolean} force force index even if the table is already indexed * @param {Function} progress function which is called with progress while indexing * @return {Promise<Boolean>} promise resolved when the indexing is complete */ FeatureTableIndex.prototype.ngaIndexWithForce = function (force, progress) { return __awaiter(this, void 0, void 0, function () { var indexed, rtreeIndex, tableIndex; return __generator(this, function (_a) { switch (_a.label) { case 0: // eslint-disable-next-line @typescript-eslint/no-empty-function progress = progress || function () { }; this.progress = function (message) { setTimeout(progress, 0, message); }; indexed = this.isIndexed(true); if (!(!indexed || force)) return [3 /*break*/, 2]; rtreeIndex = new rtreeIndex_1.RTreeIndex(this.geoPackage, this.featureDao); return [4 /*yield*/, rtreeIndex.create()]; case 1: _a.sent(); _a.label = 2; case 2: if (!(!indexed || force)) return [3 /*break*/, 5]; this.getOrCreateExtension(); return [4 /*yield*/, this.getOrCreateTableIndex()]; case 3: tableIndex = _a.sent(); return [4 /*yield*/, this.createOrClearGeometryIndicies()]; case 4: _a.sent(); return [2 /*return*/, this.indexTable(tableIndex)]; case 5: return [2 /*return*/, indexed]; } }); }); }; /** * Check if the table is indexed either with an RTree or the NGA Feature Table Index * @return {Boolean} */ FeatureTableIndex.prototype.isIndexed = function (checkOnlyNGA) { if (checkOnlyNGA === void 0) { checkOnlyNGA = false; } if (!checkOnlyNGA) { var rtreeIndex = new rtreeIndex_1.RTreeIndex(this.geoPackage, this.featureDao); this.rtreeIndexed = rtreeIndex.hasExtension(rtreeIndex.extensionName, rtreeIndex.tableName, rtreeIndex.columnName); if (this.rtreeIndexed) { return true; } } try { var result = this.getFeatureTableIndexExtension(); if (result) { var contentsDao = this.geoPackage.contentsDao; var contents = contentsDao.queryForId(this.tableName); if (!contents) return false; var lastChange = new Date(contents.last_change); var tableIndex = this.tableIndexDao.queryForId(this.tableName); if (!tableIndex || !tableIndex.last_indexed) { return false; } var lastIndexed = new Date(tableIndex.last_indexed); return lastIndexed >= lastChange; } else { return false; } } catch (e) { return false; } }; /** * Returns the feature table index extension for this table and column name if exists * @return {module:extension~Extension} */ FeatureTableIndex.prototype.getFeatureTableIndexExtension = function () { return this.getExtension(this.extensionName, this.tableName, this.columnName)[0]; }; /** * Get or create the extension for this table name and column name * @return {module:extension~Extension} */ FeatureTableIndex.prototype.getOrCreateExtension = function () { return this.getOrCreate(this.extensionName, this.tableName, this.columnName, this.extensionDefinition, extension_1.Extension.READ_WRITE); }; /** * Get or create if needed the table index * @return {TableIndex} */ FeatureTableIndex.prototype.getOrCreateTableIndex = function () { var tableIndex = this.tableIndex; if (tableIndex) return tableIndex; this.tableIndexDao.createTable(); this.createTableIndex(); return this.tableIndex; }; /** * Create the table index * @return {module:extension/index~TableIndex} */ FeatureTableIndex.prototype.createTableIndex = function () { var ti = new tableIndex_1.TableIndex(); ti.table_name = this.tableName; ti.last_indexed = new Date(); return this.tableIndexDao.create(ti); }; Object.defineProperty(FeatureTableIndex.prototype, "tableIndex", { /** * Get the table index * @return {module:extension/index~TableIndex} */ get: function () { if (this.tableIndexDao.isTableExists()) { return this.tableIndexDao.queryForId(this.tableName); } else { return; } }, enumerable: false, configurable: true }); /** * Clear the geometry indices or create the table if needed * @return {Promise} resolved when complete */ FeatureTableIndex.prototype.createOrClearGeometryIndicies = function () { this.geometryIndexDao.createTable(); return this.clearGeometryIndicies(); }; /** * Clears the geometry indices * @return {Number} number of rows deleted */ FeatureTableIndex.prototype.clearGeometryIndicies = function () { var where = this.geometryIndexDao.buildWhereWithFieldAndValue(geometryIndexDao_1.GeometryIndexDao.COLUMN_TABLE_NAME, this.tableName); var whereArgs = this.geometryIndexDao.buildWhereArgs(this.tableName); return this.geometryIndexDao.deleteWhere(where, whereArgs); }; /** * Indexes the table * @param {module:extension/index~TableIndex} tableIndex TableIndex * @return {Promise} resolved when complete */ FeatureTableIndex.prototype.indexTable = function (tableIndex) { return __awaiter(this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { return [2 /*return*/, new Promise(function (resolve, reject) { setTimeout(function () { _this.indexChunk(0, tableIndex, resolve, reject); }); }).then(function () { return _this.updateLastIndexed(tableIndex) === 1; })]; }); }); }; /** * Indexes a chunk of 100 rows * @param {Number} page page to start on * @param {module:extension/index~TableIndex} tableIndex TableIndex * @param {Function} resolve function to call when all chunks are indexed * @param {Function} reject called if there is an error */ FeatureTableIndex.prototype.indexChunk = function (page, tableIndex, resolve, reject) { var _this = this; var rows = this.featureDao.queryForChunk(100, page); if (rows.length) { this.progress('Indexing ' + page * 100 + ' to ' + (page + 1) * 100); rows.forEach(function (row) { var fr = _this.featureDao.getRow(row); _this.indexRow(tableIndex, fr.id, fr.geometry); }); setTimeout(function () { _this.indexChunk(++page, tableIndex, resolve, reject); }); } else { resolve(); } }; /** * Indexes a row * @param {ableIndex} tableIndex TableIndex` * @param {Number} geomId id of the row * @param {GeometryData} geomData GeometryData to index * @return {Boolean} success */ FeatureTableIndex.prototype.indexRow = function (tableIndex, geomId, geomData) { if (!geomData) return false; var envelope = geomData.envelope; if (!envelope) { var geometry = geomData.geometry; if (geometry) { envelope = envelopeBuilder_1.EnvelopeBuilder.buildEnvelopeWithGeometry(geometry); } } if (envelope) { var geometryIndex = this.geometryIndexDao.populate(tableIndex, geomId, envelope); return this.geometryIndexDao.createOrUpdate(geometryIndex) === 1; } else { return false; } }; /** * Update the last time this feature table was indexed * @param {module:extension/index~TableIndex} tableIndex TableIndex * @return {Object} update status */ FeatureTableIndex.prototype.updateLastIndexed = function (tableIndex) { if (!tableIndex) { tableIndex = new tableIndex_1.TableIndex(); tableIndex.table_name = this.tableName; } tableIndex.last_indexed = new Date().toISOString(); var updateIndex = this.tableIndexDao.createOrUpdate(tableIndex); return updateIndex; }; /** * Query the index with the specified bounding box and projection * @param {module:boundingBox~BoundingBox} boundingBox bounding box to query for * @param {string} projection projection the boundingBox is in * @return {IterableIterator} */ FeatureTableIndex.prototype.queryWithBoundingBox = function (boundingBox, projection) { var projectedBoundingBox = boundingBox.projectBoundingBox(projection, this.featureDao.projection); var envelope = projectedBoundingBox.buildEnvelope(); return this.queryWithGeometryEnvelope(envelope); }; /** * Query witha geometry envelope * @param {any} envelope envelope * @return {IterableIterator<any>} */ FeatureTableIndex.prototype.queryWithGeometryEnvelope = function (envelope) { if (this.rtreeIndexed) { return this.rtreeIndexDao.queryWithGeometryEnvelope(envelope); } else { return this.geometryIndexDao.queryWithGeometryEnvelope(envelope); } }; /** * Count the index with the specified bounding box and projection * @param {module:boundingBox~BoundingBox} boundingBox bounding box to query for * @param {string} projection projection the boundingBox is in * @return {Number} */ FeatureTableIndex.prototype.countWithBoundingBox = function (boundingBox, projection) { var projectedBoundingBox = boundingBox.projectBoundingBox(projection, this.featureDao.projection); var envelope = projectedBoundingBox.buildEnvelope(); return this.countWithGeometryEnvelope(envelope); }; /** * Count with a geometry envelope * @param {any} envelope envelope * @return {Number} */ FeatureTableIndex.prototype.countWithGeometryEnvelope = function (envelope) { if (this.rtreeIndexed) { return this.rtreeIndexDao.countWithGeometryEnvelope(envelope); } else { return this.geometryIndexDao.countWithGeometryEnvelope(envelope); } }; FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_AUTHOR = 'nga'; FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_NAME_NO_AUTHOR = 'geometry_index'; FeatureTableIndex.EXTENSION_NAME = extension_1.Extension.buildExtensionName(FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_AUTHOR, FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_NAME_NO_AUTHOR); FeatureTableIndex.EXTENSION_GEOMETRY_INDEX_DEFINITION = 'http://ngageoint.github.io/GeoPackage/docs/extensions/geometry-index.html'; return FeatureTableIndex; }(baseExtension_1.BaseExtension)); exports.FeatureTableIndex = FeatureTableIndex; //# sourceMappingURL=featureTableIndex.js.map