UNPKG

gdal-async

Version:

Bindings to GDAL (Geospatial Data Abstraction Library) with full async support

1,679 lines (1,551 loc) 44.9 kB
const path = require('path') const fs = require('fs') const binary = require('@mapbox/node-pre-gyp') const binding_path = binary.find(path.join(__dirname, '../package.json')) const data_path = path.resolve(__dirname, '../deps/libgdal/gdal/data') const proj_path = path.resolve(__dirname, '../deps/libproj/proj/data') if (process.env.GDAL_DATA === undefined && !fs.existsSync(data_path)) { throw new Error( `The bundled data path for node-gdal is missing '${data_path}' and GDAL_DATA environment is not set` ) } const proj_lib_env_undefined = process.env.PROJ_LIB === undefined if (proj_lib_env_undefined && !fs.existsSync(proj_path)) { throw new Error( `The bundled proj data path for node-gdal is missing '${proj_path}' and PROJ_LIB environment is not set` ) } const gdal = module.exports = require(binding_path) if (proj_lib_env_undefined && gdal.bundled) { gdal.setPROJSearchPath(proj_path) } gdal.Point.Multi = gdal.MultiPoint gdal.LineString.Multi = gdal.MultiLineString gdal.LinearRing.Multi = gdal.MultiLineString gdal.Polygon.Multi = gdal.MultiPolygon gdal.quiet() gdal.config = {} /** * @class gdal.config */ /** * Gets a GDAL configuration setting. * * @example * ``` * data_path = gdal.config.get('GDAL_DATA');``` * * @static * @method get * @param {string} key * @return {string} */ gdal.config.get = gdal.getConfigOption /** * Sets a GDAL configuration setting. * * @example * ``` * gdal.config.set('GDAL_DATA', data_path);``` * * @static * @method set * @param {string} key * @param {string} value * @return {void} */ gdal.config.set = gdal.setConfigOption delete gdal.getConfigOption delete gdal.setConfigOption if (process.env.CURL_CA_BUNDLE === undefined) { gdal.config.set('CURL_CA_BUNDLE', path.resolve(__dirname, '../deps/libcurl/cacert.pem')) } /** * Callback using the standard Node.js error convention * @element callback * @typedef callback<T> (Error, T) => void */ if (process.env.GDAL_DATA === undefined && gdal.bundled) { gdal.config.set('GDAL_DATA', data_path) } gdal.Envelope = require('./envelope.js')(gdal) gdal.Envelope3D = require('./envelope_3d.js')(gdal) const getEnvelope = gdal.Geometry.prototype.getEnvelope gdal.Geometry.prototype.getEnvelope = function () { const obj = getEnvelope.apply(this, arguments) return new gdal.Envelope(obj) } const getEnvelope3D = gdal.Geometry.prototype.getEnvelope3D gdal.Geometry.prototype.getEnvelope3D = function () { const obj = getEnvelope3D.apply(this, arguments) return new gdal.Envelope3D(obj) } const getEnvelopeAsync = gdal.Geometry.prototype.getEnvelopeAsync gdal.Geometry.prototype.getEnvelopeAsync = function () { // arguments[arguments.length - 1] is the callback const old_cb = arguments[arguments.length - 1] const new_cb = (e, r) => { const obj = e ? undefined : new gdal.Envelope(r) old_cb(e, obj) } arguments[arguments.length - 1] = new_cb getEnvelopeAsync.apply(this, arguments) } const getEnvelope3DAsync = gdal.Geometry.prototype.getEnvelope3DAsync gdal.Geometry.prototype.getEnvelope3DAsync = function () { const old_cb = arguments[arguments.length - 1] const new_cb = (e, r) => { const obj = e ? undefined : new gdal.Envelope3D(r) old_cb(e, obj) } arguments[arguments.length - 1] = new_cb getEnvelope3DAsync.apply(this, arguments) } const getExtent = gdal.Layer.prototype.getExtent gdal.Layer.prototype.getExtent = function () { const obj = getExtent.apply(this, arguments) return new gdal.Envelope(obj) } /** * Returns a {{#crossLink "Envelope"}}gdal.Envelope{{/crossLink}} object for the raster bands * * @example * ``` * const extent = dataset.getEnvelope() * ```` * * @for gdal.DatasetBands * @method getEnvelope * @return {Envelope} */ gdal.DatasetBands.prototype.getEnvelope = function () { const ulx = this.ds.geoTransform[0] const uly = this.ds.geoTransform[3] const lrx = this.ds.geoTransform[0] + this.ds.geoTransform[1] * this.ds.rasterSize.x const lry = this.ds.geoTransform[3] + this.ds.geoTransform[5] * this.ds.rasterSize.y return new gdal.Envelope({ minX: Math.min(ulx, lrx), minY: Math.min(uly, lry), maxX: Math.max(ulx, lrx), maxY: Math.max(uly, lry) }) } /** * @interface xyz { x: number, y: number, z?: number } */ // --- add additional functionality to collections --- /** * A simple key/value structure * @interface fieldValue { key: string, value: any } */ /** * forEach() callback type * @element forEachCb * @typedef forEachCb<T> (obj: T, idx: number) => boolean|void */ function defaultForEach(callback) { const n = this.count() for (let i = 0; i < n; i++) { if (callback(this.get(i), i) === false) return } } /** * map() callback type * @element mapCb * @typedef mapCb<T,U> (obj: T, index: number) => U */ function defaultMap(callback) { const result = [] this.forEach((value, i) => { result.push(callback(value, i)) }) return result } function defaultToArray() { const array = [] this.forEach((geom) => { array.push(geom) }) return array } function defaultIterator() { let i = 0 return { next: () => { const done = !(i < this.count()) return { done, value: done ? null : this.get(i++) } } } } /** * Iterates through all bands using a callback function. * Note: GDAL band indexes start at 1, not 0. * * @example * ``` * dataset.bands.forEach(function(band, i) { ... });``` * * @for gdal.DatasetBands * @method forEach * @param {forEachCb<gdal.RasterBand>} callback The callback to be called with each {{#crossLink "RasterBand"}}RasterBand{{/crossLink}} */ gdal.DatasetBands.prototype.forEach = function (callback) { const n = this.count() for (let i = 1; i <= n; i++) { if (callback(this.get(i), i) === false) return } } /** * Iterates through all bands using a callback function and builds * an array of the returned values. * * @example * ``` * var result = dataset.bands.map(function(band, i) { * return value; * });``` * * @for gdal.DatasetBands * @method map<U> * @param {mapCb<gdal.RasterBand,U>} callback The callback to be called with each {{#crossLink "RasterBand"}}RasterBand{{/crossLink}} * @return {U[]} */ gdal.DatasetBands.prototype.map = defaultMap /** * Iterates through all bands using an iterator * * @example * ``` * for (const band of dataset.bands) { * }``` * * @for gdal.DatasetBands * @type {gdal.RasterBand} * @method Symbol.iterator */ gdal.DatasetBands.prototype[Symbol.iterator] = function () { let i = 1 return { next: () => { const done = !(i <= this.count()) return { done, value: done ? null : this.get(i++) } } } } /** * Iterates through all features using a callback function. * * @example * ``` * layer.features.forEach(function(feature, i) { ... });``` * * @for gdal.LayerFeatures * @method forEach * @param {forEachCb<gdal.Feature>} callback The callback to be called with each {{#crossLink "Feature"}}Feature{{/crossLink}} */ gdal.LayerFeatures.prototype.forEach = function (callback) { let i = 0 let feature = this.first() while (feature) { if (callback(feature, i++) === false) return feature = this.next() } } /** * Iterates through all features using a callback function and builds * an array of the returned values. * * @example * ``` * var result = layer.features.map(function(band, i) { * return value; * });``` * * @for gdal.LayerFeatures * @method map<U> * @param {mapCb<gdal.Feature,U>} callback The callback to be called with each {{#crossLink "Feature"}}Feature{{/crossLink}} * @return {U[]} */ gdal.LayerFeatures.prototype.map = defaultMap /** * Iterates through all features using an iterator * * @example * ``` * for (const feature of layer.features) { * }``` * * @for gdal.LayerFeatures * @type {gdal.Feature} * @method Symbol.iterator */ gdal.LayerFeatures.prototype[Symbol.iterator] = function defaultIterator() { let feature return { next: () => { feature = feature ? this.next() : this.first() return { done: !feature, value: feature } } } } /** * Iterates through all fields using a callback function. * * @example * ``` * layer.features.get(0).fields.forEach(function(value, key) { ... });``` * * @for gdal.FeatureFields * @method forEach * @param {forEachCb<fieldValue>} callback The callback to be called with each feature `value` and `key`. */ gdal.FeatureFields.prototype.forEach = function (callback) { const obj = this.toObject() Object.entries(obj).every(([ k, v ]) => callback(v, k) !== false ) } /** * Iterates through all fields using a callback function and builds * an array of the returned values. * * @example * ``` * var result = layer.features.get(0).fields.map(function(value, key) { * return value; * });``` * * @for gdal.FeatureFields * @method map<U> * @param {mapCb<fieldValue,U>} callback The callback to be called with each feature `value` and `key`. * @return {U[]} */ gdal.FeatureFields.prototype.map = defaultMap /** * Outputs the fields as a serialized JSON string. * * @for gdal.FeatureFields * @method toJSON * @return {string} Serialized JSON */ gdal.FeatureFields.prototype.toJSON = function () { return JSON.stringify(this.toObject()) } /** * Converts the geometry to a GeoJSON object representation. * * @for gdal.Geometry * @method toObject * @return {object} GeoJSON */ gdal.Geometry.prototype.toObject = function () { return JSON.parse(this.toJSON()) } /** * Iterates through all field definitions using a callback function. * * @example * ``` * layer.fields.forEach(function(field, i) { ... });``` * * @for gdal.LayerFields * @method forEach * @param {forEachCb<gdal.FieldDefn>} callback The callback to be called with each {{#crossLink "FieldDefn"}}FieldDefn{{/crossLink}} */ gdal.LayerFields.prototype.forEach = defaultForEach /** * Iterates through all field definitions using a callback function and builds * an array of the returned values. * * @example * ``` * var result = layer.fields.map(function(field, i) { * return value; * });``` * * @for gdal.LayerFields * @method map<U> * @param {mapCb<gdal.FieldDefn,U>} callback The callback to be called with each {{#crossLink "FieldDefn"}}FieldDefn{{/crossLink}} * @return {U[]} */ gdal.LayerFields.prototype.map = defaultMap /** * Iterates through all field definitions using an iterator * * @example * ``` * for (const curve of layer.fields) { * }``` * * @for gdal.LayerFields * @type {gdal.FieldDefn} * @method Symbol.iterator */ gdal.LayerFields.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all layers using a callback function. * * @example * ``` * dataset.layers.forEach(function(layer, i) { ... });``` * * @for gdal.DatasetLayers * @method forEach * @param {forEachCb<gdal.Layer>} callback The callback to be called with each {{#crossLink "Layer"}}Layer{{/crossLink}} */ gdal.DatasetLayers.prototype.forEach = defaultForEach /** * Iterates through all layers using a callback function and builds * an array of the returned values. * * @example * ``` * var result = dataset.layers.map(function(field, i) { * return value; * });``` * * @for gdal.DatasetLayers * @method map<U> * @param {mapCb<gdal.Layer,U>} callback The callback to be called with each {{#crossLink "Layer"}}Layer{{/crossLink}} * @return {U[]} */ gdal.DatasetLayers.prototype.map = defaultMap /** * Iterates through all layers using an iterator * * @example * ``` * for (const curve of dataset.layers) { * }``` * * @for gdal.DatasetLayers * @type {gdal.Layer} * @method Symbol.iterator */ gdal.DatasetLayers.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all field definitions using a callback function. * * @example * ``` * featureDefn.forEach(function(field, i) { ... });``` * * @for gdal.FeatureDefnFields * @method forEach * @param {forEachCb<gdal.FieldDefn>} callback The callback to be called with each {{#crossLink "FieldDefn"}}FieldDefn{{/crossLink}} */ gdal.FeatureDefnFields.prototype.forEach = defaultForEach /** * Iterates through all field definitions using a callback function and builds * an array of the returned values. * * @example * ``` * var result = featureDefn.map(function(field, i) { * return value; * });``` * * @for gdal.FeatureDefnFields * @method map<U> * @param {mapCb<gdal.FieldDefn,U>} callback The callback to be called with each {{#crossLink "FieldDefn"}}FieldDefn{{/crossLink}} * @return {U[]} */ gdal.FeatureDefnFields.prototype.map = defaultMap /** * Iterates through all field definitions using an iterator * * @example * ``` * for (const defn of featureDefn) { * }``` * * @for gdal.FeatureDefnFields * @type {gdal.FieldDefn} * @method Symbol.iterator */ gdal.FeatureDefnFields.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all rings using a callback function. * * @example * ``` * polygon.rings.forEach(function(ring, i) { ... });``` * * @for gdal.PolygonRings * @method forEach * @param {forEachCb<gdal.LineString>} callback The callback to be called with each {{#crossLink "LineString"}}LineString{{/crossLink}} */ gdal.PolygonRings.prototype.forEach = defaultForEach /** * Iterates through all rings using a callback function and builds * an array of the returned values. * * @example * ``` * var result = polygon.rings.map(function(ring, i) { * return value; * });``` * * @for gdal.PolygonRings * @method map<U> * @param {mapCb<gdal.LineString,U>} callback The callback to be called with each {{#crossLink "LineString"}}LineString{{/crossLink}} * @return {U[]} */ gdal.PolygonRings.prototype.map = defaultMap /** * Iterates through all rings using an iterator * * @example * ``` * for (const ring of polygon.rings) { * }``` * * @for gdal.PolygonRings * @type {gdal.LineString} * @method Symbol.iterator */ gdal.PolygonRings.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all points using a callback function. * * @example * ``` * lineString.points.forEach(function(point, i) { ... });``` * * @for gdal.LineStringPoints * @method forEach * @param {forEachCb<gdal.Point>} callback The callback to be called with each {{#crossLink "Point"}}Point{{/crossLink}} */ gdal.LineStringPoints.prototype.forEach = defaultForEach /** * Iterates through all points using a callback function and builds * an array of the returned values. * * @example * ``` * var result = lineString.points.map(function(point, i) { * return value; * });``` * * @for gdal.LineStringPoints * @method map<U> * @param {mapCb<gdal.Point,U>} callback The callback to be called with each {{#crossLink "Point"}}Point{{/crossLink}} * @return {U[]} */ gdal.LineStringPoints.prototype.map = defaultMap /** * Iterates through all points using an iterator * * @example * ``` * for (const point of lineString.points) { * }``` * * @for gdal.LineStringPoints * @type {gdal.Point} * @method Symbol.iterator */ gdal.LineStringPoints.prototype[Symbol.iterator] = defaultIterator if (gdal.CompoundCurveCurves) { /** * Iterates through all curves using a callback function. * * @example * ``` * compoundCurve.curves.forEach(function(curve, i) { ... });``` * * @for gdal.CompoundCurveCurves * @method forEach * @param {forEachCb<gdal.SimpleCurve>} callback The callback to be called with each {{#crossLink "SimpleCurve"}}SimpleCurve{{/crossLink}} */ gdal.CompoundCurveCurves.prototype.forEach = defaultForEach /** * Iterates through all curves using a callback function and builds * an array of the returned values. * * @example * ``` * var result = compoundCurves.curves.map(function(curve, i) { * return value; * });``` * * @for gdal.CompoundCurveCurves * @method map<U> * @param {mapCb<gdal.SimpleCurve,U>} callback The callback to be called with each {{#crossLink "SimpleCurve"}}SimpleCurve{{/crossLink}} * @return {U[]} */ gdal.CompoundCurveCurves.prototype.map = defaultMap /** * Iterates through all curves using an iterator * * @example * ``` * for (const curve of compoundCurves.curves) { * } * * @for gdal.CompoundCurveCurves * @type {gdal.SimpleCurve} * @method Symbol.iterator */ gdal.CompoundCurveCurves.prototype[Symbol.iterator] = defaultIterator } /** * Iterates through all child geometries using a callback function. * * @example * ``` * geometryCollection.children.forEach(function(geometry, i) { ... });``` * * @for gdal.GeometryCollectionChildren * @method forEach * @param {forEachCb<gdal.Geometry>} callback The callback to be called with each {{#crossLink "Geometry"}}Geometry{{/crossLink}} */ gdal.GeometryCollectionChildren.prototype.forEach = defaultForEach /** * Iterates through all child geometries using a callback function and builds * an array of the returned values. * * @example * ``` * var result = geometryCollection.children.map(function(geometry, i) { * return value; * });``` * * @for gdal.GeometryCollectionChildren * @method map<U> * @param {mapCb<gdal.Geometry,U>} callback The callback to be called with each {{#crossLink "Geometry"}}Geometry{{/crossLink}} * @return {U[]} */ gdal.GeometryCollectionChildren.prototype.map = defaultMap /** * Iterates through all overviews using a callback function. * * @example * ``` * band.overviews.forEach(function(overviewBand, i) { ... });``` * * @for gdal.RasterBandOverviews * @method forEach * @param {forEachCb<gdal.RasterBand>} callback */ gdal.RasterBandOverviews.prototype.forEach = defaultForEach /** * Iterates through all raster overviews using a callback function and builds * an array of the returned values. * * @example * ``` * var result = band.overviews.map(function(overviewBand, i) { * return value; * });``` * * @for gdal.RasterBandOverviews * @method map<U> * @param {mapCb<gdal.RasterBand,U>} callback The callback to be called with each {{#crossLink "RasterBand"}}RasterBand{{/crossLink}} * @return {U[]} */ gdal.RasterBandOverviews.prototype.map = defaultMap /** * Iterates through all overview using an iterator * * @example * ``` * for (const overview of band.overviews) { * } * * @for gdal.RasterBandOverviews * @type {gdal.RasterBand} * @method Symbol.iterator */ gdal.RasterBandOverviews.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all registered drivers using a callback function. * * @example * ``` * gdal.drivers.forEach(function(driver, i) { ... });``` * * @for gdal.GDALDrivers * @method forEach * @param {forEachCb<gdal.Driver>} callback The callback to be called with each {{#crossLink "Driver"}}Driver{{/crossLink}} */ gdal.GDALDrivers.prototype.forEach = defaultForEach /** * Iterates through all drivers using a callback function and builds * an array of the returned values. * * @example * ``` * var result = gdal.drivers.map(function(driver, i) { * return value; * });``` * * @for gdal.GDALDrivers * @method map<U> * @param {mapCb<gdal.Driver,U>} callback The callback to be called with each {{#crossLink "Driver"}}Driver{{/crossLink}} * @return {U[]} */ gdal.GDALDrivers.prototype.map = defaultMap /** * Iterates through all drivers using an iterator * * @example * ``` * for (const driver of gdal.drivers) { * } * * @for gdal.GDALDrivers * @type {gdal.Driver} * @method Symbol.iterator */ gdal.GDALDrivers.prototype[Symbol.iterator] = defaultIterator /** * Outputs all geometries as a regular javascript array. * * @for gdal.GeometryCollectionChildren * @method toArray * @return {gdal.Geometry[]} List of {{#crossLink "Geometry"}}Geometry{{/crossLink}} instances. */ gdal.GeometryCollectionChildren.prototype.toArray = defaultToArray /** * Outputs all points as a regular javascript array. * * @for gdal.LineStringPoints * @method toArray * @return {gdal.Point[]} List of {{#crossLink "Point"}}Point{{/crossLink}} instances. */ gdal.LineStringPoints.prototype.toArray = defaultToArray /** * Outputs all rings as a regular javascript array. * * @for gdal.PolygonRings * @method toArray * @return {gdal.LineString[]} List of {{#crossLink "LineString"}}LineString{{/crossLink}} instances. */ gdal.PolygonRings.prototype.toArray = defaultToArray if (gdal.MDArray) { /** * Iterates through all arrays using a callback function. * * @example * ``` * group.arrays.forEach(function(array, i) { ... });``` * * @for gdal.GroupArrays * @method forEach * @param {forEachCb<gdal.MDArray>} callback The callback to be called with each {{#crossLink "MDArray"}}MDArray{{/crossLink}} */ gdal.GroupArrays.prototype.forEach = defaultForEach /** * Iterates through all arrays using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.arrays.map(function(array, i) { * return value; * });``` * * @for gdal.GroupArrays * @method map<U> * @param {mapCb<gdal.MDArray,U>} callback The callback to be called with each {{#crossLink "MDArray"}}MDArray{{/crossLink}} * @return {U[]} */ gdal.GroupArrays.prototype.map = defaultMap /** * Iterates through all arrays using an iterator * * @example * ``` * for (const array of group.arrays) { * } * * @for gdal.GroupArrays * @type {gdal.MDArray} * @method Symbol.iterator */ gdal.GroupArrays.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all groups using a callback function. * * @example * ``` * group.groups.forEach(function(array, i) { ... });``` * * @for gdal.GroupGroups * @method forEach * @param {forEachCb<gdal.Group>} callback The callback to be called with each {{#crossLink "Group"}}Group{{/crossLink}} */ gdal.GroupGroups.prototype.forEach = defaultForEach /** * Iterates through all groups using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.groups.map(function(group, i) { * return value; * });``` * * @for gdal.GroupGroups * @method map<U> * @param {mapCb<gdal.Group,U>} callback The callback to be called with each {{#crossLink "Group"}}Group{{/crossLink}} * @return {U[]} */ gdal.GroupGroups.prototype.map = defaultMap /** * Iterates through all groups using an iterator * * @example * ``` * for (const group of group.groups) { * } * * @for gdal.GroupGroups * @type {gdal.Group} * @method Symbol.iterator */ gdal.GroupGroups.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all dimensions using a callback function. * * @example * ``` * group.dimensions.forEach(function(array, i) { ... });``` * * @for gdal.GroupDimensions * @method forEach * @param {forEachCb<gdal.Dimension>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} */ gdal.GroupDimensions.prototype.forEach = defaultForEach /** * Iterates through all dimensions using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.dimensions.map(function(dimension, i) { * return value; * });``` * * @for gdal.GroupDimensions * @method map<U> * @param {mapCb<gdal.Dimension,U>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} * @return {U[]} */ gdal.GroupDimensions.prototype.map = defaultMap /** * Iterates through all dimensions using an iterator * * @example * ``` * for (const dimension of group.dimensions) { * } * * @for gdal.GroupDimensions * @type {gdal.Dimension} * @method Symbol.iterator */ gdal.GroupDimensions.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all attributes using a callback function. * * @example * ``` * group.attributes.forEach(function(array, i) { ... });``` * * @for gdal.GroupAttributes * @method forEach * @param {forEachCb<gdal.Dimension>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} */ gdal.GroupAttributes.prototype.forEach = defaultForEach /** * Iterates through all attributes using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.attributes.map(function(array, i) { * return value; * });``` * * @for gdal.GroupAttributes * @method map<U> * @param {mapCb<gdal.Attribute,U>} callback The callback to be called with each {{#crossLink "Attribute"}}Attribute{{/crossLink}} * @return {U[]} */ gdal.GroupAttributes.prototype.map = defaultMap /** * Iterates through all attributes using an iterator * * @example * ``` * for (const attribute of group.attributes) { * } * * @for gdal.GroupAttributes * @type {gdal.Attribute} * @method Symbol.iterator */ gdal.GroupAttributes.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all dimensions using a callback function and builds * an array of the returned values. * * @example * ``` * var result = array.dimensions.map(function(dimension, i) { * return value; * });``` * * @for gdal.GroupDimensions * @method map<U> * @param {mapCb<gdal.Dimension,U>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} * @return {U[]} */ gdal.ArrayDimensions.prototype.forEach = defaultForEach /** * Iterates through all dimensions using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.dimensions.map(function(array, i) { * return value; * });``` * * @for gdal.ArrayDimensions * @method map<U> * @param {mapCb<gdal.Dimension,U>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} * @return {U[]} */ gdal.ArrayDimensions.prototype.map = defaultMap /** * Iterates through all dimensions using an iterator * * @example * ``` * for (const dimension of group.dimensions) { * } * * @for gdal.ArrayDimensions * @type {gdal.Dimension} * @method Symbol.iterator */ gdal.ArrayDimensions.prototype[Symbol.iterator] = defaultIterator /** * Iterates through all attributes using a callback function. * * @example * ``` * group.attributes.forEach(function(array, i) { ... });``` * * @for gdal.ArrayAttributes * @method forEach * @param {forEachCb<gdal.Dimension>} callback The callback to be called with each {{#crossLink "Dimension"}}Dimension{{/crossLink}} */ gdal.ArrayAttributes.prototype.forEach = defaultForEach /** * Iterates through all attributes using a callback function and builds * an array of the returned values. * * @example * ``` * var result = group.attributes.map(function(array, i) { * return value; * });``` * * @for gdal.ArrayAttributes * @method map<U> * @param {mapCb<gdal.Attribute,U>} callback The callback to be called with each {{#crossLink "Attribute"}}Attribute{{/crossLink}} * @return {U[]} */ gdal.ArrayAttributes.prototype.map = defaultMap /** * Iterates through all attributes using an iterator * * @example * ``` * for (const attribute of group.attributes) { * } * * @for gdal.ArrayAttributes * @type {gdal.Attribute} * @method Symbol.iterator */ gdal.ArrayAttributes.prototype[Symbol.iterator] = defaultIterator } /** * Creates or opens a dataset. Dataset should be explicitly closed with `dataset.close()` method if opened in `"w"` mode to flush any changes. Otherwise, datasets are closed when (and if) node decides to garbage collect them. * * @example * ``` * var dataset = gdal.open('./data.shp');``` * * @example * ``` * var dataset = gdal.open(fs.readFileSync('./data.shp'));``` * * @for gdal * @throws Error * @method open * @static * @param {string|Buffer} path Path to dataset or in-memory Buffer to open * @param {string} [mode="r"] The mode to use to open the file: `"r"`, `"r+"`, or `"w"` * @param {string|string[]} [drivers] Driver name, or list of driver names to attempt to use. * * @param {number} [x_size] Used when creating a raster dataset with the `"w"` mode. * @param {number} [y_size] Used when creating a raster dataset with the `"w"` mode. * @param {number} [band_count] Used when creating a raster dataset with the `"w"` mode. * @param {string} [data_type] Used when creating a raster dataset with the `"w"` mode. * @param {string[]|object} [creation_options] Used when creating a dataset with the `"w"` mode. * * @return {Dataset} */ gdal.open = (function () { const open = gdal.open // add 'w' mode to gdal.open() method and also GDAL2-style driver selection return function ( filename, mode, drivers /* , x_size, y_size, n_bands, datatype, options */ ) { if (filename instanceof Buffer) { const buffer = filename arguments[0] = gdal.vsimem._anonymous(filename) const ds = gdal.open.apply(gdal, arguments) ds.buffer = buffer return ds } if (typeof drivers === 'string') { drivers = [ drivers ] } else if (drivers && !Array.isArray(drivers)) { throw new Error('driver(s) must be a string or list of strings') } if (mode && mode.includes && mode.includes('w')) { // create file with given driver if (!drivers) { throw new Error('Driver must be specified') } if (drivers.length !== 1) { throw new Error('Only one driver can be used to create a file') } const driver = gdal.drivers.get(drivers[0]) if (!driver) { throw new Error(`Cannot find driver: ${drivers[0]}`) } const args = Array.prototype.slice.call(arguments, 3) // x_size, y_size, ... args.unshift(filename) return driver.create.apply(driver, args) } if (arguments.length > 2) { // open file with driver list // loop through given drivers trying to open file let ds drivers.forEach((driver_name) => { const driver = gdal.drivers.get(driver_name) if (!driver) { throw new Error(`Cannot find driver: ${driver_name}`) } try { ds = driver.open(filename, mode) return false } catch (err) { /* skip driver */ } }) if (!ds) throw new Error('Error opening dataset') return ds } // call gdal.open() method normally return open.apply(gdal, arguments) } })() const promisify = require('util').promisify const callbackify = require('util').callbackify /** * Asynchronously creates or opens a dataset. Dataset should be explicitly closed with `dataset.close()` method if opened in `"w"` mode to flush any changes. Otherwise, datasets are closed when (and if) node decides to garbage collect them. * If the last parameter is a callback, then this callback is called on completion and undefined is returned. Otherwise the function returns a Promise resolved with the result. * * @example * ``` * var dataset = await gdal.openAsync('./data.shp');``` * * @example * ``` * var dataset = await gdal.openAsync(await fd.readFile('./data.shp'));``` * * @example * ``` * gdal.openAsync('./data.shp', (err, ds) => {...});``` * * @for gdal * @method openAsync * @static * @param {string|Buffer} path Path to dataset or in-memory Buffer to open * @param {string} [mode="r"] The mode to use to open the file: `"r"`, `"r+"`, or `"w"` * @param {string|string[]} [drivers] Driver name, or list of driver names to attempt to use. * * @param {number} [x_size] Used when creating a raster dataset with the `"w"` mode. * @param {number} [y_size] Used when creating a raster dataset with the `"w"` mode. * @param {number} [band_count] Used when creating a raster dataset with the `"w"` mode. * @param {string} [data_type] Used when creating a raster dataset with the `"w"` mode. * @param {string[]|object} [creation_options] Used when creating a dataset with the `"w"` mode. * @param {callback<void>} [callback=undefined] {{{cb}}} * @return {Promise<Dataset>} * */ /** * TypeScript shorthand version with callback and no optional arguments * * @for gdal * @method openAsync * @static * @param {string|Buffer} path Path to dataset or in-memory Buffer to open * @param {callback<void>} callback {{{cb}}} * @return {void} */ gdal.openAsync = (function () { const openPromise = (function () { const openPromise = promisify(gdal.openAsync) // add 'w' mode to gdal.open() method and also GDAL2-style driver selection return function ( filename, mode, drivers, _x_size, _y_size, _n_bands, _datatype, _options ) { if (filename instanceof Buffer) { const buffer = filename try { // vsimem._anonymous is always synchronous arguments[0] = gdal.vsimem._anonymous(filename) } catch (e) { return Promise.reject(e) } return gdal.openAsync.apply(gdal, arguments).then((ds) => { ds.buffer = buffer return ds }) } if (typeof drivers === 'string') { drivers = [ drivers ] } else if (drivers && !Array.isArray(drivers)) { throw new Error('driver(s) must be a string or list of strings') } if (mode === 'w') { // create file with given driver if (!drivers) { throw new Error('Driver must be specified') } if (drivers.length !== 1) { throw new Error('Only one driver can be used to create a file') } const driver = gdal.drivers.get(drivers[0]) if (!driver) { throw new Error(`Cannot find driver: ${drivers[0]}`) } const args = Array.prototype.slice.call(arguments, 3) // x_size, y_size, ... args.unshift(filename) return gdal.Driver.prototype.createAsync.apply(driver, args) } if (arguments.length > 2 && drivers) { const p = [] for (const driver_name of drivers) { const driver = gdal.drivers.get(driver_name) if (!driver) { throw new Error(`Cannot find driver: ${driver_name}`) } p.push(gdal.Driver.prototype.openAsync.call(driver, filename, mode)) } // first driver to open the file wins // normally, there are no formats supported by two drivers return Promise.any(p).catch(() => { throw new Error('Error opening dataset') }) } // call gdal.open() method normally return openPromise.call(gdal, filename, mode) } })() const openCb = callbackify(openPromise) return function (filename, mode, drivers, x_size, y_size, n_bands, datatype, options, callback) { if (typeof arguments[arguments.length - 1] === 'function' && callback === undefined) { callback = arguments[arguments.length - 1] arguments[arguments.length - 1] = undefined } if (callback) { const args = arguments Array.prototype.push.call(args, callback) return openCb.apply(gdal, args) } return openPromise.apply(gdal, arguments) } })() function fieldTypeFromValue(val) { const type = typeof val if (type === 'number') { if (val % 1 === 0) return gdal.OFTInteger return gdal.OFTReal } else if (type === 'string') { return gdal.OFTString } else if (type === 'boolean') { return gdal.OFTInteger } else if (val instanceof Date) { return gdal.OFTDateTime } else if (val instanceof Array) { const sub_type = fieldTypeFromValue(val[0]) switch (sub_type) { case gdal.OFTString: return gdal.OFTStringList case gdal.OFTInteger: return gdal.OFTIntegerList case gdal.OFTReal: return gdal.OFTRealList default: throw new Error('Array element cannot be converted into OGRFieldType') } } else if (val instanceof Buffer) { return gdal.OFTBinary } throw new Error('Value cannot be converted into OGRFieldType') } /** * Creates a LayerFields instance from an object of keys and values. * * @method fromJSON * @for gdal.LayerFields * @param {object} object * @param {boolean} [approx_ok=false] */ gdal.LayerFields.prototype.fromJSON = (function () { let warned = false return function (obj, approx_ok) { if (!warned) { console.warn( 'NODE-GDAL Deprecation Warning: LayerFields fromJSON() is deprecated, use fromObject() instead' ) warned = true } return this.fromObject(obj, approx_ok) } })() gdal.LayerFields.prototype.fromObject = function (obj, approx_ok) { approx_ok = approx_ok || false Object.entries(obj).forEach(([ k, v ]) => { const type = fieldTypeFromValue(v) const def = new gdal.FieldDefn(k, type) this.add(def, approx_ok) }) } /** * @for gdal.Geometry * @property wkbType * @final * @static * @type {number} */ gdal.Point.wkbType = gdal.wkbPoint gdal.LineString.wkbType = gdal.wkbLineString gdal.LinearRing.wkbType = gdal.wkbLinearRing if (gdal.CircularString) { gdal.CircularString.wkbType = gdal.wkbCircularString gdal.CompoundCurve.wkbType = gdal.wkbCompoundCurve gdal.MultiCurve.wkbType = gdal.wkbMultiCurve } gdal.Polygon.wkbType = gdal.wkbPolygon gdal.MultiPoint.wkbType = gdal.wkbMultiPoint gdal.MultiLineString.wkbType = gdal.wkbMultiLineString gdal.MultiPolygon.wkbType = gdal.wkbMultiPolygon gdal.GeometryCollection.wkbType = gdal.wkbGeometryCollection // enable passing geometry constructors as the geometry type gdal.DatasetLayers.prototype.create = (function () { const create = gdal.DatasetLayers.prototype.create return function (name, srs, geom_type /* , creation_options */) { if (arguments.length > 2 && geom_type instanceof Function) { if (typeof geom_type.wkbType === 'undefined') { throw new Error('Function must be a geometry constructor') } arguments[2] = geom_type.wkbType } return create.apply(this, arguments) } })() function getTypedArrayType(array) { if (array instanceof Uint8Array) return 1 // gdal.GDT_Byte if (array instanceof Int8Array) return 1 // gdal.GDT_Byte if (array instanceof Int16Array) return 3 // gdal.GDT_Int16 if (array instanceof Uint16Array) return 2 // gdal.GDT_UInt16 if (array instanceof Int32Array) return 5 // gdal.GDT_Int32 if (array instanceof Uint32Array) return 4 // gdal.GDT_UInt32 if (array instanceof Float32Array) return 6 // gdal.GDT_Float32 if (array instanceof Float64Array) return 7 // gdal.GDT_Float64 return 0 // gdal.GDT_Unknown } const mangleWrite = (args) => { let [ x, y, width, height, data, options ] = args if (!options) options = {} if (data) data._gdal_type = getTypedArrayType(data) return [ x, y, width, height, data, options.buffer_width, options.buffer_height, options.pixel_space, options.line_space, options.progress_cb, options.offset ] } const mangleRead = (args) => { let [ x, y, width, height, data, options ] = args if (!options) options = {} if (data) data._gdal_type = getTypedArrayType(data) return [ x, y, width, height, data, options.buffer_width, options.buffer_height, options.type, options.pixel_space, options.line_space, options.resampling, options.progress_cb, options.offset ] } const mangleBlock = (args) => { if (args[2]) args[2]._gdal_type = getTypedArrayType(args[2]) return args } const mangleMDArray = (args) => { if (typeof args[0] === 'object' && typeof args[0].data === 'object') { args[0].data._gdal_type = getTypedArrayType(args[0].data) } return args } gdal.RasterBandPixels.prototype.read = (function () { const read = gdal.RasterBandPixels.prototype.read return function () { return read.apply(this, mangleRead(arguments)) } })() gdal.RasterBandPixels.prototype.write = (function () { const write = gdal.RasterBandPixels.prototype.write return function () { return write.apply(this, mangleWrite(arguments)) } })() gdal.RasterBandPixels.prototype.readBlock = (function () { const readBlock = gdal.RasterBandPixels.prototype.readBlock return function (x, y, data) { if (data) data._gdal_type = getTypedArrayType(data) return readBlock.apply(this, arguments) } })() gdal.RasterBandPixels.prototype.writeBlock = (function () { const writeBlock = gdal.RasterBandPixels.prototype.writeBlock return function (x, y, data) { data._gdal_type = getTypedArrayType(data) return writeBlock.apply(this, arguments) } })() if (gdal.MDArray) { gdal.MDArray.prototype.read = (function () { const read = gdal.MDArray.prototype.read return function () { return read.apply(this, mangleMDArray(arguments)) } })() } const GroupCollection = { countAsync: 0, getAsync: 1 } // Generic promisifiable methods and the argument number of their callbacks const promisifiables = { Driver: { createAsync: 6, createCopyAsync: 3, openAsync: 2 }, Dataset: { flushAsync: 0, buildOverviewsAsync: 4, executeSQLAsync: 3 }, Layer: { flushAsync: 0 }, RasterBand: { flushAsync: 0, fillAsync: 2, computeStatisticsAsync: 1 }, RasterBandPixels: { readAsync: 13, writeAsync: 11, readBlockAsync: 3, writeBlockAsync: 3, getAsync: 2, setAsync: 3 }, DatasetLayers: { getAsync: 1, createAsync: 4, countAsync: 0, copyAsync: 3, removeAsync: 1 }, LayerFeatures: { getAsync: 1, setAsync: 2, firstAsync: 0, nextAsync: 0, addAsync: 1, countAsync: 1, removeAsync: 1 }, DatasetBands: { getAsync: 1, createAsync: 2, countAsync: 0 }, Geometry: { $fromWKTAsync: 2, $fromWKBAsync: 2, $fromGeoJsonAsync: 1, $fromGeoJsonBufferAsync: 1, toKMLAsync: 0, toGMLAsync: 0, toWKTAsync: 0, toWKBAsync: 2, toJSONAsync: 0, centroidAsync: 0, convexHullAsync: 0, boundaryAsync: 0, intersectionAsync: 1, flattenTo2DAsync: 0, unionAsync: 1, differenceAsync: 1, symDifferenceAsync: 1, simplifyAsync: 1, simplifyPreserveTopologyAsync: 1, bufferAsync: 2, getEnvelopeAsync: 0, getEnvelope3DAsync: 0, closeRingsAsync: 0, emptyAsync: 0, swapXYAsync: 0, isEmptyAsync: 0, isValidAsync: 0, makeValidAsync: 0, isSimpleAsync: 0, isRingAsync: 0, intersectsAsync: 1, equalsAsync: 1, disjointAsync: 1, touchesAsync: 1, crossesAsync: 1, withinAsync: 1, containsAsync: 1, overlapsAsync: 1, distanceAsync: 1, transformAsync: 1, transformToAsync: 1 }, MDArray: { readAsync: 1 }, GroupArrays: GroupCollection, GroupDimensions: GroupCollection, GroupAttributes: GroupCollection, GroupGroups: GroupCollection, ArrayDimensions: GroupCollection, ArrayAttributes: GroupCollection, $: { $fillNodataAsync: 1, $contourGenerateAsync: 1, $sieveFilterAsync: 1, $checksumImageAsync: 5, $polygonizeAsync: 1, $reprojectImageAsync: 1, $suggestedWarpOutputAsync: 1, $_acquireLocksAsync: 3 } } const argMangle = { RasterBandPixels: { readAsync: mangleRead, writeAsync: mangleWrite, readBlockAsync: mangleBlock, writeBlockAsync: mangleBlock }, MDArray: { readAsync: mangleMDArray } } // For each *Async function create a function that checks if the last parameter is a callback // Then call either the original, either the promisified version with the callback // placed at the right argument number since the C++ code does not support floating callbacks for (const c of Object.keys(promisifiables)) { const klass = c === '$' ? gdal : gdal[c] if (klass === undefined) { continue } for (const _m of Object.keys(promisifiables[c])) { const { base, m } = _m.startsWith('$') ? { base: klass, m: _m.slice(1) } : { base: klass.prototype, m: _m } if (base[m] === undefined) { continue } base[m] = (function () { const promisified = promisify(base[m]) const original = base[m] const cbArg = promisifiables[c][_m] const mangle = argMangle[c] && argMangle[c][_m] ? argMangle[c][_m] : (a) => a return function () { let callback if (typeof arguments[arguments.length - 1] === 'function') { callback = arguments[arguments.length - 1] arguments[arguments.length - 1] = undefined } let args = Array.prototype.slice.call(mangle(arguments), 0, cbArg) if (callback) { args[cbArg] = callback return original.apply(this, args) } args = Object.assign(new Array(cbArg).fill(undefined), args) return promisified.apply(this, args) } })() } }