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
378 lines (377 loc) • 13.3 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Relation = exports.Way = exports.Node = void 0;
const utils_1 = require("./utils");
const utils_2 = require("./utils");
const polytags_json_1 = __importDefault(require("./polytags.json"));
class OsmObject {
constructor(type, id, refElems) {
this.type = type;
this.id = id;
this.refElems = refElems;
this.tags = {};
this.props = { id: this.getCompositeId() };
this.refCount = 0;
this.hasTag = false;
if (refElems) {
refElems.add(this.getCompositeId(), this);
}
}
addTags(tags) {
this.tags = Object.assign(this.tags, tags);
this.hasTag = tags ? true : false;
}
addTag(k, v) {
this.tags[k] = v;
this.hasTag = k ? true : false;
}
addProp(k, v) {
this.props[k] = v;
}
addProps(props) {
this.props = Object.assign(this.props, props);
}
getCompositeId() {
return `${this.type}/${this.id}`;
}
getProps() {
return Object.assign(this.props, this.tags);
}
toFeatureArray() {
return [];
}
}
class Node extends OsmObject {
constructor(id, refElems) {
super('node', id, refElems);
this.latLng = null;
}
setLatLng(latLng) {
this.latLng = latLng;
}
toFeatureArray() {
if (this.latLng) {
return [{
type: 'Feature',
id: this.getCompositeId(),
properties: this.getProps(),
geometry: {
type: 'Point',
coordinates: (0, utils_1.strToFloat)([this.latLng.lon, this.latLng.lat]),
},
}];
}
return [];
}
getLatLng() {
return this.latLng;
}
}
exports.Node = Node;
class Way extends OsmObject {
constructor(id, refElems) {
super('way', id, refElems);
this.latLngArray = [];
this.isPolygon = false;
}
addLatLng(latLng) {
this.latLngArray.push(latLng);
}
setLatLngArray(latLngArray) {
this.latLngArray = latLngArray;
}
addNodeRef(ref) {
const binder = new utils_2.LateBinder(this.latLngArray, (id) => {
const node = this.refElems.get(`node/${id}`);
if (node) {
node.refCount++;
return node.getLatLng();
}
}, this, [ref]);
this.latLngArray.push(binder);
this.refElems.addBinder(binder);
}
addTags(tags) {
super.addTags(tags);
for (const [k, v] of Object.entries(tags)) {
this.analyzeTag(k, v);
}
}
addTag(k, v) {
super.addTag(k, v);
this.analyzeTag(k, v);
}
toCoordsArray() {
return this.latLngArray.map((latLng) => [latLng.lon, latLng.lat]);
}
toFeatureArray() {
let coordsArray = this.toCoordsArray();
if (coordsArray.length > 1) {
coordsArray = (0, utils_1.strToFloat)(coordsArray);
const feature = {
type: 'Feature',
id: this.getCompositeId(),
properties: this.getProps(),
geometry: {
type: 'LineString',
coordinates: coordsArray,
},
};
if (this.isPolygon && (0, utils_1.isRing)(coordsArray)) {
if ((0, utils_1.ringDirection)(coordsArray) !== 'counterclockwise') {
coordsArray.reverse();
}
feature.geometry = {
type: 'Polygon',
coordinates: [coordsArray],
};
return [feature];
}
return [feature];
}
return [];
}
analyzeTag(k, v) {
const o = polytags_json_1.default[k];
if (o) {
this.isPolygon = true;
if (o.whitelist) {
this.isPolygon = o.whitelist.indexOf(v) >= 0 ? true : false;
}
else if (o.blacklist) {
this.isPolygon = o.blacklist.indexOf(v) >= 0 ? false : true;
}
}
}
}
exports.Way = Way;
class Relation extends OsmObject {
constructor(id, refElems) {
super('relation', id, refElems);
this.relations = [];
this.nodes = [];
this.bounds = undefined;
}
setBounds(bounds) {
this.bounds = bounds;
}
addMember(member) {
switch (member.type) {
// super relation, need to do combination
case 'relation':
let binder = new utils_2.LateBinder(this.relations, (id) => {
const relation = this.refElems.get(`relation/${id}`);
if (relation) {
relation.refCount++;
return relation;
}
}, this, [member.ref]);
this.relations.push(binder);
this.refElems.addBinder(binder);
break;
case 'way':
if (!member.role) {
member.role = '';
}
let ways = this[member.role];
if (!ways) {
ways = this[member.role] = [];
}
if (member.geometry) {
const way = new Way(member.ref, this.refElems);
way.setLatLngArray(member.geometry);
way.refCount++;
ways.push(way);
}
else if (member.nodes) {
const way = new Way(member.ref, this.refElems);
for (const nid of member.nodes) {
way.addNodeRef(nid);
}
way.refCount++;
ways.push(way);
}
else {
let binder = new utils_2.LateBinder(ways, (nid) => {
const way = this.refElems.get(`way/${nid}`);
if (way) {
way.refCount++;
return way;
}
}, this, [member.ref]);
ways.push(binder);
this.refElems.addBinder(binder);
}
break;
case 'node':
let node = null;
if (member.lat && member.lon) {
node = new Node(member.ref, this.refElems);
node.setLatLng({ lon: member.lon, lat: member.lat });
if (member.tags) {
node.addTags(member.tags);
}
for (const [k, v] of Object.entries(member)) {
if (['id', 'type', 'lat', 'lon'].indexOf(k) < 0) {
node.addProp(k, v);
}
}
node.refCount++;
this.nodes.push(node);
}
else {
let binder = new utils_2.LateBinder(this.nodes, (id) => {
const nn = this.refElems.get(`node/${id}`);
if (nn) {
nn.refCount++;
return nn;
}
}, this, [member.ref]);
this.nodes.push(binder);
this.refElems.addBinder(binder);
}
default:
break;
}
}
toFeatureArray() {
function constructStringGeometry(ws) {
const strings = ws ? ws.toStrings() : [];
if (strings.length > 0) {
if (strings.length === 1) {
return {
type: 'LineString',
coordinates: strings[0],
};
}
return {
type: 'MultiLineString',
coordinates: strings,
};
}
return null;
}
function constructPolygonGeometry(ows, iws) {
const outerRings = ows ? ows.toRings('counterclockwise') : [];
const innerRings = iws ? iws.toRings('clockwise') : [];
if (outerRings.length > 0) {
const compositPolyons = [];
let ring;
for (ring of outerRings) {
compositPolyons.push([ring]);
}
// link inner polygons to outer containers
ring = innerRings.shift();
while (ring) {
for (const idx in outerRings) {
if ((0, utils_1.ptInsidePolygon)((0, utils_1.first)(ring), outerRings[idx])) {
compositPolyons[idx].push(ring);
break;
}
}
ring = innerRings.shift();
}
// construct the Polygon/MultiPolygon geometry
if (compositPolyons.length === 1) {
return {
type: 'Polygon',
coordinates: compositPolyons[0],
};
}
return {
type: 'MultiPolygon',
coordinates: compositPolyons,
};
}
return null;
}
const polygonFeatures = [];
const stringFeatures = [];
let pointFeatures = [];
// need to do combination when there're nested relations
const notWayFields = ['type', 'id', 'refElems', 'tags', 'props', 'refCount', 'hasTag', 'relations', 'nodes', 'bounds'];
for (const relation of this.relations) {
if (relation) {
let waysFieldNames = Object.keys(relation).filter(fieldName => notWayFields.indexOf(fieldName) < 0);
for (const fieldName of waysFieldNames) {
const ways = relation[fieldName];
if (ways) {
const thisWays = this[fieldName];
if (thisWays) {
thisWays.push(...ways);
}
else {
this[fieldName] = ways;
}
}
}
}
}
let waysFieldNames = Object.keys(this).filter(fieldName => notWayFields.indexOf(fieldName) < 0);
for (const fieldName of waysFieldNames) {
const ways = this[fieldName];
if (ways) {
this[fieldName] = new utils_2.WayCollection();
for (const way of ways) {
this[fieldName].addWay(way);
}
}
}
let geometry = null;
let templateFeature = {
type: 'Feature',
id: this.getCompositeId(),
bbox: this.bounds,
properties: this.getProps(),
geometry: null
};
if (!this.bounds) {
delete templateFeature.bbox;
}
if (this.outer) {
let feature = Object.assign({}, templateFeature);
let geometry = constructPolygonGeometry(this.outer, this.inner);
if (geometry) {
feature.geometry = geometry;
polygonFeatures.push(feature);
}
}
else {
let multiLineGeometry = {
type: 'MultiLineString',
coordinates: []
};
for (let way of waysFieldNames) {
if (way != null && way !== 'inner') {
let ws = this[way];
if (ws) {
let geometry = constructStringGeometry(ws);
if (geometry) {
if (geometry.type === 'LineString') {
multiLineGeometry.coordinates.push(geometry.coordinates);
}
else if (geometry.type === 'MultiLineString') {
let feature = Object.assign({}, templateFeature);
feature.geometry = geometry;
stringFeatures.push(feature);
}
}
}
}
}
if (multiLineGeometry.coordinates.length > 0) {
let feature = Object.assign({}, templateFeature);
feature.geometry = multiLineGeometry;
stringFeatures.push(feature);
}
}
for (let node of this.nodes) {
pointFeatures = pointFeatures.concat(node.toFeatureArray());
}
return [...polygonFeatures, ...stringFeatures, ...pointFeatures];
}
}
exports.Relation = Relation;