diff --git a/examples/wkb.html b/examples/wkb.html new file mode 100644 index 0000000000..faa782a5b0 --- /dev/null +++ b/examples/wkb.html @@ -0,0 +1,9 @@ +--- +layout: example.html +title: WKB +shortdesc: Example of using the WKB parser. +docs: > + Create features from geometries in WKB (Well Known Binary) format. +tags: "wkb, well known binary" +--- +
diff --git a/examples/wkb.js b/examples/wkb.js new file mode 100644 index 0000000000..dc04b6ed6e --- /dev/null +++ b/examples/wkb.js @@ -0,0 +1,34 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import WKB from '../src/ol/format/WKB.js'; +import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; + +const raster = new TileLayer({ + source: new OSM(), +}); + +const wkb = + '0103000000010000000500000054E3A59BC4602540643BDF4F8D1739C05C8FC2F5284C4140EC51B81E852B34C0D578E926316843406F1283C0CAD141C01B2FDD2406012B40A4703D0AD79343C054E3A59BC4602540643BDF4F8D1739C0'; + +const format = new WKB(); + +const feature = format.readFeature(wkb, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', +}); + +const vector = new VectorLayer({ + source: new VectorSource({ + features: [feature], + }), +}); + +const map = new Map({ + layers: [raster, vector], + target: 'map', + view: new View({ + center: [2952104.0199, -3277504.823], + zoom: 4, + }), +}); diff --git a/src/ol/format/Feature.js b/src/ol/format/Feature.js index 67cecd902c..9f629c776a 100644 --- a/src/ol/format/Feature.js +++ b/src/ol/format/Feature.js @@ -187,7 +187,7 @@ class FeatureFormat { * @abstract * @param {import("../Feature.js").default} feature Feature. * @param {WriteOptions} [opt_options] Write options. - * @return {string} Result. + * @return {string|ArrayBuffer} Result. */ writeFeature(feature, opt_options) { return abstract(); @@ -199,7 +199,7 @@ class FeatureFormat { * @abstract * @param {Array} features Features. * @param {WriteOptions} [opt_options] Write options. - * @return {string} Result. + * @return {string|ArrayBuffer} Result. */ writeFeatures(features, opt_options) { return abstract(); @@ -211,7 +211,7 @@ class FeatureFormat { * @abstract * @param {import("../geom/Geometry.js").default} geometry Geometry. * @param {WriteOptions} [opt_options] Write options. - * @return {string} Result. + * @return {string|ArrayBuffer} Result. */ writeGeometry(geometry, opt_options) { return abstract(); diff --git a/src/ol/format/WKB.js b/src/ol/format/WKB.js new file mode 100644 index 0000000000..831fd3c800 --- /dev/null +++ b/src/ol/format/WKB.js @@ -0,0 +1,885 @@ +/** + * @module ol/format/WKB + */ +import Feature from '../Feature.js'; +import FeatureFormat, {transformGeometryWithOptions} from './Feature.js'; +import FormatType from './FormatType.js'; +import GeometryCollection from '../geom/GeometryCollection.js'; +import GeometryLayout from '../geom/GeometryLayout.js'; +import GeometryType from '../geom/GeometryType.js'; +import LineString from '../geom/LineString.js'; +import MultiLineString from '../geom/MultiLineString.js'; +import MultiPoint from '../geom/MultiPoint.js'; +import MultiPolygon from '../geom/MultiPolygon.js'; +import Point from '../geom/Point.js'; +import Polygon from '../geom/Polygon.js'; +import {get as getProjection} from '../proj.js'; + +import SimpleGeometry from '../geom/SimpleGeometry.js'; +import {assign} from '../obj.js'; + +// WKB spec: https://www.ogc.org/standards/sfa +// EWKB spec: https://raw.githubusercontent.com/postgis/postgis/2.1.0/doc/ZMSgeoms.txt + +/** + * @const + * @enum {number} + */ +const WKBGeometryType = { + POINT: 1, + LINE_STRING: 2, + POLYGON: 3, + MULTI_POINT: 4, + MULTI_LINE_STRING: 5, + MULTI_POLYGON: 6, + GEOMETRY_COLLECTION: 7, + + /* + CIRCULAR_STRING: 8, + COMPOUND_CURVE: 9, + CURVE_POLYGON: 10, + + MULTI_CURVE: 11, + MULTI_SURFACE: 12, + CURVE: 13, + SURFACE: 14, + */ + + POLYHEDRAL_SURFACE: 15, + TIN: 16, + TRIANGLE: 17, +}; + +class WkbReader { + /** + * @param {DataView} view source to read + */ + constructor(view) { + this.view_ = view; + this.pos_ = 0; + + this.initialized_ = false; + this.isLittleEndian_ = false; + this.hasZ_ = false; + this.hasM_ = false; + /** @type {number} */ + this.srid_ = null; + + this.layout_ = GeometryLayout.XY; + } + + /** + * @return {number} value + */ + readUint8() { + return this.view_.getUint8(this.pos_++); + } + + /** + * @param {boolean} [isLittleEndian] Whether read value as little endian + * @return {number} value + */ + readUint32(isLittleEndian) { + return this.view_.getUint32( + (this.pos_ += 4) - 4, + isLittleEndian !== undefined ? isLittleEndian : this.isLittleEndian_ + ); + } + + /** + * @param {boolean} [isLittleEndian] Whether read value as little endian + * @return {number} value + */ + readDouble(isLittleEndian) { + return this.view_.getFloat64( + (this.pos_ += 8) - 8, + isLittleEndian !== undefined ? isLittleEndian : this.isLittleEndian_ + ); + } + + /** + * @return {import('../coordinate.js').Coordinate} coords for Point + */ + readPoint() { + /** @type import('../coordinate.js').Coordinate */ + const coords = []; + + coords.push(this.readDouble()); + coords.push(this.readDouble()); + if (this.hasZ_) { + coords.push(this.readDouble()); + } + if (this.hasM_) { + coords.push(this.readDouble()); + } + + return coords; + } + + /** + * @return {Array} coords for LineString / LinearRing + */ + readLineString() { + const numPoints = this.readUint32(); + + /** @type Array */ + const coords = []; + for (let i = 0; i < numPoints; i++) { + coords.push(this.readPoint()); + } + + return coords; + } + + /** + * @return {Array>} coords for Polygon like + */ + readPolygon() { + const numRings = this.readUint32(); + + /** @type Array> */ + const rings = []; + for (let i = 0; i < numRings; i++) { + rings.push(this.readLineString()); // as a LinearRing + } + + return rings; + } + + /** + * @param {number} [expectedTypeId] Expected WKB Type ID + * @return {number} WKB Type ID + */ + readWkbHeader(expectedTypeId) { + const byteOrder = this.readUint8(); + const isLittleEndian = byteOrder > 0; + + const wkbType = this.readUint32(isLittleEndian); + const wkbTypeThousandth = Math.floor((wkbType & 0x0fffffff) / 1000); + const hasZ = + Boolean(wkbType & 0x80000000) || + wkbTypeThousandth === 1 || + wkbTypeThousandth === 3; + const hasM = + Boolean(wkbType & 0x40000000) || + wkbTypeThousandth === 2 || + wkbTypeThousandth === 3; + const hasSRID = Boolean(wkbType & 0x20000000); + const typeId = (wkbType & 0x0fffffff) % 1000; // Assume 1000 is an upper limit for type ID + const layout = ['XY', hasZ ? 'Z' : '', hasM ? 'M' : ''].join(''); + + const srid = hasSRID ? this.readUint32(isLittleEndian) : null; + + if (expectedTypeId !== undefined && expectedTypeId !== typeId) { + throw new Error('Unexpected WKB geometry type ' + typeId); + } + + if (this.initialized_) { + // sanity checks + if (this.isLittleEndian_ !== isLittleEndian) { + throw new Error('Inconsistent endian'); + } + if (this.layout_ !== layout) { + throw new Error('Inconsistent geometry layout'); + } + if (srid && this.srid_ !== srid) { + throw new Error('Inconsistent coordinate system (SRID)'); + } + } else { + this.isLittleEndian_ = isLittleEndian; + this.hasZ_ = hasZ; + this.hasM_ = hasM; + this.layout_ = layout; + this.srid_ = srid; + this.initialized_ = true; + } + + return typeId; + } + + /** + * @param {number} typeId WKB Type ID + * @return {any} values read + */ + readWkbPayload(typeId) { + switch (typeId) { + case WKBGeometryType.POINT: + return this.readPoint(); + + case WKBGeometryType.LINE_STRING: + return this.readLineString(); + + case WKBGeometryType.POLYGON: + case WKBGeometryType.TRIANGLE: + return this.readPolygon(); + + case WKBGeometryType.MULTI_POINT: + return this.readMultiPoint(); + + case WKBGeometryType.MULTI_LINE_STRING: + return this.readMultiLineString(); + + case WKBGeometryType.MULTI_POLYGON: + case WKBGeometryType.POLYHEDRAL_SURFACE: + case WKBGeometryType.TIN: + return this.readMultiPolygon(); + + case WKBGeometryType.GEOMETRY_COLLECTION: + return this.readGeometryCollection(); + + default: + throw new Error( + 'Unsupported WKB geometry type ' + typeId + ' is found' + ); + } + } + + /** + * @param {number} expectedTypeId Expected WKB Type ID + * @return {any} values read + */ + readWkbBlock(expectedTypeId) { + return this.readWkbPayload(this.readWkbHeader(expectedTypeId)); + } + + /** + * @param {Function} reader reader function for each item + * @param {number} [expectedTypeId] Expected WKB Type ID + * @return {any} values read + */ + readWkbCollection(reader, expectedTypeId) { + const num = this.readUint32(); + + const items = []; + for (let i = 0; i < num; i++) { + const result = reader.call(this, expectedTypeId); + if (result) { + items.push(result); + } + } + + return items; + } + + /** + * @return {Array} coords for MultiPoint + */ + readMultiPoint() { + return this.readWkbCollection(this.readWkbBlock, WKBGeometryType.POINT); + } + + /** + * @return {Array>} coords for MultiLineString like + */ + readMultiLineString() { + return this.readWkbCollection( + this.readWkbBlock, + WKBGeometryType.LINE_STRING + ); + } + + /** + * @return {Array>>} coords for MultiPolygon like + */ + readMultiPolygon() { + return this.readWkbCollection(this.readWkbBlock, WKBGeometryType.POLYGON); + } + + /** + * @return {Array} array of geometries + */ + readGeometryCollection() { + return this.readWkbCollection(this.readGeometry); + } + + /** + * @return {import('../geom/Geometry.js').default} geometry + */ + readGeometry() { + const typeId = this.readWkbHeader(); + const result = this.readWkbPayload(typeId); + + switch (typeId) { + case WKBGeometryType.POINT: + return new Point( + /** @type {import('../coordinate.js').Coordinate} */ (result), + this.layout_ + ); + + case WKBGeometryType.LINE_STRING: + return new LineString( + /** @type {Array} */ (result), + this.layout_ + ); + + case WKBGeometryType.POLYGON: + case WKBGeometryType.TRIANGLE: + return new Polygon( + /** @type {Array>} */ (result), + this.layout_ + ); + + case WKBGeometryType.MULTI_POINT: + return new MultiPoint( + /** @type {Array} */ (result), + this.layout_ + ); + + case WKBGeometryType.MULTI_LINE_STRING: + return new MultiLineString( + /** @type {Array>} */ (result), + this.layout_ + ); + + case WKBGeometryType.MULTI_POLYGON: + case WKBGeometryType.POLYHEDRAL_SURFACE: + case WKBGeometryType.TIN: + return new MultiPolygon( + /** @type {Array>>} */ (result), + this.layout_ + ); + + case WKBGeometryType.GEOMETRY_COLLECTION: + return new GeometryCollection( + /** @type {Array} */ (result) + ); + + default: + return null; + } + } + + /** + * @return {number} SRID in the EWKB. `null` if not defined. + */ + getSrid() { + return this.srid_; + } +} + +class WkbWriter { + /** + * @type {object} + * @property {string} [layout] geometryLayout + * @property {boolean} [littleEndian=true] littleEndian + * @property {boolean} [ewkb=true] Whether writes in EWKB format + * @property {object} [nodata] NoData value for each axes + * @param {object} opts options + */ + constructor(opts) { + opts = opts || {}; + + /** @type {string} */ + this.layout_ = opts.layout; + this.isLittleEndian_ = opts.littleEndian !== false; + + this.isEWKB_ = opts.ewkb !== false; + + /** @type {Array>} */ + this.writeQueue_ = []; + + /** + * @type {object} + * @property {number} X NoData value for X + * @property {number} Y NoData value for Y + * @property {number} Z NoData value for Z + * @property {number} M NoData value for M + */ + this.nodata_ = assign({X: 0, Y: 0, Z: 0, M: 0}, opts.nodata); + } + + /** + * @param {number} value value + */ + writeUint8(value) { + this.writeQueue_.push([1, value]); + } + + /** + * @param {number} value value + */ + writeUint32(value) { + this.writeQueue_.push([4, value]); + } + + /** + * @param {number} value value + */ + writeDouble(value) { + this.writeQueue_.push([8, value]); + } + + /** + * @param {import('../coordinate.js').Coordinate} coords coords + * @param {import("../geom/GeometryLayout").default} layout layout + */ + writePoint(coords, layout) { + /** + * @type {object} + * @property {number} X NoData value for X + * @property {number} Y NoData value for Y + * @property {number} [Z] NoData value for Z + * @property {number} [M] NoData value for M + */ + const coordsObj = assign.apply( + null, + layout.split('').map((axis, idx) => ({[axis]: coords[idx]})) + ); + + for (const axis of this.layout_) { + this.writeDouble( + axis in coordsObj ? coordsObj[axis] : this.nodata_[axis] + ); + } + } + + /** + * @param {Array} coords coords + * @param {import("../geom/GeometryLayout").default} layout layout + */ + writeLineString(coords, layout) { + this.writeUint32(coords.length); // numPoints + for (let i = 0; i < coords.length; i++) { + this.writePoint(coords[i], layout); + } + } + + /** + * @param {Array>} rings rings + * @param {import("../geom/GeometryLayout").default} layout layout + */ + writePolygon(rings, layout) { + this.writeUint32(rings.length); // numRings + for (let i = 0; i < rings.length; i++) { + this.writeLineString(rings[i], layout); // as a LinearRing + } + } + + /** + * @param {number} wkbType WKB Type ID + * @param {number} [srid] SRID + */ + writeWkbHeader(wkbType, srid) { + wkbType %= 1000; // Assume 1000 is an upper limit for type ID + if (this.layout_.indexOf('Z') >= 0) { + wkbType += this.isEWKB_ ? 0x80000000 : 1000; + } + if (this.layout_.indexOf('M') >= 0) { + wkbType += this.isEWKB_ ? 0x40000000 : 2000; + } + if (this.isEWKB_ && Number.isInteger(srid)) { + wkbType |= 0x20000000; + } + + this.writeUint8(this.isLittleEndian_ ? 1 : 0); + this.writeUint32(wkbType); + if (this.isEWKB_ && Number.isInteger(srid)) { + this.writeUint32(srid); + } + } + + /** + * @param {Array} coords coords + * @param {string} layout layout + */ + writeMultiPoint(coords, layout) { + this.writeUint32(coords.length); // numItems + for (let i = 0; i < coords.length; i++) { + this.writeWkbHeader(1); + this.writePoint(coords[i], layout); + } + } + + /** + * @param {Array>} coords coords + * @param {string} layout layout + */ + writeMultiLineString(coords, layout) { + this.writeUint32(coords.length); // numItems + for (let i = 0; i < coords.length; i++) { + this.writeWkbHeader(2); + this.writeLineString(coords[i], layout); + } + } + + /** + * @param {Array>>} coords coords + * @param {string} layout layout + */ + writeMultiPolygon(coords, layout) { + this.writeUint32(coords.length); // numItems + for (let i = 0; i < coords.length; i++) { + this.writeWkbHeader(3); + this.writePolygon(coords[i], layout); + } + } + + /** + * @param {Array} geometries geometries + */ + writeGeometryCollection(geometries) { + this.writeUint32(geometries.length); // numItems + + for (let i = 0; i < geometries.length; i++) { + this.writeGeometry(geometries[i]); + } + } + + /** + * @param {import("../geom/Geometry.js").default} geom geometry + * @param {import("../geom/GeometryLayout.js").default} [layout] layout + * @return {import("../geom/GeometryLayout.js").default} minumum layout made by common axes + */ + findMinimumLayout(geom, layout = GeometryLayout.XYZM) { + /** + * @param {import("../geom/GeometryLayout.js").default} a A + * @param {import("../geom/GeometryLayout.js").default} b B + * @return {import("../geom/GeometryLayout.js").default} minumum layout made by common axes + */ + const GeometryLayout_min = (a, b) => { + if (a === b) { + return a; + } + + if (a === GeometryLayout.XYZM) { + // anything `b` is minimum + return b; + } + if (b === GeometryLayout.XYZM) { + // anything `a` is minimum + return a; + } + + // otherwise, incompatible + return GeometryLayout.XY; + }; + + if (geom instanceof SimpleGeometry) { + return GeometryLayout_min(geom.getLayout(), layout); + } + + if (geom instanceof GeometryCollection) { + const geoms = geom.getGeometriesArray(); + for (let i = 0; i < geoms.length && layout !== GeometryLayout.XY; i++) { + layout = this.findMinimumLayout(geoms[i], layout); + } + } + + return layout; + } + + /** + * @param {import("../geom/Geometry.js").default} geom geometry + * @param {number} [srid] SRID + */ + writeGeometry(geom, srid) { + const wkblut = { + [GeometryType.POINT]: WKBGeometryType.POINT, + [GeometryType.LINE_STRING]: WKBGeometryType.LINE_STRING, + [GeometryType.POLYGON]: WKBGeometryType.POLYGON, + [GeometryType.MULTI_POINT]: WKBGeometryType.MULTI_POINT, + [GeometryType.MULTI_LINE_STRING]: WKBGeometryType.MULTI_LINE_STRING, + [GeometryType.MULTI_POLYGON]: WKBGeometryType.MULTI_POLYGON, + [GeometryType.GEOMETRY_COLLECTION]: WKBGeometryType.GEOMETRY_COLLECTION, + }; + const geomType = geom.getType(); + const typeId = wkblut[geomType]; + + if (!typeId) { + throw new Error('GeometryType ' + geomType + ' is not supported'); + } + + // first call of writeGeometry() traverse whole geometries to determine its output layout if not specified on constructor. + if (!this.layout_) { + this.layout_ = this.findMinimumLayout(geom); + } + + this.writeWkbHeader(typeId, srid); + + if (geom instanceof SimpleGeometry) { + const writerLUT = { + [GeometryType.POINT]: this.writePoint, + [GeometryType.LINE_STRING]: this.writeLineString, + [GeometryType.POLYGON]: this.writePolygon, + [GeometryType.MULTI_POINT]: this.writeMultiPoint, + [GeometryType.MULTI_LINE_STRING]: this.writeMultiLineString, + [GeometryType.MULTI_POLYGON]: this.writeMultiPolygon, + }; + writerLUT[geomType].call(this, geom.getCoordinates(), geom.getLayout()); + } else if (geom instanceof GeometryCollection) { + this.writeGeometryCollection(geom.getGeometriesArray()); + } + } + + getBuffer() { + const byteLength = this.writeQueue_.reduce((acc, item) => acc + item[0], 0); + const buffer = new ArrayBuffer(byteLength); + const view = new DataView(buffer); + + let pos = 0; + this.writeQueue_.forEach((item) => { + switch (item[0]) { + case 1: + view.setUint8(pos, item[1]); + break; + case 4: + view.setUint32(pos, item[1], this.isLittleEndian_); + break; + case 8: + view.setFloat64(pos, item[1], this.isLittleEndian_); + break; + default: + break; + } + + pos += item[0]; + }); + + return buffer; + } +} + +/** + * @typedef {Object} Options + * @property {boolean} [splitCollection=false] Whether to split GeometryCollections into multiple features on reading. + * @property {boolean} [hex=true] Returns hex string instead of ArrayBuffer for output. This also is used as a hint internally whether it should load contents as text or ArrayBuffer on reading. + * @property {boolean} [littleEndian=true] Use littleEndian for output. + * @property {boolean} [ewkb=true] Use EWKB format for output. + * @property {import("../geom/GeometryLayout").default} [geometryLayout=null] Use specific coordinate layout for output features (null: auto detect) + * @property {number} [nodataZ=0] If the `geometryLayout` doesn't match with geometry to be output, this value is used to fill missing coordinate value of Z. + * @property {number} [nodataM=0] If the `geometryLayout` doesn't match with geometry to be output, this value is used to fill missing coordinate value of M. + * @property {number|boolean} [srid=true] SRID for output. Specify integer value to enforce the value as a SRID. Specify `true` to extract from `dataProjection`. `false` to suppress the output. This option only takes effect when `ewkb` is `true`. + */ + +/** + * @classdesc + * Geometry format for reading and writing data in the `Well-Known Binary` (WKB) format. + * Also supports `Extended Well-Known Binary` (EWKB) format, used in PostGIS for example. + * + * @api + */ +class WKB extends FeatureFormat { + /** + * @param {Options} [opt_options] Optional configuration object. + */ + constructor(opt_options) { + super(); + + const options = opt_options ? opt_options : {}; + + this.splitCollection = Boolean(options.splitCollection); + + this.viewCache_ = null; + + this.hex_ = options.hex !== false; + this.littleEndian_ = options.littleEndian !== false; + this.ewkb_ = options.ewkb !== false; + + this.layout_ = options.geometryLayout; // null for auto detect + this.nodataZ_ = options.nodataZ || 0; + this.nodataM_ = options.nodataM || 0; + + this.srid_ = options.srid; + } + + /** + * @return {import("./FormatType.js").default} Format. + */ + getType() { + return this.hex_ ? FormatType.TEXT : FormatType.ARRAY_BUFFER; + } + + /** + * Read a single feature from a source. + * + * @param {string|ArrayBuffer|ArrayBufferView} source Source. + * @param {import("./Feature.js").ReadOptions} [opt_options] Read options. + * @return {import("../Feature.js").FeatureLike} Feature. + * @api + */ + readFeature(source, opt_options) { + return new Feature({ + geometry: this.readGeometry(source, opt_options), + }); + } + + /** + * Read all features from a source. + * + * @param {string|ArrayBuffer|ArrayBufferView} source Source. + * @param {import("./Feature.js").ReadOptions} [opt_options] Read options. + * @return {Array} Features. + * @api + */ + readFeatures(source, opt_options) { + let geometries = []; + const geometry = this.readGeometry(source, opt_options); + if (this.splitCollection && geometry instanceof GeometryCollection) { + geometries = geometry.getGeometriesArray(); + } else { + geometries = [geometry]; + } + return geometries.map((geometry) => new Feature({geometry})); + } + + /** + * Read a single geometry from a source. + * + * @param {string|ArrayBuffer|ArrayBufferView} source Source. + * @param {import("./Feature.js").ReadOptions} [opt_options] Read options. + * @return {import("../geom/Geometry.js").default} Geometry. + * @api + */ + readGeometry(source, opt_options) { + const view = getDataView(source); + if (!view) { + return null; + } + + const reader = new WkbReader(view); + const geometry = reader.readGeometry(); + + this.viewCache_ = view; // cache for internal subsequent call of readProjection() + const options = this.getReadOptions(source, opt_options); + this.viewCache_ = null; // release + + return transformGeometryWithOptions(geometry, false, options); + } + + /** + * Read the projection from a source. + * + * @param {string|ArrayBuffer|ArrayBufferView} source Source. + * @return {import("../proj/Projection.js").default|undefined} Projection. + * @api + */ + readProjection(source) { + const view = this.viewCache_ || getDataView(source); + if (!view) { + return undefined; + } + + const reader = new WkbReader(view); + reader.readWkbHeader(); + + return ( + (reader.getSrid() && getProjection('EPSG:' + reader.getSrid())) || + undefined + ); + } + + /** + * Encode a feature in this format. + * + * @param {import("../Feature.js").default} feature Feature. + * @param {import("./Feature.js").WriteOptions} [opt_options] Write options. + * @return {string|ArrayBuffer} Result. + * @api + */ + writeFeature(feature, opt_options) { + return this.writeGeometry(feature.getGeometry(), opt_options); + } + + /** + * Encode an array of features in this format. + * + * @param {Array} features Features. + * @param {import("./Feature.js").WriteOptions} [opt_options] Write options. + * @return {string|ArrayBuffer} Result. + * @api + */ + writeFeatures(features, opt_options) { + return this.writeGeometry( + new GeometryCollection(features.map((f) => f.getGeometry())), + opt_options + ); + } + + /** + * Write a single geometry in this format. + * + * @param {import("../geom/Geometry.js").default} geometry Geometry. + * @param {import("./Feature.js").WriteOptions} [opt_options] Write options. + * @return {string|ArrayBuffer} Result. + * @api + */ + writeGeometry(geometry, opt_options) { + const options = this.adaptOptions(opt_options); + + const writer = new WkbWriter({ + layout: this.layout_, + littleEndian: this.littleEndian_, + ewkb: this.ewkb_, + + nodata: { + Z: this.nodataZ_, + M: this.nodataM_, + }, + }); + + // extract SRID from `dataProjection` + let srid = Number.isInteger(this.srid_) ? Number(this.srid_) : null; + if (this.srid_ !== false && !Number.isInteger(this.srid_)) { + const dataProjection = + options.dataProjection && getProjection(options.dataProjection); + if (dataProjection) { + const code = dataProjection.getCode(); + if (code.indexOf('EPSG:') === 0) { + srid = Number(code.substring(5)); + } + } + } + + writer.writeGeometry( + transformGeometryWithOptions(geometry, true, options), + srid + ); + const buffer = writer.getBuffer(); + + return this.hex_ ? encodeHexString(buffer) : buffer; + } +} + +/** + * @param {ArrayBuffer} buffer source buffer + * @return {string} encoded hex string + */ +function encodeHexString(buffer) { + const view = new Uint8Array(buffer); + return Array.from(view.values()) + .map((x) => (x < 16 ? '0' : '') + Number(x).toString(16).toUpperCase()) + .join(''); +} + +/** + * @param {string} text source text + * @return {DataView} decoded binary buffer + */ +function decodeHexString(text) { + const buffer = new Uint8Array(text.length / 2); + for (let i = 0; i < text.length / 2; i++) { + buffer[i] = parseInt(text.substr(i * 2, 2), 16); + } + return new DataView(buffer.buffer); +} + +/** + * @param {string | ArrayBuffer | ArrayBufferView} source source + * @return {DataView} data view + */ +function getDataView(source) { + if (typeof source === 'string') { + return decodeHexString(source); + } else if (ArrayBuffer.isView(source)) { + if (source instanceof DataView) { + return source; + } + return new DataView(source.buffer, source.byteOffset, source.byteLength); + } else if (source instanceof ArrayBuffer) { + return new DataView(source); + } else { + return null; + } +} + +export default WKB; diff --git a/test/spec/ol/format/wkb.test.js b/test/spec/ol/format/wkb.test.js new file mode 100644 index 0000000000..dcd2bf51a2 --- /dev/null +++ b/test/spec/ol/format/wkb.test.js @@ -0,0 +1,1450 @@ +import Feature from '../../../../src/ol/Feature.js'; +import GeometryCollection from '../../../../src/ol/geom/GeometryCollection.js'; +import Point from '../../../../src/ol/geom/Point.js'; +import SimpleGeometry from '../../../../src/ol/geom/SimpleGeometry.js'; +import WKB from '../../../../src/ol/format/WKB.js'; +import WKT from '../../../../src/ol/format/WKT.js'; +import {transform} from '../../../../src/ol/proj.js'; + +const patterns = [ + [ + '010100000000000000000024400000000000002440', + 'POINT(10 10)', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000140240000000000004024000000000000', + 'POINT(10 10)', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '01020000000300000000000000000024400000000000002440000000000000344000000000000034400000000000003e400000000000004440', + 'LINESTRING(10 10,20 20,30 40)', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '0000000002000000034024000000000000402400000000000040340000000000004034000000000000403e0000000000004044000000000000', + 'LINESTRING(10 10,20 20,30 40)', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '0103000000010000000500000000000000000024400000000000002440000000000000244000000000000034400000000000003440000000000000344000000000000034400000000000002e4000000000000024400000000000002440', + 'POLYGON((10 10,10 20,20 20,20 15,10 10))', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000300000001000000054024000000000000402400000000000040240000000000004034000000000000403400000000000040340000000000004034000000000000402e00000000000040240000000000004024000000000000', + 'POLYGON((10 10,10 20,20 20,20 15,10 10))', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010400000002000000010100000000000000000024400000000000002440010100000000000000000034400000000000003440', + 'MULTIPOINT(10 10,20 20)', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000400000002000000000140240000000000004024000000000000000000000140340000000000004034000000000000', + 'MULTIPOINT(10 10,20 20)', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '01050000000200000001020000000200000000000000000024400000000000002440000000000000344000000000000034400102000000020000000000000000002e400000000000002e400000000000003e400000000000002e40', + 'MULTILINESTRING((10 10,20 20),(15 15,30 15))', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '0000000005000000020000000002000000024024000000000000402400000000000040340000000000004034000000000000000000000200000002402e000000000000402e000000000000403e000000000000402e000000000000', + 'MULTILINESTRING((10 10,20 20),(15 15,30 15))', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '0106000000020000000103000000010000000500000000000000000024400000000000002440000000000000244000000000000034400000000000003440000000000000344000000000000034400000000000002e4000000000000024400000000000002440010300000001000000040000000000000000004e400000000000004e400000000000805140000000000080514000000000000054400000000000004e400000000000004e400000000000004e40', + 'MULTIPOLYGON(((10 10,10 20,20 20,20 15,10 10)),((60 60,70 70,80 60,60 60)))', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000600000002000000000300000001000000054024000000000000402400000000000040240000000000004034000000000000403400000000000040340000000000004034000000000000402e0000000000004024000000000000402400000000000000000000030000000100000004404e000000000000404e000000000000405180000000000040518000000000004054000000000000404e000000000000404e000000000000404e000000000000', + 'MULTIPOLYGON(((10 10,10 20,20 20,20 15,10 10)),((60 60,70 70,80 60,60 60)))', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '01070000000300000001010000000000000000002440000000000000244001010000000000000000003e400000000000003e400102000000020000000000000000002e400000000000002e4000000000000034400000000000003440', + 'GEOMETRYCOLLECTION(POINT(10 10),POINT(30 30),LINESTRING(15 15,20 20))', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '0000000007000000030000000001402400000000000040240000000000000000000001403e000000000000403e000000000000000000000200000002402e000000000000402e00000000000040340000000000004034000000000000', + 'GEOMETRYCOLLECTION(POINT(10 10),POINT(30 30),LINESTRING(15 15,20 20))', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + + [ + '01e9030000000000000000244000000000000024400000000000001440', + 'POINT Z (10 10 5)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003e9402400000000000040240000000000004014000000000000', + 'POINT Z (10 10 5)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0101000080000000000000244000000000000024400000000000001440', + 'POINT Z (10 10 5)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '0080000001402400000000000040240000000000004014000000000000', + 'POINT Z (10 10 5)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ea030000030000000000000000002440000000000000244000000000000014400000000000003440000000000000344000000000000010400000000000003e4000000000000044400000000000000840', + 'LINESTRING Z (10 10 5,20 20 4,30 40 3)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ea00000003402400000000000040240000000000004014000000000000403400000000000040340000000000004010000000000000403e00000000000040440000000000004008000000000000', + 'LINESTRING Z (10 10 5,20 20 4,30 40 3)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0102000080030000000000000000002440000000000000244000000000000014400000000000003440000000000000344000000000000010400000000000003e4000000000000044400000000000000840', + 'LINESTRING Z (10 10 5,20 20 4,30 40 3)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000200000003402400000000000040240000000000004014000000000000403400000000000040340000000000004010000000000000403e00000000000040440000000000004008000000000000', + 'LINESTRING Z (10 10 5,20 20 4,30 40 3)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01eb030000010000000500000000000000000024400000000000002440000000000000144000000000000024400000000000003440000000000000104000000000000034400000000000003440000000000000084000000000000034400000000000002e40000000000000004000000000000024400000000000002440000000000000f03f', + 'POLYGON Z ((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003eb00000001000000054024000000000000402400000000000040140000000000004024000000000000403400000000000040100000000000004034000000000000403400000000000040080000000000004034000000000000402e0000000000004000000000000000402400000000000040240000000000003ff0000000000000', + 'POLYGON Z ((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0103000080010000000500000000000000000024400000000000002440000000000000144000000000000024400000000000003440000000000000104000000000000034400000000000003440000000000000084000000000000034400000000000002e40000000000000004000000000000024400000000000002440000000000000f03f', + 'POLYGON Z ((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000300000001000000054024000000000000402400000000000040140000000000004024000000000000403400000000000040100000000000004034000000000000403400000000000040080000000000004034000000000000402e0000000000004000000000000000402400000000000040240000000000003ff0000000000000', + 'POLYGON Z ((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ec0300000200000001e903000000000000000024400000000000002440000000000000144001e9030000000000000000344000000000000034400000000000001440', + 'MULTIPOINT Z (10 10 5,20 20 5)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ec0000000200000003e940240000000000004024000000000000401400000000000000000003e9403400000000000040340000000000004014000000000000', + 'MULTIPOINT Z (10 10 5,20 20 5)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '01040000800200000001010000800000000000002440000000000000244000000000000014400101000080000000000000344000000000000034400000000000001440', + 'MULTIPOINT Z (10 10 5,20 20 5)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '00800000040000000200800000014024000000000000402400000000000040140000000000000080000001403400000000000040340000000000004014000000000000', + 'MULTIPOINT Z (10 10 5,20 20 5)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ed0300000200000001ea0300000200000000000000000024400000000000002440000000000000144000000000000034400000000000003440000000000000104001ea030000020000000000000000002e400000000000002e4000000000000008400000000000003e400000000000002e400000000000000040', + 'MULTILINESTRING Z ((10 10 5,20 20 4),(15 15 3,30 15 2))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ed0000000200000003ea0000000240240000000000004024000000000000401400000000000040340000000000004034000000000000401000000000000000000003ea00000002402e000000000000402e0000000000004008000000000000403e000000000000402e0000000000004000000000000000', + 'MULTILINESTRING Z ((10 10 5,20 20 4),(15 15 3,30 15 2))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0105000080020000000102000080020000000000000000002440000000000000244000000000000014400000000000003440000000000000344000000000000010400102000080020000000000000000002e400000000000002e4000000000000008400000000000003e400000000000002e400000000000000040', + 'MULTILINESTRING Z ((10 10 5,20 20 4),(15 15 3,30 15 2))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000500000002008000000200000002402400000000000040240000000000004014000000000000403400000000000040340000000000004010000000000000008000000200000002402e000000000000402e0000000000004008000000000000403e000000000000402e0000000000004000000000000000', + 'MULTILINESTRING Z ((10 10 5,20 20 4),(15 15 3,30 15 2))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ee0300000200000001eb030000010000000500000000000000000024400000000000002440000000000000144000000000000024400000000000003440000000000000104000000000000034400000000000003440000000000000084000000000000034400000000000002e40000000000000004000000000000024400000000000002440000000000000f03f01eb03000001000000040000000000000000004e400000000000004e40000000000000144000000000008051400000000000805140000000000000104000000000000054400000000000004e4000000000000008400000000000004e400000000000004e400000000000000040', + 'MULTIPOLYGON Z (((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1)),((60 60 5,70 70 4,80 60 3,60 60 2)))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ee0000000200000003eb00000001000000054024000000000000402400000000000040140000000000004024000000000000403400000000000040100000000000004034000000000000403400000000000040080000000000004034000000000000402e0000000000004000000000000000402400000000000040240000000000003ff000000000000000000003eb0000000100000004404e000000000000404e00000000000040140000000000004051800000000000405180000000000040100000000000004054000000000000404e0000000000004008000000000000404e000000000000404e0000000000004000000000000000', + 'MULTIPOLYGON Z (((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1)),((60 60 5,70 70 4,80 60 3,60 60 2)))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0106000080020000000103000080010000000500000000000000000024400000000000002440000000000000144000000000000024400000000000003440000000000000104000000000000034400000000000003440000000000000084000000000000034400000000000002e40000000000000004000000000000024400000000000002440000000000000f03f010300008001000000040000000000000000004e400000000000004e40000000000000144000000000008051400000000000805140000000000000104000000000000054400000000000004e4000000000000008400000000000004e400000000000004e400000000000000040', + 'MULTIPOLYGON Z (((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1)),((60 60 5,70 70 4,80 60 3,60 60 2)))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000600000002008000000300000001000000054024000000000000402400000000000040140000000000004024000000000000403400000000000040100000000000004034000000000000403400000000000040080000000000004034000000000000402e0000000000004000000000000000402400000000000040240000000000003ff000000000000000800000030000000100000004404e000000000000404e00000000000040140000000000004051800000000000405180000000000040100000000000004054000000000000404e0000000000004008000000000000404e000000000000404e0000000000004000000000000000', + 'MULTIPOLYGON Z (((10 10 5,10 20 4,20 20 3,20 15 2,10 10 1)),((60 60 5,70 70 4,80 60 3,60 60 2)))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ef0300000300000001e903000000000000000024400000000000002440000000000000144001e90300000000000000003e400000000000003e40000000000000144001ea030000020000000000000000002e400000000000002e400000000000001440000000000000344000000000000034400000000000001440', + 'GEOMETRYCOLLECTION Z (POINT Z (10 10 5),POINT Z (30 30 5),LINESTRING Z (15 15 5,20 20 5))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ef0000000300000003e940240000000000004024000000000000401400000000000000000003e9403e000000000000403e000000000000401400000000000000000003ea00000002402e000000000000402e0000000000004014000000000000403400000000000040340000000000004014000000000000', + 'GEOMETRYCOLLECTION Z (POINT Z (10 10 5),POINT Z (30 30 5),LINESTRING Z (15 15 5,20 20 5))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010700008003000000010100008000000000000024400000000000002440000000000000144001010000800000000000003e400000000000003e4000000000000014400102000080020000000000000000002e400000000000002e400000000000001440000000000000344000000000000034400000000000001440', + 'GEOMETRYCOLLECTION Z (POINT Z (10 10 5),POINT Z (30 30 5),LINESTRING Z (15 15 5,20 20 5))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '00800000070000000300800000014024000000000000402400000000000040140000000000000080000001403e000000000000403e0000000000004014000000000000008000000200000002402e000000000000402e0000000000004014000000000000403400000000000040340000000000004014000000000000', + 'GEOMETRYCOLLECTION Z (POINT Z (10 10 5),POINT Z (30 30 5),LINESTRING Z (15 15 5,20 20 5))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + + [ + '01d1070000000000000000244000000000000024400000000000804640', + 'POINT M (10 10 45)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d1402400000000000040240000000000004046800000000000', + 'POINT M (10 10 45)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0101000040000000000000244000000000000024400000000000804640', + 'POINT M (10 10 45)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '0040000001402400000000000040240000000000004046800000000000', + 'POINT M (10 10 45)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d2070000030000000000000000002440000000000000244000000000008046400000000000003440000000000000344000000000000046400000000000003e4000000000000044400000000000804540', + 'LINESTRING M (10 10 45,20 20 44,30 40 43)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d200000003402400000000000040240000000000004046800000000000403400000000000040340000000000004046000000000000403e00000000000040440000000000004045800000000000', + 'LINESTRING M (10 10 45,20 20 44,30 40 43)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0102000040030000000000000000002440000000000000244000000000008046400000000000003440000000000000344000000000000046400000000000003e4000000000000044400000000000804540', + 'LINESTRING M (10 10 45,20 20 44,30 40 43)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000200000003402400000000000040240000000000004046800000000000403400000000000040340000000000004046000000000000403e00000000000040440000000000004045800000000000', + 'LINESTRING M (10 10 45,20 20 44,30 40 43)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d3070000010000000500000000000000000024400000000000002440000000000080464000000000000024400000000000003440000000000000464000000000000034400000000000003440000000000080454000000000000034400000000000002e400000000000004540000000000000244000000000000024400000000000804440', + 'POLYGON M ((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d300000001000000054024000000000000402400000000000040468000000000004024000000000000403400000000000040460000000000004034000000000000403400000000000040458000000000004034000000000000402e0000000000004045000000000000402400000000000040240000000000004044800000000000', + 'POLYGON M ((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0103000040010000000500000000000000000024400000000000002440000000000080464000000000000024400000000000003440000000000000464000000000000034400000000000003440000000000080454000000000000034400000000000002e400000000000004540000000000000244000000000000024400000000000804440', + 'POLYGON M ((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000300000001000000054024000000000000402400000000000040468000000000004024000000000000403400000000000040460000000000004034000000000000403400000000000040458000000000004034000000000000402e0000000000004045000000000000402400000000000040240000000000004044800000000000', + 'POLYGON M ((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d40700000200000001d107000000000000000024400000000000002440000000000080464001d1070000000000000000344000000000000034400000000000804640', + 'MULTIPOINT M (10 10 45,20 20 45)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d40000000200000007d140240000000000004024000000000000404680000000000000000007d1403400000000000040340000000000004046800000000000', + 'MULTIPOINT M (10 10 45,20 20 45)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '01040000400200000001010000400000000000002440000000000000244000000000008046400101000040000000000000344000000000000034400000000000804640', + 'MULTIPOINT M (10 10 45,20 20 45)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '00400000040000000200400000014024000000000000402400000000000040468000000000000040000001403400000000000040340000000000004046800000000000', + 'MULTIPOINT M (10 10 45,20 20 45)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d50700000200000001d20700000200000000000000000024400000000000002440000000000080464000000000000034400000000000003440000000000000464001d2070000020000000000000000002e400000000000002e4000000000008045400000000000003e400000000000002e400000000000004540', + 'MULTILINESTRING M ((10 10 45,20 20 44),(15 15 43,30 15 42))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d50000000200000007d20000000240240000000000004024000000000000404680000000000040340000000000004034000000000000404600000000000000000007d200000002402e000000000000402e0000000000004045800000000000403e000000000000402e0000000000004045000000000000', + 'MULTILINESTRING M ((10 10 45,20 20 44),(15 15 43,30 15 42))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0105000040020000000102000040020000000000000000002440000000000000244000000000008046400000000000003440000000000000344000000000000046400102000040020000000000000000002e400000000000002e4000000000008045400000000000003e400000000000002e400000000000004540', + 'MULTILINESTRING M ((10 10 45,20 20 44),(15 15 43,30 15 42))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000500000002004000000200000002402400000000000040240000000000004046800000000000403400000000000040340000000000004046000000000000004000000200000002402e000000000000402e0000000000004045800000000000403e000000000000402e0000000000004045000000000000', + 'MULTILINESTRING M ((10 10 45,20 20 44),(15 15 43,30 15 42))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d60700000200000001d3070000010000000500000000000000000024400000000000002440000000000080464000000000000024400000000000003440000000000000464000000000000034400000000000003440000000000080454000000000000034400000000000002e40000000000000454000000000000024400000000000002440000000000080444001d307000001000000040000000000000000004e400000000000004e40000000000080464000000000008051400000000000805140000000000000464000000000000054400000000000004e4000000000008045400000000000004e400000000000004e400000000000004540', + 'MULTIPOLYGON M (((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41)),((60 60 45,70 70 44,80 60 43,60 60 42)))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d60000000200000007d300000001000000054024000000000000402400000000000040468000000000004024000000000000403400000000000040460000000000004034000000000000403400000000000040458000000000004034000000000000402e000000000000404500000000000040240000000000004024000000000000404480000000000000000007d30000000100000004404e000000000000404e00000000000040468000000000004051800000000000405180000000000040460000000000004054000000000000404e0000000000004045800000000000404e000000000000404e0000000000004045000000000000', + 'MULTIPOLYGON M (((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41)),((60 60 45,70 70 44,80 60 43,60 60 42)))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0106000040020000000103000040010000000500000000000000000024400000000000002440000000000080464000000000000024400000000000003440000000000000464000000000000034400000000000003440000000000080454000000000000034400000000000002e400000000000004540000000000000244000000000000024400000000000804440010300004001000000040000000000000000004e400000000000004e40000000000080464000000000008051400000000000805140000000000000464000000000000054400000000000004e4000000000008045400000000000004e400000000000004e400000000000004540', + 'MULTIPOLYGON M (((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41)),((60 60 45,70 70 44,80 60 43,60 60 42)))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000600000002004000000300000001000000054024000000000000402400000000000040468000000000004024000000000000403400000000000040460000000000004034000000000000403400000000000040458000000000004034000000000000402e000000000000404500000000000040240000000000004024000000000000404480000000000000400000030000000100000004404e000000000000404e00000000000040468000000000004051800000000000405180000000000040460000000000004054000000000000404e0000000000004045800000000000404e000000000000404e0000000000004045000000000000', + 'MULTIPOLYGON M (((10 10 45,10 20 44,20 20 43,20 15 42,10 10 41)),((60 60 45,70 70 44,80 60 43,60 60 42)))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d70700000300000001d107000000000000000024400000000000002440000000000080464001d10700000000000000003e400000000000003e40000000000080464001d2070000020000000000000000002e400000000000002e400000000000804640000000000000344000000000000034400000000000804640', + 'GEOMETRYCOLLECTION M (POINT M (10 10 45),POINT M (30 30 45),LINESTRING M (15 15 45,20 20 45))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d70000000300000007d140240000000000004024000000000000404680000000000000000007d1403e000000000000403e000000000000404680000000000000000007d200000002402e000000000000402e0000000000004046800000000000403400000000000040340000000000004046800000000000', + 'GEOMETRYCOLLECTION M (POINT M (10 10 45),POINT M (30 30 45),LINESTRING M (15 15 45,20 20 45))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010700004003000000010100004000000000000024400000000000002440000000000080464001010000400000000000003e400000000000003e4000000000008046400102000040020000000000000000002e400000000000002e400000000000804640000000000000344000000000000034400000000000804640', + 'GEOMETRYCOLLECTION M (POINT M (10 10 45),POINT M (30 30 45),LINESTRING M (15 15 45,20 20 45))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '00400000070000000300400000014024000000000000402400000000000040468000000000000040000001403e000000000000403e0000000000004046800000000000004000000200000002402e000000000000402e0000000000004046800000000000403400000000000040340000000000004046800000000000', + 'GEOMETRYCOLLECTION M (POINT M (10 10 45),POINT M (30 30 45),LINESTRING M (15 15 45,20 20 45))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + + [ + '01b90b00000000000000002440000000000000244000000000000014400000000000804640', + 'POINT ZM (10 10 5 45)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bb94024000000000000402400000000000040140000000000004046800000000000', + 'POINT ZM (10 10 5 45)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01010000c00000000000002440000000000000244000000000000014400000000000804640', + 'POINT ZM (10 10 5 45)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000014024000000000000402400000000000040140000000000004046800000000000', + 'POINT ZM (10 10 5 45)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01ba0b000003000000000000000000244000000000000024400000000000001440000000000080464000000000000034400000000000003440000000000000104000000000000046400000000000003e40000000000000444000000000000008400000000000804540', + 'LINESTRING ZM (10 10 5 45,20 20 4 44,30 40 3 43)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bba0000000340240000000000004024000000000000401400000000000040468000000000004034000000000000403400000000000040100000000000004046000000000000403e000000000000404400000000000040080000000000004045800000000000', + 'LINESTRING ZM (10 10 5 45,20 20 4 44,30 40 3 43)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01020000c003000000000000000000244000000000000024400000000000001440000000000080464000000000000034400000000000003440000000000000104000000000000046400000000000003e40000000000000444000000000000008400000000000804540', + 'LINESTRING ZM (10 10 5 45,20 20 4 44,30 40 3 43)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000020000000340240000000000004024000000000000401400000000000040468000000000004034000000000000403400000000000040100000000000004046000000000000403e000000000000404400000000000040080000000000004045800000000000', + 'LINESTRING ZM (10 10 5 45,20 20 4 44,30 40 3 43)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bb0b0000010000000500000000000000000024400000000000002440000000000000144000000000008046400000000000002440000000000000344000000000000010400000000000004640000000000000344000000000000034400000000000000840000000000080454000000000000034400000000000002e400000000000000040000000000000454000000000000024400000000000002440000000000000f03f0000000000804440', + 'POLYGON ZM ((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbb00000001000000054024000000000000402400000000000040140000000000004046800000000000402400000000000040340000000000004010000000000000404600000000000040340000000000004034000000000000400800000000000040458000000000004034000000000000402e00000000000040000000000000004045000000000000402400000000000040240000000000003ff00000000000004044800000000000', + 'POLYGON ZM ((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01030000c0010000000500000000000000000024400000000000002440000000000000144000000000008046400000000000002440000000000000344000000000000010400000000000004640000000000000344000000000000034400000000000000840000000000080454000000000000034400000000000002e400000000000000040000000000000454000000000000024400000000000002440000000000000f03f0000000000804440', + 'POLYGON ZM ((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000300000001000000054024000000000000402400000000000040140000000000004046800000000000402400000000000040340000000000004010000000000000404600000000000040340000000000004034000000000000400800000000000040458000000000004034000000000000402e00000000000040000000000000004045000000000000402400000000000040240000000000003ff00000000000004044800000000000', + 'POLYGON ZM ((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bc0b00000200000001b90b0000000000000000244000000000000024400000000000001440000000000080464001b90b00000000000000003440000000000000344000000000000014400000000000804640', + 'MULTIPOINT ZM (10 10 5 45,20 20 5 45)', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbc000000020000000bb940240000000000004024000000000000401400000000000040468000000000000000000bb94034000000000000403400000000000040140000000000004046800000000000', + 'MULTIPOINT ZM (10 10 5 45,20 20 5 45)', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01040000c00200000001010000c0000000000000244000000000000024400000000000001440000000000080464001010000c00000000000003440000000000000344000000000000014400000000000804640', + 'MULTIPOINT ZM (10 10 5 45,20 20 5 45)', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000040000000200c0000001402400000000000040240000000000004014000000000000404680000000000000c00000014034000000000000403400000000000040140000000000004046800000000000', + 'MULTIPOINT ZM (10 10 5 45,20 20 5 45)', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bd0b00000200000001ba0b0000020000000000000000002440000000000000244000000000000014400000000000804640000000000000344000000000000034400000000000001040000000000000464001ba0b0000020000000000000000002e400000000000002e40000000000000084000000000008045400000000000003e400000000000002e4000000000000000400000000000004540', + 'MULTILINESTRING ZM ((10 10 5 45,20 20 4 44),(15 15 3 43,30 15 2 42))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbd000000020000000bba00000002402400000000000040240000000000004014000000000000404680000000000040340000000000004034000000000000401000000000000040460000000000000000000bba00000002402e000000000000402e00000000000040080000000000004045800000000000403e000000000000402e00000000000040000000000000004045000000000000', + 'MULTILINESTRING ZM ((10 10 5 45,20 20 4 44),(15 15 3 43,30 15 2 42))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01050000c00200000001020000c0020000000000000000002440000000000000244000000000000014400000000000804640000000000000344000000000000034400000000000001040000000000000464001020000c0020000000000000000002e400000000000002e40000000000000084000000000008045400000000000003e400000000000002e4000000000000000400000000000004540', + 'MULTILINESTRING ZM ((10 10 5 45,20 20 4 44),(15 15 3 43,30 15 2 42))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000050000000200c0000002000000024024000000000000402400000000000040140000000000004046800000000000403400000000000040340000000000004010000000000000404600000000000000c000000200000002402e000000000000402e00000000000040080000000000004045800000000000403e000000000000402e00000000000040000000000000004045000000000000', + 'MULTILINESTRING ZM ((10 10 5 45,20 20 4 44),(15 15 3 43,30 15 2 42))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01be0b00000200000001bb0b0000010000000500000000000000000024400000000000002440000000000000144000000000008046400000000000002440000000000000344000000000000010400000000000004640000000000000344000000000000034400000000000000840000000000080454000000000000034400000000000002e400000000000000040000000000000454000000000000024400000000000002440000000000000f03f000000000080444001bb0b000001000000040000000000000000004e400000000000004e4000000000000014400000000000804640000000000080514000000000008051400000000000001040000000000000464000000000000054400000000000004e40000000000000084000000000008045400000000000004e400000000000004e4000000000000000400000000000004540', + 'MULTIPOLYGON ZM (((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41)),((60 60 5 45,70 70 4 44,80 60 3 43,60 60 2 42)))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbe000000020000000bbb00000001000000054024000000000000402400000000000040140000000000004046800000000000402400000000000040340000000000004010000000000000404600000000000040340000000000004034000000000000400800000000000040458000000000004034000000000000402e00000000000040000000000000004045000000000000402400000000000040240000000000003ff000000000000040448000000000000000000bbb0000000100000004404e000000000000404e0000000000004014000000000000404680000000000040518000000000004051800000000000401000000000000040460000000000004054000000000000404e00000000000040080000000000004045800000000000404e000000000000404e00000000000040000000000000004045000000000000', + 'MULTIPOLYGON ZM (((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41)),((60 60 5 45,70 70 4 44,80 60 3 43,60 60 2 42)))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01060000c00200000001030000c0010000000500000000000000000024400000000000002440000000000000144000000000008046400000000000002440000000000000344000000000000010400000000000004640000000000000344000000000000034400000000000000840000000000080454000000000000034400000000000002e400000000000000040000000000000454000000000000024400000000000002440000000000000f03f000000000080444001030000c001000000040000000000000000004e400000000000004e4000000000000014400000000000804640000000000080514000000000008051400000000000001040000000000000464000000000000054400000000000004e40000000000000084000000000008045400000000000004e400000000000004e4000000000000000400000000000004540', + 'MULTIPOLYGON ZM (((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41)),((60 60 5 45,70 70 4 44,80 60 3 43,60 60 2 42)))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000060000000200c000000300000001000000054024000000000000402400000000000040140000000000004046800000000000402400000000000040340000000000004010000000000000404600000000000040340000000000004034000000000000400800000000000040458000000000004034000000000000402e00000000000040000000000000004045000000000000402400000000000040240000000000003ff0000000000000404480000000000000c00000030000000100000004404e000000000000404e0000000000004014000000000000404680000000000040518000000000004051800000000000401000000000000040460000000000004054000000000000404e00000000000040080000000000004045800000000000404e000000000000404e00000000000040000000000000004045000000000000', + 'MULTIPOLYGON ZM (((10 10 5 45,10 20 4 44,20 20 3 43,20 15 2 42,10 10 1 41)),((60 60 5 45,70 70 4 44,80 60 3 43,60 60 2 42)))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bf0b00000300000001b90b0000000000000000244000000000000024400000000000001440000000000080464001b90b00000000000000003e400000000000003e400000000000001440000000000080464001ba0b0000020000000000000000002e400000000000002e40000000000000144000000000008046400000000000003440000000000000344000000000000014400000000000804640', + 'GEOMETRYCOLLECTION ZM (POINT ZM (10 10 5 45),POINT ZM (30 30 5 45),LINESTRING ZM (15 15 5 45,20 20 5 45))', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbf000000030000000bb940240000000000004024000000000000401400000000000040468000000000000000000bb9403e000000000000403e000000000000401400000000000040468000000000000000000bba00000002402e000000000000402e000000000000401400000000000040468000000000004034000000000000403400000000000040140000000000004046800000000000', + 'GEOMETRYCOLLECTION ZM (POINT ZM (10 10 5 45),POINT ZM (30 30 5 45),LINESTRING ZM (15 15 5 45,20 20 5 45))', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01070000c00300000001010000c0000000000000244000000000000024400000000000001440000000000080464001010000c00000000000003e400000000000003e400000000000001440000000000080464001020000c0020000000000000000002e400000000000002e40000000000000144000000000008046400000000000003440000000000000344000000000000014400000000000804640', + 'GEOMETRYCOLLECTION ZM (POINT ZM (10 10 5 45),POINT ZM (30 30 5 45),LINESTRING ZM (15 15 5 45,20 20 5 45))', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000070000000300c0000001402400000000000040240000000000004014000000000000404680000000000000c0000001403e000000000000403e0000000000004014000000000000404680000000000000c000000200000002402e000000000000402e000000000000401400000000000040468000000000004034000000000000403400000000000040140000000000004046800000000000', + 'GEOMETRYCOLLECTION ZM (POINT ZM (10 10 5 45),POINT ZM (30 30 5 45),LINESTRING ZM (15 15 5 45,20 20 5 45))', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + + [ + '0101000000000000000000f87f000000000000f87f', + 'POINT EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '00000000017ff80000000000007ff8000000000000', + 'POINT EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010200000000000000', + 'LINESTRING EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000200000000', + 'LINESTRING EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010300000000000000', + 'POLYGON EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000300000000', + 'POLYGON EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010400000000000000', + 'MULTIPOINT EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000400000000', + 'MULTIPOINT EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010500000000000000', + 'MULTILINESTRING EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000500000000', + 'MULTILINESTRING EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010600000000000000', + 'MULTIPOLYGON EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000600000000', + 'MULTIPOLYGON EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '010700000000000000', + 'GEOMETRYCOLLECTION EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XY'}, + ], + [ + '000000000700000000', + 'GEOMETRYCOLLECTION EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XY'}, + ], + + /* + // XXX: These doesn't work since WKT parser returns 2D geometry instead of 3D/4D + ['01e9030000000000000000f87f000000000000f87f000000000000f87f', 'POINT Z EMPTY', { littleEndian: true, ewkb: false, geometryLayout: 'XYZ' }], + ['00000003e97ff80000000000007ff80000000000007ff8000000000000', 'POINT Z EMPTY', { littleEndian: false, ewkb: false, geometryLayout: 'XYZ' }], + ['0101000080000000000000f87f000000000000f87f000000000000f87f', 'POINT Z EMPTY', { littleEndian: true, ewkb: true, geometryLayout: 'XYZ' }], + ['00800000017ff80000000000007ff80000000000007ff8000000000000', 'POINT Z EMPTY', { littleEndian: false, ewkb: true, geometryLayout: 'XYZ' }], + */ + [ + '01ea03000000000000', + 'LINESTRING Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ea00000000', + 'LINESTRING Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010200008000000000', + 'LINESTRING Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000200000000', + 'LINESTRING Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01eb03000000000000', + 'POLYGON Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003eb00000000', + 'POLYGON Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010300008000000000', + 'POLYGON Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000300000000', + 'POLYGON Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ec03000000000000', + 'MULTIPOINT Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ec00000000', + 'MULTIPOINT Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010400008000000000', + 'MULTIPOINT Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000400000000', + 'MULTIPOINT Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ed03000000000000', + 'MULTILINESTRING Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ed00000000', + 'MULTILINESTRING Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010500008000000000', + 'MULTILINESTRING Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000500000000', + 'MULTILINESTRING Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ee03000000000000', + 'MULTIPOLYGON Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ee00000000', + 'MULTIPOLYGON Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010600008000000000', + 'MULTIPOLYGON Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000600000000', + 'MULTIPOLYGON Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01ef03000000000000', + 'GEOMETRYCOLLECTION Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003ef00000000', + 'GEOMETRYCOLLECTION Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '010700008000000000', + 'GEOMETRYCOLLECTION Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '008000000700000000', + 'GEOMETRYCOLLECTION Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + + /* + // XXX: These doesn't work since WKT parser returns 2D geometry instead of 3D/4D + ['01d1070000000000000000f87f000000000000f87f000000000000f87f', 'POINT M EMPTY', { littleEndian: true, ewkb: false, geometryLayout: 'XYM' }], + ['00000007d17ff80000000000007ff80000000000007ff8000000000000', 'POINT M EMPTY', { littleEndian: false, ewkb: false, geometryLayout: 'XYM' }], + ['0101000040000000000000f87f000000000000f87f000000000000f87f', 'POINT M EMPTY', { littleEndian: true, ewkb: true, geometryLayout: 'XYM' }], + ['00400000017ff80000000000007ff80000000000007ff8000000000000', 'POINT M EMPTY', { littleEndian: false, ewkb: true, geometryLayout: 'XYM' }], + */ + [ + '01d207000000000000', + 'LINESTRING M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d200000000', + 'LINESTRING M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010200004000000000', + 'LINESTRING M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000200000000', + 'LINESTRING M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d307000000000000', + 'POLYGON M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d300000000', + 'POLYGON M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010300004000000000', + 'POLYGON M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000300000000', + 'POLYGON M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d407000000000000', + 'MULTIPOINT M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d400000000', + 'MULTIPOINT M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010400004000000000', + 'MULTIPOINT M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000400000000', + 'MULTIPOINT M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d507000000000000', + 'MULTILINESTRING M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d500000000', + 'MULTILINESTRING M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010500004000000000', + 'MULTILINESTRING M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000500000000', + 'MULTILINESTRING M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d607000000000000', + 'MULTIPOLYGON M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d600000000', + 'MULTIPOLYGON M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010600004000000000', + 'MULTIPOLYGON M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000600000000', + 'MULTIPOLYGON M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01d707000000000000', + 'GEOMETRYCOLLECTION M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d700000000', + 'GEOMETRYCOLLECTION M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '010700004000000000', + 'GEOMETRYCOLLECTION M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '004000000700000000', + 'GEOMETRYCOLLECTION M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + + /* + // XXX: These doesn't work since WKT parser returns 2D geometry instead of 3D/4D + ['01b90b0000000000000000f87f000000000000f87f000000000000f87f000000000000f87f', 'POINT ZM EMPTY', { littleEndian: true, ewkb: false, geometryLayout: 'XYZM' }], + ['0000000bb97ff80000000000007ff80000000000007ff80000000000007ff8000000000000', 'POINT ZM EMPTY', { littleEndian: false, ewkb: false, geometryLayout: 'XYZM' }], + ['01010000c0000000000000f87f000000000000f87f000000000000f87f000000000000f87f', 'POINT ZM EMPTY', { littleEndian: true, ewkb: true, geometryLayout: 'XYZM' }], + ['00c00000017ff80000000000007ff80000000000007ff80000000000007ff8000000000000', 'POINT ZM EMPTY', { littleEndian: false, ewkb: true, geometryLayout: 'XYZM' }], + */ + [ + '01ba0b000000000000', + 'LINESTRING ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bba00000000', + 'LINESTRING ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01020000c000000000', + 'LINESTRING ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000200000000', + 'LINESTRING ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bb0b000000000000', + 'POLYGON ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbb00000000', + 'POLYGON ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01030000c000000000', + 'POLYGON ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000300000000', + 'POLYGON ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bc0b000000000000', + 'MULTIPOINT ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbc00000000', + 'MULTIPOINT ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01040000c000000000', + 'MULTIPOINT ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000400000000', + 'MULTIPOINT ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bd0b000000000000', + 'MULTILINESTRING ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbd00000000', + 'MULTILINESTRING ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01050000c000000000', + 'MULTILINESTRING ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000500000000', + 'MULTILINESTRING ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01be0b000000000000', + 'MULTIPOLYGON ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbe00000000', + 'MULTIPOLYGON ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01060000c000000000', + 'MULTIPOLYGON ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000600000000', + 'MULTIPOLYGON ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '01bf0b000000000000', + 'GEOMETRYCOLLECTION ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bbf00000000', + 'GEOMETRYCOLLECTION ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01070000c000000000', + 'GEOMETRYCOLLECTION ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c000000700000000', + 'GEOMETRYCOLLECTION ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], +]; + +describe('ol.format.WKB', function () { + const format = new WKB(); + + describe('#readProjection(string)', function () { + it('returns the default projection', function () { + const wkb = '0101000000000000000000F03F0000000000000040'; // POINT(1 2) + const projection = format.readProjection(wkb); + expect(projection).to.be(undefined); + }); + + it('returns an embed projection', function () { + const wkb = '0101000020E6100000000000000000F03F0000000000000040'; // SRID=4326;POINT(1 2) + const projection = format.readProjection(wkb); + expect(projection.getCode()).to.be('EPSG:4326'); + }); + }); + + describe('#readProjection(Uint8Array)', function () { + it('returns the default projection', function () { + const wkb = new Uint8Array([ + // POINT(1 2) + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + ]); + const projection = format.readProjection(wkb); + expect(projection).to.be(undefined); + }); + + it('returns an embed projection', function () { + const wkb = new Uint8Array([ + // SRID=4326;POINT(1 2) + 0x01, + 0x01, + 0x00, + 0x00, + 0x20, + 0xe6, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + ]); + const projection = format.readProjection(wkb); + expect(projection.getCode()).to.be('EPSG:4326'); + }); + }); + + describe('#readGeometry(string)', function () { + it('transforms with dataProjection and featureProjection', function () { + const wkb = '0101000000000000000000F03F0000000000000040'; // POINT(1 2) + const geom = format.readGeometry(wkb, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + expect(geom.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + }); + + it('transforms with auto detection of dataProjection', function () { + const wkb = '0101000020E6100000000000000000F03F0000000000000040'; // SRID=4326;POINT(1 2) + const geom = format.readGeometry(wkb, { + featureProjection: 'EPSG:3857', + }); + expect(geom.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + }); + }); + + describe('#readGeometry(Uint8Array)', function () { + it('transforms with dataProjection and featureProjection', function () { + const wkb = new Uint8Array([ + // POINT(1 2) + 0x01, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + ]); + const geom = format.readGeometry(wkb, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + expect(geom.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + }); + + it('transforms with auto detection of dataProjection', function () { + const wkb = new Uint8Array([ + // SRID=4326;POINT(1 2) + 0x01, + 0x01, + 0x00, + 0x00, + 0x20, + 0xe6, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xf0, + 0x3f, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x40, + ]); + const geom = format.readGeometry(wkb, { + featureProjection: 'EPSG:3857', + }); + expect(geom.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + }); + }); + + describe('#readGeometry(string)', function () { + function compareGeometries(a, b) { + expect(a.getType()).to.eql(b.getType()); + + if (a instanceof GeometryCollection || b instanceof GeometryCollection) { + expect(a).to.be.a(GeometryCollection); + expect(b).to.be.a(GeometryCollection); + + const aGeoms = a.getGeometries(); + const bGeoms = b.getGeometries(); + + expect(aGeoms.length).to.eql(bGeoms.length); + + for (let i = 0; i < aGeoms.length; i++) { + compareGeometries(aGeoms[i], bGeoms[i]); + } + + return; + } + + expect(a).to.be.a(SimpleGeometry); + expect(b).to.be.a(SimpleGeometry); + + expect(a.getLayout()).to.eql(b.getLayout()); + + const aCoords = a.getCoordinates(); + const bCoords = b.getCoordinates(); + + expect(aCoords.length).to.eql(bCoords.length); + + expect(JSON.stringify(aCoords)).to.eql(JSON.stringify(bCoords)); // compare arrays + } + + // tests for several patterns + for (const pair of patterns) { + const wkb = pair[0]; + const wkt = pair[1]; + + it('works on ' + wkb + ' (' + wkt + ')', function () { + const wkb_result = new WKB().readGeometry(wkb); + const wkt_result = new WKT().readGeometry(wkt); + + compareGeometries(wkb_result, wkt_result); + }); + } + }); + + describe('#writeGeometry()', function () { + it('transforms with dataProjection and featureProjection', function () { + const geom = new Point([1, 2]).transform('EPSG:4326', 'EPSG:3857'); + const wkb = format.writeGeometry(geom, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + const got = format.readGeometry(wkb).getCoordinates(); + expect(got[0]).to.roughlyEqual(1, 1e-6); + expect(got[1]).to.roughlyEqual(2, 1e-6); + }); + + // tests for several patterns + for (const item of patterns) { + const wkb = item[0]; + const wkt = item[1]; + const opts = item[2]; + + it('for ' + wkt + ' with opt ' + JSON.stringify(opts), function () { + const wkt_result = new WKT().readGeometry(wkt); + const wkb_result = new WKB(opts).writeGeometry(wkt_result); + + expect(wkb_result.toLowerCase()).to.eql(wkb); + }); + } + + // XXX: Additional tests for empty POINT Z/M/ZM since WKT parser returns 2D geometry instead of 3D/4D + it('returns proper representation for 3D/4D point', function () { + const testPatterns = [ + [ + '01e9030000000000000000f87f000000000000f87f000000000000f87f', + 'POINT Z EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '00000003e97ff80000000000007ff80000000000007ff8000000000000', + 'POINT Z EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZ'}, + ], + [ + '0101000080000000000000f87f000000000000f87f000000000000f87f', + 'POINT Z EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '00800000017ff80000000000007ff80000000000007ff8000000000000', + 'POINT Z EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZ'}, + ], + [ + '01d1070000000000000000f87f000000000000f87f000000000000f87f', + 'POINT M EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '00000007d17ff80000000000007ff80000000000007ff8000000000000', + 'POINT M EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYM'}, + ], + [ + '0101000040000000000000f87f000000000000f87f000000000000f87f', + 'POINT M EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '00400000017ff80000000000007ff80000000000007ff8000000000000', + 'POINT M EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYM'}, + ], + [ + '01b90b0000000000000000f87f000000000000f87f000000000000f87f000000000000f87f', + 'POINT ZM EMPTY', + {littleEndian: true, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '0000000bb97ff80000000000007ff80000000000007ff80000000000007ff8000000000000', + 'POINT ZM EMPTY', + {littleEndian: false, ewkb: false, geometryLayout: 'XYZM'}, + ], + [ + '01010000c0000000000000f87f000000000000f87f000000000000f87f000000000000f87f', + 'POINT ZM EMPTY', + {littleEndian: true, ewkb: true, geometryLayout: 'XYZM'}, + ], + [ + '00c00000017ff80000000000007ff80000000000007ff80000000000007ff8000000000000', + 'POINT ZM EMPTY', + {littleEndian: false, ewkb: true, geometryLayout: 'XYZM'}, + ], + ]; + + for (const item of testPatterns) { + const wkb = item[0]; + const opts = item[2]; + + const geom = new Point([], opts.geometryLayout); + expect(new WKB(opts).writeGeometry(geom).toLowerCase()).to.eql(wkb); + } + }); + + it('detects geometry dimension automatically (common case)', function () { + const geom = new GeometryCollection([ + new Point([1, 2, 3], 'XYZ'), + new Point([1, 2, 4], 'XYZM'), + new Point([1, 2, 3, 4], 'XYZM'), + ]); + const wkb = new WKB().writeGeometry(geom); + const geoms = new WKB().readGeometry(wkb).getGeometries(); + + for (let i = 0; i < geoms.length; i++) { + expect(geoms[i].getLayout()).to.eql('XYZ'); + } + }); + + it('detects geometry dimension automatically (incompatible case)', function () { + const geom = new GeometryCollection([ + new Point([1, 2, 3], 'XYZ'), + new Point([1, 2, 4], 'XYM'), + new Point([1, 2, 3, 4], 'XYZM'), + ]); + const wkb = new WKB().writeGeometry(geom); + const geoms = new WKB().readGeometry(wkb).getGeometries(); + + for (let i = 0; i < geoms.length; i++) { + expect(geoms[i].getLayout()).to.eql('XY'); + } + }); + + it('interpolates missing Z value', function () { + const geom = new GeometryCollection([ + new Point([1, 2, 3], 'XY'), // 3rd coord is intentional + new Point([1, 2, 4], 'XYM'), + new Point([1, 2, 3, 4], 'XYZM'), + ]); + const wkb = new WKB({nodataZ: 98, geometryLayout: 'XYZ'}).writeGeometry( + geom + ); + + // GEOMETRYCOLLECTION Z (POINT Z (1 2 98),POINT Z (1 2 98),POINT Z (1 2 3)) + expect(wkb).to.eql( + '0107000080030000000101000080000000000000F03F000000000000004000000000008058400101000080000000000000F03F000000000000004000000000008058400101000080000000000000F03F00000000000000400000000000000840' + ); + }); + + it('interpolates missing M value', function () { + const geom = new GeometryCollection([ + new Point([1, 2, 3], 'XY'), // 3rd coord is intentional + new Point([1, 2, 4], 'XYM'), + new Point([1, 2, 3, 4], 'XYZM'), + ]); + const wkb = new WKB({nodataM: 99, geometryLayout: 'XYM'}).writeGeometry( + geom + ); + + // GEOMETRYCOLLECTION M (POINT M (1 2 99),POINT M (1 2 4),POINT M (1 2 4)) + expect(wkb).to.eql( + '0107000040030000000101000040000000000000F03F00000000000000400000000000C058400101000040000000000000F03F000000000000004000000000000010400101000040000000000000F03F00000000000000400000000000001040' + ); + }); + + it('interpolates missing Z and M value', function () { + const geom = new GeometryCollection([ + new Point([1, 2, 3], 'XY'), // 3rd coord is intentional + new Point([1, 2, 4], 'XYM'), + new Point([1, 2, 3, 4], 'XYZM'), + ]); + const wkb = new WKB({ + nodataZ: 98, + nodataM: 99, + geometryLayout: 'XYZM', + }).writeGeometry(geom); + + // GEOMETRYCOLLECTION ZM (POINT ZM (1 2 98 99),POINT ZM (1 2 98 4),POINT ZM (1 2 3 4)) + expect(wkb).to.eql( + '01070000C00300000001010000C0000000000000F03F000000000000004000000000008058400000000000C0584001010000C0000000000000F03F00000000000000400000000000805840000000000000104001010000C0000000000000F03F000000000000004000000000000008400000000000001040' + ); + }); + }); + + describe('#readFeature()', function () { + it('transforms with dataProjection and featureProjection', function () { + const wkb = '0101000000000000000000F03F0000000000000040'; // POINT(1 2) + const feature = format.readFeature(wkb, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + const geom = feature.getGeometry(); + expect(geom.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + }); + }); + + describe('#writeFeature()', function () { + it('transforms with dataProjection and featureProjection', function () { + const feature = new Feature( + new Point([1, 2]).transform('EPSG:4326', 'EPSG:3857') + ); + const wkt = format.writeFeature(feature, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + const gotFeature = format.readFeature(wkt); + expect(gotFeature).to.be.a(Feature); + const got = gotFeature.getGeometry().getCoordinates(); + expect(got[0]).to.roughlyEqual(1, 1e-6); + expect(got[1]).to.roughlyEqual(2, 1e-6); + }); + }); + + describe('#readFeatures()', function () { + const format = new WKB({splitCollection: true}); + + it('transforms with dataProjection and featureProjection', function () { + const wkb = + '0107000000020000000101000000000000000000F03F0000000000000040010100000000000000000010400000000000001440'; // GEOMETRYCOLLECTION(POINT(1 2),POINT(4 5)) + const features = format.readFeatures(wkb, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + expect(features.length).to.eql(2); + const point1 = features[0].getGeometry(); + const point2 = features[1].getGeometry(); + expect(point1.getType()).to.eql('Point'); + expect(point2.getType()).to.eql('Point'); + expect(point1.getCoordinates()).to.eql( + transform([1, 2], 'EPSG:4326', 'EPSG:3857') + ); + expect(point2.getCoordinates()).to.eql( + transform([4, 5], 'EPSG:4326', 'EPSG:3857') + ); + }); + }); + + describe('#writeFeatures()', function () { + const format = new WKB({splitCollection: true}); + + it('transforms with dataProjection and featureProjection', function () { + const features = [ + new Feature(new Point([1, 2]).transform('EPSG:4326', 'EPSG:3857')), + new Feature(new Point([4, 5]).transform('EPSG:4326', 'EPSG:3857')), + ]; + const wkt = format.writeFeatures(features, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); + const gotFeatures = format.readFeatures(wkt); + expect(gotFeatures).to.have.length(2); + expect(gotFeatures[0].getGeometry().getCoordinates()[0]).to.roughlyEqual( + 1, + 1e-6 + ); + expect(gotFeatures[0].getGeometry().getCoordinates()[1]).to.roughlyEqual( + 2, + 1e-6 + ); + expect(gotFeatures[1].getGeometry().getCoordinates()[0]).to.roughlyEqual( + 4, + 1e-6 + ); + expect(gotFeatures[1].getGeometry().getCoordinates()[1]).to.roughlyEqual( + 5, + 1e-6 + ); + }); + }); +});