osm2geojson-lite
Version:
a lightweight yet faster osm (either in xml or in json formats) to geojson convertor - 4x faster than xmldom + osmtogeojson in most situations - implemented in pure JavaScript without any 3rd party dependency
223 lines (222 loc) • 7.53 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WayCollection = exports.RefElements = exports.LateBinder = exports.strToFloat = exports.ptInsidePolygon = exports.ringDirection = exports.isRing = exports.coordsToKey = exports.last = exports.first = void 0;
exports.purgeProps = purgeProps;
exports.mergeProps = mergeProps;
exports.addPropToFeature = addPropToFeature;
exports.addPropToFeatures = addPropToFeatures;
exports.addToMap = addToMap;
exports.removeFromMap = removeFromMap;
exports.getFirstFromMap = getFirstFromMap;
function purgeProps(obj, blacklist) {
if (obj) {
const rs = Object.assign({}, obj);
if (blacklist) {
for (const prop of blacklist) {
delete rs[prop];
}
}
return rs;
}
return {};
}
function mergeProps(obj1, obj2) {
obj1 = obj1 ? obj1 : {};
obj2 = obj2 ? obj2 : {};
return Object.assign(obj1, obj2);
}
function addPropToFeature(f, k, v) {
if (f.properties && k && v) {
f.properties[k] = v;
}
}
function addPropToFeatures(fs, k, v) {
for (const f of fs) {
addPropToFeature(f, k, v);
}
}
const first = (a) => a[0];
exports.first = first;
const last = (a) => a[a.length - 1];
exports.last = last;
const coordsToKey = (a) => a.join(',');
exports.coordsToKey = coordsToKey;
function addToMap(m, k, v) {
const a = m[k];
if (a) {
a.push(v);
}
else {
m[k] = [v];
}
}
function removeFromMap(m, k, v) {
const a = m[k];
let idx = -1;
if (a) {
idx = a.indexOf(v);
}
if (idx >= 0) {
a.splice(idx, 1);
}
}
function getFirstFromMap(m, k) {
const a = m[k];
if (a && a.length > 0) {
return a[0];
}
return null;
}
// need 3+ different points to form a ring, here using > 3 is 'coz a the first and the last points are actually the same
const isRing = (a) => a.length > 3 && (0, exports.coordsToKey)((0, exports.first)(a)) === (0, exports.coordsToKey)((0, exports.last)(a));
exports.isRing = isRing;
const ringDirection = (a, xIdx, yIdx) => {
xIdx = xIdx || 0, yIdx = yIdx || 1;
// get the index of the point which has the maximum x value
const m = a.reduce((maxxIdx, v, idx) => a[maxxIdx][xIdx || 0] > v[xIdx || 0] ? maxxIdx : idx, 0);
// 'coz the first point is virtually the same one as the last point,
// we need to skip a.length - 1 for left when m = 0,
// and skip 0 for right when m = a.length - 1;
const l = m <= 0 ? a.length - 2 : m - 1;
const r = m >= a.length - 1 ? 1 : m + 1;
const xa = a[l][xIdx];
const xb = a[m][xIdx];
const xc = a[r][xIdx];
const ya = a[l][yIdx];
const yb = a[m][yIdx];
const yc = a[r][yIdx];
const det = (xb - xa) * (yc - ya) - (xc - xa) * (yb - ya);
return det < 0 ? 'clockwise' : 'counterclockwise';
};
exports.ringDirection = ringDirection;
const ptInsidePolygon = (pt, polygon, xIdx, yIdx) => {
xIdx = xIdx || 0, yIdx = yIdx || 1;
let result = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
if ((polygon[i][xIdx] <= pt[xIdx] && pt[xIdx] < polygon[j][xIdx] ||
polygon[j][xIdx] <= pt[xIdx] && pt[xIdx] < polygon[i][xIdx]) &&
pt[yIdx] < (polygon[j][yIdx] - polygon[i][yIdx]) * (pt[xIdx] - polygon[i][xIdx]) / (polygon[j][xIdx] - polygon[i][xIdx]) + polygon[i][yIdx]) {
result = !result;
}
}
return result;
};
exports.ptInsidePolygon = ptInsidePolygon;
const strToFloat = (el) => el instanceof Array ? el.map(exports.strToFloat) : parseFloat(el);
exports.strToFloat = strToFloat;
class LateBinder {
constructor(container, valueFunc, ctx, args) {
this.container = container;
this.valueFunc = valueFunc;
this.ctx = ctx;
this.args = args;
}
bind() {
const v = this.valueFunc.apply(this.ctx, this.args);
if (this.container instanceof Array) {
const idx = this.container.indexOf(this);
if (idx >= 0) {
const args = [idx, 1];
if (v) {
args.push(v);
}
Array.prototype.splice.apply(this.container, args);
}
}
else if (typeof this.container === 'object' && !Array.isArray(this.container)) {
const container = this.container;
const k = Object.keys(container).find((nv) => container[nv] === this);
if (k) {
if (v) {
container[k] = v;
}
else {
delete container[k];
}
}
}
}
}
exports.LateBinder = LateBinder;
class RefElements extends Map {
constructor() {
super();
this.binders = [];
}
add(k, v) {
this.set(k, v);
}
addBinder(binder) {
this.binders.push(binder);
}
bindAll() {
this.binders.forEach((binder) => binder.bind());
}
}
exports.RefElements = RefElements;
class WayCollection extends Array {
constructor() {
super();
this.firstMap = {};
this.lastMap = {};
}
addWay(way) {
const w = way.toCoordsArray();
if (w.length > 0) {
this.push(w);
addToMap(this.firstMap, (0, exports.coordsToKey)((0, exports.first)(w)), w);
addToMap(this.lastMap, (0, exports.coordsToKey)((0, exports.last)(w)), w);
}
}
toStrings() {
const strings = [];
let way = this.shift();
while (way) {
removeFromMap(this.firstMap, (0, exports.coordsToKey)((0, exports.first)(way)), way);
removeFromMap(this.lastMap, (0, exports.coordsToKey)((0, exports.last)(way)), way);
let current = way;
let next;
do {
const key = (0, exports.coordsToKey)((0, exports.last)(current));
let shouldReverse = false;
next = getFirstFromMap(this.firstMap, key);
if (!next) {
next = getFirstFromMap(this.lastMap, key);
shouldReverse = true;
}
if (next) {
this.splice(this.indexOf(next), 1);
removeFromMap(this.firstMap, (0, exports.coordsToKey)((0, exports.first)(next)), next);
removeFromMap(this.lastMap, (0, exports.coordsToKey)((0, exports.last)(next)), next);
if (shouldReverse) {
// always reverse shorter one to save time
if (next.length > current.length) {
[current, next] = [next, current];
}
next.reverse();
}
current = current.concat(next.slice(1));
}
} while (next);
strings.push((0, exports.strToFloat)(current));
way = this.shift();
}
return strings;
}
toRings(direction) {
const strings = this.toStrings();
const rings = [];
let str = strings.shift();
while (str) {
if ((0, exports.isRing)(str)) {
if ((0, exports.ringDirection)(str) !== direction) {
str.reverse();
}
rings.push(str);
}
str = strings.shift();
}
return rings;
}
}
exports.WayCollection = WayCollection;