Show:
/* eslint no-console: 0 */
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');

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");
}

var gdal = module.exports = require(binding_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 = {};

/**
 * Gets a GDAL configuration setting.
 *
 * @example
 * ```
 * data_path = gdal.config.get('GDAL_DATA');```
 *
 * @for gdal
 * @static
 * @method config.get
 * @param {string} key
 * @return {string}
 */
gdal.config.get = gdal.getConfigOption;

/**
 * Sets a GDAL configuration setting.
 *
 * @example
 * ```
 * gdal.config.set('GDAL_DATA', data_path);```
 *
 * @for gdal
 * @static
 * @method config.set
 * @param {string} key
 * @param {string} value
 * @return {mixed}
 */
gdal.config.set = gdal.setConfigOption;

delete gdal.getConfigOption;
delete gdal.setConfigOption;

if (process.env.GDAL_DATA === undefined) {
	gdal.config.set('GDAL_DATA', data_path);
}

gdal.Envelope = require('./envelope.js')(gdal);
gdal.Envelope3D = require('./envelope_3d.js')(gdal);

var getEnvelope = gdal.Geometry.prototype.getEnvelope;
gdal.Geometry.prototype.getEnvelope = function() {
	var obj = getEnvelope.apply(this, arguments);
	return new gdal.Envelope(obj);
};

var getEnvelope3D = gdal.Geometry.prototype.getEnvelope3D;
gdal.Geometry.prototype.getEnvelope3D = function() {
	var obj = getEnvelope3D.apply(this, arguments);
	return new gdal.Envelope3D(obj);
};

var getExtent = gdal.Layer.prototype.getExtent;
gdal.Layer.prototype.getExtent = function() {
	var obj = getExtent.apply(this, arguments);
	return new gdal.Envelope(obj);
};

// --- add additional functionality to collections ---

function defaultForEach(callback) {
	var n = this.count();
	for (var i = 0; i < n; i++) {
		if (callback(this.get(i), i) === false) return;
	}
}

function defaultMap(callback) {
	var result = [];
	this.forEach(function(value, i) {
		result.push(callback(value, i));
	});
	return result;
}

function defaultToArray() {
	var array = [];
	this.forEach(function(geom) {
		array.push(geom);
	});
	return array;
}

/**
 * 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 {Function} callback The callback to be called with each {{#crossLink "gdal.RasterBand"}}RasterBand{{/crossLink}}
 */
gdal.DatasetBands.prototype.forEach = function(callback) {
	var n = this.count();
	for (var 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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.RasterBand"}}RasterBand{{/crossLink}}
 */
gdal.DatasetBands.prototype.map = defaultMap;

/**
 * Iterates through all features using a callback function.
 *
 * @example
 * ```
 * layer.features.forEach(function(feature, i) { ... });```
 *
 * @for gdal.LayerFeatures
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Feature"}}Feature{{/crossLink}}
 */
gdal.LayerFeatures.prototype.forEach = function(callback) {
	var i = 0;
	var 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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Feature"}}Feature{{/crossLink}}
 */
gdal.LayerFeatures.prototype.map = defaultMap;

/**
 * Iterates through all fields using a callback function.
 *
 * @example
 * ```
 * layer.features.get(0).fields.forEach(function(value, key) { ... });```
 *
 * @for gdal.FeatureFields
 * @method forEach
 * @param {Function} callback The callback to be called with each feature `value` and `key`.
 */
gdal.FeatureFields.prototype.forEach = function(callback) {
	var obj = this.toObject();
	var names = Object.keys(obj);
	var n = names.length;
	for (var i = 0; i < n; i++) {
		var key = names[i];
		var value = obj[key];
		if (callback(value, key) === false) return;
	}
};

/**
 * 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
 * @param {Function} callback The callback to be called with each feature `value` and `key`.
 */
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 {Function} callback The callback to be called with each {{#crossLink "gdal.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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.FieldDefn"}}FieldDefn{{/crossLink}}
 */
gdal.LayerFields.prototype.map = defaultMap;

/**
 * Iterates through all layers using a callback function.
 *
 * @example
 * ```
 * dataset.layers.forEach(function(layer, i) { ... });```
 *
 * @for gdal.DatasetLayers
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Layer"}}Layer{{/crossLink}}
 */
gdal.DatasetLayers.prototype.map = defaultMap;

/**
 * Iterates through all field definitions using a callback function.
 *
 * @example
 * ```
 * featureDefn.forEach(function(field, i) { ... });```
 *
 * @for gdal.FeatureDefnFields
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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 = dataset.layers.map(function(field, i) {
 *     return value;
 * });```
 *
 * @for gdal.FeatureDefnFields
 * @method map
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.FieldDefn"}}FieldDefn{{/crossLink}}
 */
gdal.FeatureDefnFields.prototype.map = defaultMap;

/**
 * Iterates through all rings using a callback function.
 *
 * @example
 * ```
 * polygon.rings.forEach(function(ring, i) { ... });```
 *
 * @for gdal.PolygonRings
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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.LineStringPoints
 * @method map
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.LineString"}}LineString{{/crossLink}}
 */
gdal.PolygonRings.prototype.map = defaultMap;

/**
 * Iterates through all points using a callback function.
 *
 * @example
 * ```
 * lineString.points.forEach(function(point, i) { ... });```
 *
 * @for gdal.LineStringPoints
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Point"}}Point{{/crossLink}}
 */
gdal.LineStringPoints.prototype.map = defaultMap;

/**
 * Iterates through all child geometries using a callback function.
 *
 * @example
 * ```
 * geometryCollection.children.forEach(function(geometry, i) { ... });```
 *
 * @for gdal.GeometryCollectionChildren
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Geometry"}}Geometry{{/crossLink}}
 */
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 {Function} 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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Geometry"}}Geometry{{/crossLink}}
 */
gdal.RasterBandOverviews.prototype.map = defaultMap;

/**
 * Iterates through all registered drivers using a callback function.
 *
 * @example
 * ```
 * gdal.drivers.forEach(function(driver, i) { ... });```
 *
 * @for gdal.GDALDrivers
 * @method forEach
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.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
 * @param {Function} callback The callback to be called with each {{#crossLink "gdal.Driver"}}Driver{{/crossLink}}
 */
gdal.GDALDrivers.prototype.map = defaultMap;

/**
 * Outputs all geometries as a regular javascript array.
 *
 * @for gdal.GeometryCollectionChildren
 * @method toArray
 * @return {Array} List of {{#crossLink "gdal.Geometry"}}Geometry{{/crossLink}} instances.
 */
gdal.GeometryCollectionChildren.prototype.toArray = defaultToArray;

/**
 * Outputs all points as a regular javascript array.
 *
 * @for gdal.LineStringPoints
 * @method toArray
 * @return {Array} List of {{#crossLink "gdal.Point"}}Point{{/crossLink}} instances.
 */
gdal.LineStringPoints.prototype.toArray = defaultToArray;

/**
 * Outputs all rings as a regular javascript array.
 *
 * @for gdal.PolygonRings
 * @method toArray
 * @return {Array} List of {{#crossLink "gdal.LineString"}}LineString{{/crossLink}} instances.
 */
gdal.PolygonRings.prototype.toArray = defaultToArray;

/**
 * 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');```
 *
 * @for gdal
 * @throws Error
 * @method open
 * @static
 * @param {String} path Path to dataset to open
 * @param {String} [mode="r"] The mode to use to open the file: `"r"`, `"r+"`, or `"w"`
 * @param {String|Array} [drivers] Driver name, or list of driver names to attempt to use.
 *
 * @param {Integer} [x_size] Used when creating a raster dataset with the `"w"` mode.
 * @param {Integer} [y_size] Used when creating a raster dataset with the `"w"` mode.
 * @param {Integer} [band_count] Used when creating a raster dataset with the `"w"` mode.
 * @param {Integer} [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 {gdal.Dataset}
 */
gdal.open = (function() {
	var 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 (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');
			}
			var driver = gdal.drivers.get(drivers[0]);
			if (!driver) {
				throw new Error('Cannot find driver: ' + drivers[0]);
			}

			var 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
			var ds;
			drivers.forEach(function(driver_name) {
				var 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);
	};
})();

function fieldTypeFromValue(val) {
	var 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) {
		var 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() {
	var 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;
	var field_names = Object.keys(obj);
	for (var i = 0; i < field_names.length; i++) {
		var name  = field_names[i];
		var value = obj[field_names[i]];
		var type  = fieldTypeFromValue(value);
		var def   = new gdal.FieldDefn(name, type);
		this.add(def, approx_ok);
	}
};

gdal.Point.wkbType               = gdal.wkbPoint;
gdal.LineString.wkbType          = gdal.wkbLineString;
gdal.LinearRing.wkbType          = gdal.wkbLinearRing;
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() {
	var 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
}

gdal.RasterBandPixels.prototype.read = (function() {
	var read = gdal.RasterBandPixels.prototype.read;
	return function(x, y, width, height, data, options) {
		if (!options) options = {};
		if (data) data._gdal_type = getTypedArrayType(data);
		return read.apply(this, [x, y, width, height, data, options.buffer_width, options.buffer_height, options.type, options.pixel_space, options.line_space]);
	};
})();

gdal.RasterBandPixels.prototype.write = (function() {
	var write = gdal.RasterBandPixels.prototype.write;
	return function(x, y, width, height, data, options) {
		if (!options) options = {};
		if (data) data._gdal_type = getTypedArrayType(data);
		return write.apply(this, [x, y, width, height, data, options.buffer_width, options.buffer_height, options.pixel_space, options.line_space]);
	};
})();

gdal.RasterBandPixels.prototype.readBlock = (function() {
	var 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() {
	var writeBlock = gdal.RasterBandPixels.prototype.writeBlock;
	return function(x, y, data) {
		data._gdal_type = getTypedArrayType(data);
		return writeBlock.apply(this, arguments);
	};
})();