308 lines
11 KiB
JavaScript
308 lines
11 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var Node_1 = __importDefault(require("./Node"));
|
|
var Edge_1 = __importDefault(require("./Edge"));
|
|
var EdgeRing_1 = __importDefault(require("./EdgeRing"));
|
|
var meta_1 = require("@turf/meta");
|
|
var invariant_1 = require("@turf/invariant");
|
|
/**
|
|
* Validates the geoJson.
|
|
*
|
|
* @param {GeoJSON} geoJson - input geoJson.
|
|
* @throws {Error} if geoJson is invalid.
|
|
*/
|
|
function validateGeoJson(geoJson) {
|
|
if (!geoJson)
|
|
throw new Error("No geojson passed");
|
|
if (geoJson.type !== "FeatureCollection" &&
|
|
geoJson.type !== "GeometryCollection" &&
|
|
geoJson.type !== "MultiLineString" &&
|
|
geoJson.type !== "LineString" &&
|
|
geoJson.type !== "Feature")
|
|
throw new Error("Invalid input type '" + geoJson.type + "'. Geojson must be FeatureCollection, GeometryCollection, LineString, MultiLineString or Feature");
|
|
}
|
|
/**
|
|
* Represents a planar graph of edges and nodes that can be used to compute a polygonization.
|
|
*
|
|
* Although, this class is inspired by GEOS's `geos::operation::polygonize::PolygonizeGraph`,
|
|
* it isn't a rewrite. As regards algorithm, this class implements the same logic, but it
|
|
* isn't a javascript transcription of the C++ source.
|
|
*
|
|
* This graph is directed (both directions are created)
|
|
*/
|
|
var Graph = /** @class */ (function () {
|
|
function Graph() {
|
|
this.edges = []; //< {Edge[]} dirEdges
|
|
// The key is the `id` of the Node (ie: coordinates.join(','))
|
|
this.nodes = {};
|
|
}
|
|
/**
|
|
* Creates a graph from a GeoJSON.
|
|
*
|
|
* @param {FeatureCollection<LineString>} geoJson - it must comply with the restrictions detailed in the index
|
|
* @returns {Graph} - The newly created graph
|
|
* @throws {Error} if geoJson is invalid.
|
|
*/
|
|
Graph.fromGeoJson = function (geoJson) {
|
|
validateGeoJson(geoJson);
|
|
var graph = new Graph();
|
|
meta_1.flattenEach(geoJson, function (feature) {
|
|
invariant_1.featureOf(feature, "LineString", "Graph::fromGeoJson");
|
|
// When a LineString if formed by many segments, split them
|
|
meta_1.coordReduce(feature, function (prev, cur) {
|
|
if (prev) {
|
|
var start = graph.getNode(prev), end = graph.getNode(cur);
|
|
graph.addEdge(start, end);
|
|
}
|
|
return cur;
|
|
});
|
|
});
|
|
return graph;
|
|
};
|
|
/**
|
|
* Creates or get a Node.
|
|
*
|
|
* @param {number[]} coordinates - Coordinates of the node
|
|
* @returns {Node} - The created or stored node
|
|
*/
|
|
Graph.prototype.getNode = function (coordinates) {
|
|
var id = Node_1.default.buildId(coordinates);
|
|
var node = this.nodes[id];
|
|
if (!node)
|
|
node = this.nodes[id] = new Node_1.default(coordinates);
|
|
return node;
|
|
};
|
|
/**
|
|
* Adds an Edge and its symetricall.
|
|
*
|
|
* Edges are added symetrically, i.e.: we also add its symetric
|
|
*
|
|
* @param {Node} from - Node which starts the Edge
|
|
* @param {Node} to - Node which ends the Edge
|
|
*/
|
|
Graph.prototype.addEdge = function (from, to) {
|
|
var edge = new Edge_1.default(from, to), symetricEdge = edge.getSymetric();
|
|
this.edges.push(edge);
|
|
this.edges.push(symetricEdge);
|
|
};
|
|
/**
|
|
* Removes Dangle Nodes (nodes with grade 1).
|
|
*/
|
|
Graph.prototype.deleteDangles = function () {
|
|
var _this = this;
|
|
Object.keys(this.nodes)
|
|
.map(function (id) { return _this.nodes[id]; })
|
|
.forEach(function (node) { return _this._removeIfDangle(node); });
|
|
};
|
|
/**
|
|
* Check if node is dangle, if so, remove it.
|
|
*
|
|
* It calls itself recursively, removing a dangling node might cause another dangling node
|
|
*
|
|
* @param {Node} node - Node to check if it's a dangle
|
|
*/
|
|
Graph.prototype._removeIfDangle = function (node) {
|
|
var _this = this;
|
|
// As edges are directed and symetrical, we count only innerEdges
|
|
if (node.innerEdges.length <= 1) {
|
|
var outerNodes = node.getOuterEdges().map(function (e) { return e.to; });
|
|
this.removeNode(node);
|
|
outerNodes.forEach(function (n) { return _this._removeIfDangle(n); });
|
|
}
|
|
};
|
|
/**
|
|
* Delete cut-edges (bridge edges).
|
|
*
|
|
* The graph will be traversed, all the edges will be labeled according the ring
|
|
* in which they are. (The label is a number incremented by 1). Edges with the same
|
|
* label are cut-edges.
|
|
*/
|
|
Graph.prototype.deleteCutEdges = function () {
|
|
var _this = this;
|
|
this._computeNextCWEdges();
|
|
this._findLabeledEdgeRings();
|
|
// Cut-edges (bridges) are edges where both edges have the same label
|
|
this.edges.forEach(function (edge) {
|
|
if (edge.label === edge.symetric.label) {
|
|
_this.removeEdge(edge.symetric);
|
|
_this.removeEdge(edge);
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Set the `next` property of each Edge.
|
|
*
|
|
* The graph will be transversed in a CW form, so, we set the next of the symetrical edge as the previous one.
|
|
* OuterEdges are sorted CCW.
|
|
*
|
|
* @param {Node} [node] - If no node is passed, the function calls itself for every node in the Graph
|
|
*/
|
|
Graph.prototype._computeNextCWEdges = function (node) {
|
|
var _this = this;
|
|
if (typeof node === "undefined") {
|
|
Object.keys(this.nodes).forEach(function (id) {
|
|
return _this._computeNextCWEdges(_this.nodes[id]);
|
|
});
|
|
}
|
|
else {
|
|
node.getOuterEdges().forEach(function (edge, i) {
|
|
node.getOuterEdge((i === 0 ? node.getOuterEdges().length : i) - 1).symetric.next = edge;
|
|
});
|
|
}
|
|
};
|
|
/**
|
|
* Computes the next edge pointers going CCW around the given node, for the given edgering label.
|
|
*
|
|
* This algorithm has the effect of converting maximal edgerings into minimal edgerings
|
|
*
|
|
* XXX: method literally transcribed from `geos::operation::polygonize::PolygonizeGraph::computeNextCCWEdges`,
|
|
* could be written in a more javascript way.
|
|
*
|
|
* @param {Node} node - Node
|
|
* @param {number} label - Ring's label
|
|
*/
|
|
Graph.prototype._computeNextCCWEdges = function (node, label) {
|
|
var edges = node.getOuterEdges();
|
|
var firstOutDE, prevInDE;
|
|
for (var i = edges.length - 1; i >= 0; --i) {
|
|
var de = edges[i], sym = de.symetric, outDE = void 0, inDE = void 0;
|
|
if (de.label === label)
|
|
outDE = de;
|
|
if (sym.label === label)
|
|
inDE = sym;
|
|
if (!outDE || !inDE)
|
|
// This edge is not in edgering
|
|
continue;
|
|
if (inDE)
|
|
prevInDE = inDE;
|
|
if (outDE) {
|
|
if (prevInDE) {
|
|
prevInDE.next = outDE;
|
|
prevInDE = undefined;
|
|
}
|
|
if (!firstOutDE)
|
|
firstOutDE = outDE;
|
|
}
|
|
}
|
|
if (prevInDE)
|
|
prevInDE.next = firstOutDE;
|
|
};
|
|
/**
|
|
* Finds rings and labels edges according to which rings are.
|
|
*
|
|
* The label is a number which is increased for each ring.
|
|
*
|
|
* @returns {Edge[]} edges that start rings
|
|
*/
|
|
Graph.prototype._findLabeledEdgeRings = function () {
|
|
var edgeRingStarts = [];
|
|
var label = 0;
|
|
this.edges.forEach(function (edge) {
|
|
if (edge.label >= 0)
|
|
return;
|
|
edgeRingStarts.push(edge);
|
|
var e = edge;
|
|
do {
|
|
e.label = label;
|
|
e = e.next;
|
|
} while (!edge.isEqual(e));
|
|
label++;
|
|
});
|
|
return edgeRingStarts;
|
|
};
|
|
/**
|
|
* Computes the EdgeRings formed by the edges in this graph.
|
|
*
|
|
* @returns {EdgeRing[]} - A list of all the EdgeRings in the graph.
|
|
*/
|
|
Graph.prototype.getEdgeRings = function () {
|
|
var _this = this;
|
|
this._computeNextCWEdges();
|
|
// Clear labels
|
|
this.edges.forEach(function (edge) {
|
|
edge.label = undefined;
|
|
});
|
|
this._findLabeledEdgeRings().forEach(function (edge) {
|
|
// convertMaximalToMinimalEdgeRings
|
|
_this._findIntersectionNodes(edge).forEach(function (node) {
|
|
_this._computeNextCCWEdges(node, edge.label);
|
|
});
|
|
});
|
|
var edgeRingList = [];
|
|
// find all edgerings
|
|
this.edges.forEach(function (edge) {
|
|
if (edge.ring)
|
|
return;
|
|
edgeRingList.push(_this._findEdgeRing(edge));
|
|
});
|
|
return edgeRingList;
|
|
};
|
|
/**
|
|
* Find all nodes in a Maxima EdgeRing which are self-intersection nodes.
|
|
*
|
|
* @param {Node} startEdge - Start Edge of the Ring
|
|
* @returns {Node[]} - intersection nodes
|
|
*/
|
|
Graph.prototype._findIntersectionNodes = function (startEdge) {
|
|
var intersectionNodes = [];
|
|
var edge = startEdge;
|
|
var _loop_1 = function () {
|
|
// getDegree
|
|
var degree = 0;
|
|
edge.from.getOuterEdges().forEach(function (e) {
|
|
if (e.label === startEdge.label)
|
|
++degree;
|
|
});
|
|
if (degree > 1)
|
|
intersectionNodes.push(edge.from);
|
|
edge = edge.next;
|
|
};
|
|
do {
|
|
_loop_1();
|
|
} while (!startEdge.isEqual(edge));
|
|
return intersectionNodes;
|
|
};
|
|
/**
|
|
* Get the edge-ring which starts from the provided Edge.
|
|
*
|
|
* @param {Edge} startEdge - starting edge of the edge ring
|
|
* @returns {EdgeRing} - EdgeRing which start Edge is the provided one.
|
|
*/
|
|
Graph.prototype._findEdgeRing = function (startEdge) {
|
|
var edge = startEdge;
|
|
var edgeRing = new EdgeRing_1.default();
|
|
do {
|
|
edgeRing.push(edge);
|
|
edge.ring = edgeRing;
|
|
edge = edge.next;
|
|
} while (!startEdge.isEqual(edge));
|
|
return edgeRing;
|
|
};
|
|
/**
|
|
* Removes a node from the Graph.
|
|
*
|
|
* It also removes edges asociated to that node
|
|
* @param {Node} node - Node to be removed
|
|
*/
|
|
Graph.prototype.removeNode = function (node) {
|
|
var _this = this;
|
|
node.getOuterEdges().forEach(function (edge) { return _this.removeEdge(edge); });
|
|
node.innerEdges.forEach(function (edge) { return _this.removeEdge(edge); });
|
|
delete this.nodes[node.id];
|
|
};
|
|
/**
|
|
* Remove edge from the graph and deletes the edge.
|
|
*
|
|
* @param {Edge} edge - Edge to be removed
|
|
*/
|
|
Graph.prototype.removeEdge = function (edge) {
|
|
this.edges = this.edges.filter(function (e) { return !e.isEqual(edge); });
|
|
edge.deleteEdge();
|
|
};
|
|
return Graph;
|
|
}());
|
|
exports.default = Graph;
|