ol-ext
Version:
A set of cool extensions for OpenLayers (ol) in node modules structure
426 lines (419 loc) • 14.5 kB
JavaScript
/*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<string>|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<string>|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<ol.coordinate>} v
* @param {number} decimal
* @return {string|Array<string>}
* @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<string>}
* @param {number} decimal Number of decimals
* @return {ol.coordinate|Array<ol.coordinate>} 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<ol.Feature>} 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<ol.Feature>}
* @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