/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ import ol_format_GeoJSON from 'ol/format/GeoJSON.js' /** Feature format for reading and writing data in the GeoJSONX format. * @constructor * @extends {ol_format_GeoJSON} * @param {*} options options. * @param {number} options.decimals number of decimals to save, default 7 for EPSG:4326, 2 for other projections * @param {boolean|Array<*>} options.deleteNullProperties An array of property values to remove, if false, keep all properties, default [null,undefined,""] * @param {boolean|Array<*>} options.extended Decode/encode extended GeoJSON with foreign members (id, bbox, title, etc.), default false * @param {Array|function} options.whiteList A list of properties to keep on features when encoding or a function that takes a property name and retrun true if the property is whitelisted * @param {Array|function} options.blackList A list of properties to remove from features when encoding or a function that takes a property name and retrun true if the property is blacklisted * @param {string} [options.layout='XY'] layout layout (XY or XYZ or XYZM) * @param {ol.ProjectionLike} options.dataProjection Projection of the data we are reading. If not provided `EPSG:4326` * @param {ol.ProjectionLike} options.featureProjection Projection of the feature geometries created by the format reader. If not provided, features will be returned in the dataProjection. */ var ol_format_GeoJSONX = class olformatGeoJSONX extends ol_format_GeoJSON { constructor(options) { options = options || {}; super(options); this._hash = {}; this._count = 0; this._extended = options.extended; if (typeof (options.whiteList) === 'function') { this._whiteList = options.whiteList; } else if (options.whiteList && options.whiteList.indexOf) { this._whiteList = function (k) { return options.whiteList.indexOf(k) > -1; }; } else { this._whiteList = function () { return true; }; } if (typeof (options.blackList) === 'function') { this._blackList = options.blackList; } else if (options.blackList && options.blackList.indexOf) { this._blackList = function (k) { return options.blackList.indexOf(k) > -1; }; } else { this._blackList = function () { return false; }; } this._deleteNull = options.deleteNullProperties === false ? false : [null, undefined, ""]; var decimals = 2; if (!options.dataProjection || options.dataProjection === 'EPSG:4326') decimals = 7; if (!isNaN(parseInt(options.decimals))) decimals = parseInt(options.decimals); this._decimals = decimals; this.setLayout(options.layout || 'XY'); } /** Set geometry layout * @param {string} layout the geometry layout (XY or XYZ or XYZM) */ setLayout(layout) { switch (layout) { case 'XYZ': case 'XYZM': { this._layout = layout; break; } default: { this._layout = 'XY'; break; } } } /** Get geometry layout * @return {string} layout */ getLayout() { return this._layout; } /** Encode a number * @param {number} number Number to encode * @param {number} decimals Number of decimals * @param {string} */ encodeNumber(number, decimals) { if (isNaN(Number(number)) || number === null || !isFinite(number)) { number = 0; } if (!decimals && decimals !== 0) decimals = this._decimals; // Round number number = Math.round(number * Math.pow(10, decimals)); // Zigzag encoding (get positive number) if (number < 0) number = -2 * number - 1; else number = 2 * number; // Encode var result = ''; var modulo, residual = number; while (true) { modulo = residual % this._size; result = this._radix.charAt(modulo) + result; residual = Math.floor(residual / this._size); if (residual == 0) break; } return result; } /** Decode a number * @param {string} s * @param {number} decimals Number of decimals * @return {number} */ decodeNumber(s, decimals) { if (!decimals && decimals !== 0) decimals = this._decimals; var decode = 0; s.split('').forEach(function (c) { decode = (decode * this._size) + this._radix.indexOf(c); }.bind(this)); // Zigzag encoding var result = Math.floor(decode / 2); if (result !== decode / 2) result = -1 - result; return result / Math.pow(10, decimals); } /** Encode coordinates * @param {ol.coordinate|Array} v * @param {number} decimal * @return {string|Array} * @api */ encodeCoordinates(v, decimal) { var i, p, tp; if (typeof (v[0]) === 'number') { p = this.encodeNumber(v[0], decimal) + ',' + this.encodeNumber(v[1], decimal); if (this._layout[2] == 'Z' && v.length > 2) p += ',' + this.encodeNumber(v[2], 2); if (this._layout[3] == 'M' && v.length > 3) p += ',' + this.encodeNumber(v[3], 0); return p; } else if (v.length && v[0]) { if (typeof (v[0][0]) === 'number') { var dxy = [0, 0, 0, 0]; var xy = []; var hasZ = (this._layout[2] == 'Z' && v[0].length > 2); var hasM = (this._layout[3] == 'M' && v[0].length > 3); for (i = 0; i < v.length; i++) { tp = [ Math.round(v[i][0] * Math.pow(10, decimal)), Math.round(v[i][1] * Math.pow(10, decimal)) ]; if (hasZ) tp[2] = v[i][2]; if (hasM) tp[3] = v[i][3]; v[i] = tp; var dx = v[i][0] - dxy[0]; var dy = v[i][1] - dxy[1]; // Prevent same coords if (i == 0 || (dx !== 0 || dy !== 0)) { p = this.encodeNumber(dx, 0) + ',' + this.encodeNumber(dy, 0) + (hasZ ? ',' + this.encodeNumber(v[i][2] - dxy[2], 2) : '') + (hasM ? ',' + this.encodeNumber(v[i][3] - dxy[3], 0) : ''); xy.push(p); dxy = v[i]; } } // Almost 2 points... if (xy.length < 2 && v.length > 1) { p = 'A,A' + (hasZ ? ',A':'') + (hasM ? ',A':''); xy.push(p); } // encoded return xy.join(';'); } else { for (i = 0; i < v.length; i++) { v[i] = this.encodeCoordinates(v[i], decimal); } return v; } } else { return this.encodeCoordinates([0, 0], decimal); } } /** Decode coordinates * @param {string|Array} * @param {number} decimal Number of decimals * @return {ol.coordinate|Array} v * @api */ decodeCoordinates(v, decimals) { var i, p; if (typeof (v) === 'string') { v = v.split(';'); if (v.length > 1) { var pow = Math.pow(10, decimals); var dxy = [0, 0, 0, 0]; v.forEach(function (vi, i) { v[i] = vi.split(','); v[i][0] = Math.round((this.decodeNumber(v[i][0], decimals) + dxy[0]) * pow) / pow; v[i][1] = Math.round((this.decodeNumber(v[i][1], decimals) + dxy[1]) * pow) / pow; if (v[i].length > 2) v[i][2] = Math.round((this.decodeNumber(v[i][2], 2) + dxy[2]) * pow) / pow; if (v[i].length > 3) v[i][3] = Math.round((this.decodeNumber(v[i][3], 0) + dxy[3]) * pow) / pow; dxy = v[i]; }.bind(this)); return v; } else { v = v[0].split(','); p = [this.decodeNumber(v[0], decimals), this.decodeNumber(v[1], decimals)]; if (v.length > 2) p[2] = this.decodeNumber(v[2], 2); if (v.length > 3) p[3] = this.decodeNumber(v[3], 0); return p; } } else if (v.length) { var r = []; for (i = 0; i < v.length; i++) { r[i] = this.decodeCoordinates(v[i], decimals); } return r; } else { return [0, 0]; } } /** Encode an array of features as a GeoJSONX object. * @param {Array} features Features. * @param {*} options Write options. * @return {*} GeoJSONX Object. * @override * @api */ writeFeaturesObject(features, options) { options = options || {}; this._count = 0; this._hash = {}; var geojson = ol_format_GeoJSON.prototype.writeFeaturesObject.call(this, features, options); geojson.decimals = this._decimals; geojson.hashProperties = []; Object.keys(this._hash).forEach(function (k) { geojson.hashProperties.push(k); }.bind(this)); this._count = 0; this._hash = {}; // Push features at the end of the object var temp = geojson.features; delete geojson.features; geojson.features = temp; return geojson; } /** Encode a set of features as a GeoJSONX object. * @param {ol.Feature} feature Feature * @param {*} options Write options. * @return {*} GeoJSONX Object. * @override * @api */ writeFeatureObject(source, options) { var f0 = ol_format_GeoJSON.prototype.writeFeatureObject.call(this, source, options); // Only features supported yet if (f0.type !== 'Feature') throw 'GeoJSONX doesn\'t support ' + f0.type + '.'; var f = []; // Encode geometry if (f0.geometry.type === 'Point') { f.push(this.encodeCoordinates(f0.geometry.coordinates, this._decimals)); } else if (f0.geometry.type === 'MultiPoint') { var pts = []; f0.geometry.coordinates.forEach(function (p) { pts.push(this.encodeCoordinates(p, this._decimals)); }.bind(this)); f.push([ this._type[f0.geometry.type], pts.join(';') ]); } else { if (!this._type[f0.geometry.type]) { throw 'GeoJSONX doesn\'t support ' + f0.geometry.type + '.'; } f.push([ this._type[f0.geometry.type], this.encodeCoordinates(f0.geometry.coordinates, this._decimals) ]); } // Encode properties var k; var prop = []; for (k in f0.properties) { if (!this._whiteList(k) || this._blackList(k)) continue; if (!this._hash.hasOwnProperty(k)) { this._hash[k] = this._count; this._count++; } if (!this._deleteNull || this._deleteNull.indexOf(f0.properties[k]) < 0) { prop.push(this._hash[k], f0.properties[k]); } } // Create prop table if (prop.length || this._extended) { f.push(prop); } // Other properties (id, title, bbox, centerline... if (this._extended) { var found = false; prop = {}; for (k in f0) { if (!/^type$|^geometry$|^properties$/.test(k)) { prop[k] = f0[k]; found = true; } } if (found) { f.push(prop); } } return f; } /** Encode a geometry as a GeoJSONX object. * @param {ol.geom.Geometry} geometry Geometry. * @param {*} options Write options. * @return {*} Object. * @override * @api */ writeGeometryObject(source, options) { var g = ol_format_GeoJSON.prototype.writeGeometryObject.call(this, source, options); // Encode geometry if (g.type === 'Point') { return this.encodeCoordinates(g.coordinates, this._decimals); } else { return [ this._type[g.type], this.encodeCoordinates(g.coordinates, this._decimals) ]; } } /** Decode a GeoJSONX object. * @param {*} object GeoJSONX * @param {*} options Read options. * @return {Array} * @override * @api */ readFeaturesFromObject(object, options) { this._hashProperties = object.hashProperties || []; options = options || {}; options.decimals = parseInt(object.decimals); if (!options.decimals && options.decimals !== 0) throw 'Bad file format...'; var features = ol_format_GeoJSON.prototype.readFeaturesFromObject.call(this, object, options); return features; } /** Decode GeoJSONX Feature object. * @param {*} object GeoJSONX * @param {*} options Read options. * @return {ol.Feature} */ readFeatureFromObject(f0, options) { var f = { type: 'Feature' }; if (typeof (f0[0]) === 'string') { f.geometry = { type: 'Point', coordinates: this.decodeCoordinates(f0[0], typeof (options.decimals) === 'number' ? options.decimals : this.decimals) }; } else { f.geometry = { type: this._toType[f0[0][0]] }; if (f.geometry.type === 'MultiPoint') { var g = f.geometry.coordinates = []; var coords = f0[0][1].split(';'); coords.forEach(function (c) { c = c.split(','); g.push([this.decodeNumber(c[0], options.decimals), this.decodeNumber(c[1], options.decimals)]); }.bind(this)); } else { f.geometry.coordinates = this.decodeCoordinates(f0[0][1], typeof (options.decimals) === 'number' ? options.decimals : this.decimals); } } if (this._hashProperties && f0[1]) { f.properties = {}; var t = f0[1]; for (var i = 0; i < t.length; i += 2) { f.properties[this._hashProperties[t[i]]] = t[i + 1]; } } else { f.properties = f0[1]; } // Extended properties if (f0[2]) { for (var k in f0[2]) { f[k] = f0[2][k]; } } var feature = ol_format_GeoJSON.prototype.readFeatureFromObject.call(this, f, options); return feature; } } /** Radix */ ol_format_GeoJSONX.prototype._radix = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ !#$%&\'()*-.:<=>?@[]^_`{|}~'; /** Radix size */ ol_format_GeoJSONX.prototype._size = ol_format_GeoJSONX.prototype._radix.length; /** GeoSJON types */ ol_format_GeoJSONX.prototype._type = { "Point": 0, "LineString": 1, "Polygon": 2, "MultiPoint": 3, "MultiLineString": 4, "MultiPolygon": 5, "GeometryCollection": null // Not supported }; /** GeoSJONX types */ ol_format_GeoJSONX.prototype._toType = [ "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" ]; export default ol_format_GeoJSONX