From 626350d13e41448e8214f713a8aecb01db23886d Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 21 May 2017 23:54:55 -0300 Subject: [PATCH 01/36] First commit --- README.md | 3 + index.js | 11 +++ package.json | 33 +++++++ test.js | 84 ++++++++++++++++++ util.js | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 369 insertions(+) create mode 100644 README.md create mode 100644 index.js create mode 100644 package.json create mode 100644 test.js create mode 100644 util.js diff --git a/README.md b/README.md new file mode 100644 index 0000000000..7cd896a79a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# @turf/polyonize + +# polygonize diff --git a/index.js b/index.js new file mode 100644 index 0000000000..56960ac507 --- /dev/null +++ b/index.js @@ -0,0 +1,11 @@ +const { Graph } = require('./util'); +/** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) + * @param FeatureCollection: TODO: preconditions! + * @return FeatureCollection + */ +module.export = function polygonize(geoJson) { + const graph = Graph.fromGeoJson(geoJson); + + graph.deleteDangles(); + graph.deleteCutEdges(); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000..7ee23ec3a6 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "@turf/polygonize", + "version": "4.3.0", + "description": "turf polygonize module", + "main": "index.js", + "scripts": { + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "git://github.com/Turfjs/turf.git" + }, + "keywords": [ + "turf", + "geojson", + "gis", + "polygonize" + ], + "author": "Turf Authors", + "contributors": [ + "Nicolas Cisco <@nickcis>" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/Turfjs/turf/issues" + }, + "homepage": "https://github.com/Turfjs/turf#readme", + "devDependencies": { + "@turf/helpers": "^4.3.0", + "tape": "^4.6.3" + }, + "dependencies": {} +} diff --git a/test.js b/test.js new file mode 100644 index 0000000000..6180366587 --- /dev/null +++ b/test.js @@ -0,0 +1,84 @@ +const test = require('tape'), + { Graph, Node, Edge } = require('./util'), + { featureCollection, lineString } = require('@turf/helpers'); + +test('graph.fromGeoJson', t => { + const geoJson = featureCollection([ + lineString([[0, 1], [0, 0]]), + lineString([[1, 1], [0, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); + + // Edges are symetric + t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); + + t.end(); +}); + +test('node.outerEdges CCW order', t => { + const geoJson = featureCollection([ + lineString([[0, 1], [0, 0]]), + lineString([[1, 1], [0, 0]]), + lineString([[1, 0], [0, 0]]), + lineString([[1, -1], [0, 0]]), + lineString([[0, -1], [0, 0]]), + lineString([[-1, -1], [0, 0]]), + lineString([[-1, 0], [0, 0]]), + lineString([[-1, 1], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson), + node = graph.getNode([0, 0]); + + t.deepEqual( + node.outerEdges.map(e => e.to.coordinates), + [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], + 'Outernodes have to ve in CCW order' + ); + + t.end(); +}); + +test('deleteDangles', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [0, 2]]), + lineString([[0, 1], [1, 1]]), + lineString([[1, 1], [1, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteDangles(); + + t.equal(Object.keys(graph.nodes).length, 4); + + t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); + + t.end(); +}); + +test('deleteCutEdges', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + + t.equal(Object.keys(graph.nodes).length, 6); + t.equal(graph.edges.length, 12); + + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); + + t.end(); +}); diff --git a/util.js b/util.js new file mode 100644 index 0000000000..d5c95c262b --- /dev/null +++ b/util.js @@ -0,0 +1,238 @@ +// 1. PolygonizerGraph -> Grafo dirijo (a ambos lados) y no dirijido +// +// 2. Delete Dangles -> Remueve todos los nodos que tengan grado 1 +// 3. deleteCutEdges -> Numerar los edges de acuerdo a los rings, si un edge y su simetrico tienen el mismo numero, significa que son cut-edges (bridge) + +class Graph { + static fromGeoJson(geoJson) { + const graph = new Graph(); + geoJson.features.forEach(feature => { + const start = graph.getNode(feature.geometry.coordinates[0]), + end = graph.getNode(feature.geometry.coordinates[1]); + + graph.addEdge(start, end); + }); + + return graph; + } + + /** Creates or get a Node + * @param Array coordinates + * @return Node + */ + getNode(coordinates) { + const id = Node.buildId(coordinates); + let node = this.nodes[id]; + if (!node) + node = this.nodes[id] = new Node(coordinates); + + return node; + } + + /** Edges are added symetrically + */ + addEdge(from, to) { + const edge = new Edge(from, to), + symetricEdge = edge.getSymetric(); + + this.edges.push(edge); + this.edges.push(symetricEdge); + } + + constructor() { + this.edges = []; + + // The key is the `id` of the Node (ie: coordinates.join(',')) + this.nodes = {}; + } + + /** Remove Dangle Nodes (nodes with grade 1) + */ + deleteDangles() { + Object.values(this.nodes) + .forEach(node => 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 + */ + _removeIfDangle(node) { + // As edges are directed and symetrical, we count only innerEdges + if (node.innerEdges.length <= 1) { + const outerNodes = node.outerEdges.map(e => e.to); + this.removeNode(node); + outerNodes.forEach(n => this._removeIfDangle(n)); + } + } + + /** Delete cut-edges (bridge edges) + */ + deleteCutEdges() { + Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + this._findLabeledEdgeRings(); + + // Cut-edges (bridges) are edges where both edges have the same label + this.edges.forEach(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 is the previous one. + * OuterEdges are sorted CCW. + * @param Node + */ + _computeNextCWEdges(node) { + node.outerEdges.forEach((edge, i) => { + edge.symetric.next = node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1]; + }); + } + + /** Finds rings and labels edges according to which rings are. + * The label is a number which is increased for each ring. + * @return Array> + */ + _findLabeledEdgeRings() { + const rings = []; + let label = 0; + this.edges.forEach(edge => { + if (edge.label >= 0) + return; + + const ring = []; + let e = edge; + do { + e.label = label; + ring.push(e); + e = e.next; + } while (!edge.isEqual(e)); + + label++; + rings.push(ring); + }); + + return rings; + } + + /** Removes a node from the Graph. + * It also removes edges asociated to that node + * @param Node + */ + removeNode(node) { + node.outerEdges.forEach(edge => this.removeEdge(edge)); + node.innerEdges.forEach(edge => this.removeEdge(edge)); + delete this.nodes[node.id]; + } + + /** Remove edge from the graph and deletes the edge + * @param Edge + */ + removeEdge(edge) { + this.edges = this.edges.filter(e => !e.isEqual(edge)); + edge.deleteEdge(); + } +} + +class Edge { + /** Creates or get the symetric Edge + * @return Edge + */ + getSymetric() { + if (! this.symetric) { + this.symetric = new Edge(this.to, this.from); + this.symetric.symetric = this; + } + + return this.symetric; + } + + constructor(from, to) { + this.from = from; //< start + this.to = to; //< End + + this.next = undefined; //< The edge to be computed after + this.label = undefined; //< Used in order to detect Cut Edges (Bridges) + this.symetric = undefined; //< The symetric edge of this + + this.from.addOuterEdge(this); + this.to.addInnerEdge(this); + } + + deleteEdge() { + this.from.removeOuterEdge(this); + this.to.removeInnerEdge(this); + } + + isEqual(edge) { + return this.from.id == edge.from.id && this.to.id == edge.to.id; + } +} + +class Node { + static buildId(coordinates) { + return coordinates.join(','); + } + + constructor(coordinates) { + this.id = Node.buildId(coordinates); + this.coordinates = coordinates; + this.innerEdges = []; //< Array + + // We wil store to (out) edges in an CCW order + this.outerEdges = []; //< Array + } + + removeInnerEdge(edge) { + this.innerEdges = this.innerEdges.filter(e => e.from.id != edge.from.id); + } + + removeOuterEdge(edge) { + this.outerEdges = this.outerEdges.filter(e => e.to.id != edge.to.id); + } + + /** Outer edges are stored CCW order + */ + addOuterEdge(edge) { + this.outerEdges.push(edge); + this.outerEdges.sort((a, b) => { + const aNode = a.to, + bNode = b.to; + + if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) + return 1; + if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) + return -1; + + if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { + if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) + return aNode.coordinates[1] - bNode.coordinates[1]; + return bNode.coordinates[1] - aNode.coordinates[1]; + } + + const det = (aNode.coordinates[0] - this.coordinates[0]) * (bNode.coordinates[1] - this.coordinates[1]) - + (bNode.coordinates[0] - this.coordinates[0]) * (aNode.coordinates[1] - this.coordinates[1]); + if (det < 0) + return 1; + if (det > 0) + return -1; + + const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), + d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); + + return d1 - d2; + }); + } + + addInnerEdge(edge) { + this.innerEdges.push(edge); + } +} + +module.exports = { + Graph, + Node, + Edge +}; From 25ab9ef5501fa79556e2e98e58e98af8a3bae675 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Mon, 22 May 2017 04:10:15 -0300 Subject: [PATCH 02/36] Implemented getEdgeRings --- test.js | 20 +++++++++ util.js | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 140 insertions(+), 8 deletions(-) diff --git a/test.js b/test.js index 6180366587..311d9cd3f3 100644 --- a/test.js +++ b/test.js @@ -82,3 +82,23 @@ test('deleteCutEdges', t => { t.end(); }); + +test('getEdgeRings', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + const edgeRings = graph.getEdgeRings(); + + console.log(edgeRings); + + t.end(); +}); diff --git a/util.js b/util.js index d5c95c262b..381acef3c0 100644 --- a/util.js +++ b/util.js @@ -68,7 +68,7 @@ class Graph { /** Delete cut-edges (bridge edges) */ deleteCutEdges() { - Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + this._computeNextCWEdges(); this._findLabeledEdgeRings(); // Cut-edges (bridges) are edges where both edges have the same label @@ -83,38 +83,149 @@ class Graph { /** 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 is the previous one. * OuterEdges are sorted CCW. - * @param Node + * + * @param Node (optional) If no node is passed, the function calls itself for every node in the Graph */ _computeNextCWEdges(node) { + if (typeof(node) == "undefined") + return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + node.outerEdges.forEach((edge, i) => { edge.symetric.next = node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1]; }); } + /** 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 PolygonizeGraph::computeNextCCWEdges + * + * @param Node + * @param label + */ + _computeNextCCWEdges(node, label) { + const edges = node.outerEdges; + let firstOutDE, + prevInDE; + + for (let i = edges.length - 1; i >= 0; --i) { + let de = edges[i], + sym = de.symetric, + outDE, + inDE; + + 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. - * @return Array> + * @return Array edges that start rings */ _findLabeledEdgeRings() { - const rings = []; + const edgeRingStarts = []; let label = 0; this.edges.forEach(edge => { if (edge.label >= 0) return; - const ring = []; + edgeRingStarts.push(edge); + let e = edge; do { e.label = label; - ring.push(e); e = e.next; } while (!edge.isEqual(e)); label++; - rings.push(ring); }); - return rings; + return edgeRingStarts; + } + + /** Computes the EdgeRings formed by the edges in this graph + * @return Array TODO: + */ + getEdgeRings() { + this._computeNextCWEdges(); + + // Clear labels + this.edges.forEach(edge => edge.label = undefined); + + // convertMaximalToMinimalEdgeRings + this._findLabeledEdgeRings().forEach(edge => { + this._findIntersectionNodes(edge).forEach(node => { + this._computeNextCCWEdges(node, edge.label) + }); + }); + + const edgeRingList = []; + + // find all edgerings + this.edges.forEach(edge => { + if (edge.ring) + return; + edgeRingList.push(this._findEdgeRing(edge)); + }); + + return edgeRingList; + } + + _findIntersectionNodes(startEdge) { + const intersectionNodes = []; + let edge = startEdge; + do { + // getDegree + let degree = 0; + edge.from.outerEdges.forEach(e => { + if (startEdge.label == e.label) + ++degree; + }); + + if (degree > 1) + intersectionNodes.push(edge.from); + + edge = edge.next; + } while(!startEdge.isEqual(edge)); + + return intersectionNodes; + } + + _findEdgeRing(startEdge) { + let edge = startEdge; + const edgeRing = []; + + do { + edgeRing.push(edge); + edge.ring = edgeRing; + edge = edge.next; + } while(!startEdge.isEqual(edge)); + + return edgeRing; } /** Removes a node from the Graph. @@ -156,6 +267,7 @@ class Edge { this.next = undefined; //< The edge to be computed after this.label = undefined; //< Used in order to detect Cut Edges (Bridges) this.symetric = undefined; //< The symetric edge of this + this.ring = undefined; //< TODO: explanation this.from.addOuterEdge(this); this.to.addInnerEdge(this); From 1fd022086d33f934c43f4686dbff5f2486d56abd Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 23 May 2017 02:28:56 -0300 Subject: [PATCH 03/36] Added EdgeRing class. Improved documentation --- index.js | 21 ++++++++++- test.js | 6 +++- util.js | 107 ++++++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 112 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 56960ac507..296b7ecb65 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ -const { Graph } = require('./util'); +const { Graph, findValidEdgeRings } = require('./util'); + /** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) * @param FeatureCollection: TODO: preconditions! * @return FeatureCollection @@ -6,6 +7,24 @@ const { Graph } = require('./util'); module.export = function polygonize(geoJson) { const graph = Graph.fromGeoJson(geoJson); + // 1. Remove dangle node graph.deleteDangles(); + + // 2. Remove cut-edges (bridge edges) graph.deleteCutEdges(); + + // 3. Get all holes and shells + const holes = [], + shells = []; + + graph.getEdgeRings().forEach(edgeRing => { + if (edgeRing.isHole()) + holes.push(edgeRing); + else + shells.push(edgeRing); + }); + + // 4. Assign Holes to Shells + + // 5. EdgeRings to Polygons }; diff --git a/test.js b/test.js index 311d9cd3f3..beed7fb5e7 100644 --- a/test.js +++ b/test.js @@ -98,7 +98,11 @@ test('getEdgeRings', t => { graph.deleteCutEdges(); const edgeRings = graph.getEdgeRings(); - console.log(edgeRings); + t.equal(edgeRings.length, 4); + + edgeRings.forEach(edgeRing => { + t.equal(edgeRing.length, 3); + }); t.end(); }); diff --git a/util.js b/util.js index 381acef3c0..0cd7b01a4c 100644 --- a/util.js +++ b/util.js @@ -1,8 +1,32 @@ -// 1. PolygonizerGraph -> Grafo dirijo (a ambos lados) y no dirijido -// -// 2. Delete Dangles -> Remueve todos los nodos que tengan grado 1 -// 3. deleteCutEdges -> Numerar los edges de acuerdo a los rings, si un edge y su simetrico tienen el mismo numero, significa que son cut-edges (bridge) +/** Returns the direction of the point q relative to the vector p1 -> p2. + * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() + * + * @param p1 [Array]: the origin point of the vector + * @param p2 [Array]: the final point of the vector + * @param q [Array]: the point to compute the direction to + * + * @return 1 if q is ccw (left) from p1->p2, + * -1 if q is cw (right) from p1->p2, + * 0 if q is colinear with p1->p2 + */ +function orientationIndex(p1, p2, q) { + const dx1 = p2[0] - p1[0], + dy1 = p2[1] - p1[1], + dx2 = q[0] - p2[0], + dy2 = q[1] - p2[1]; + + return Math.sign(dx1 * dy2 - dx2 * dy1); +} +/** 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) + */ class Graph { static fromGeoJson(geoJson) { const graph = new Graph(); @@ -17,8 +41,8 @@ class Graph { } /** Creates or get a Node - * @param Array coordinates - * @return Node + * @param coordinates [Array] + * @return [Node] */ getNode(coordinates) { const id = Node.buildId(coordinates); @@ -55,6 +79,8 @@ class Graph { /** 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] */ _removeIfDangle(node) { // As edges are directed and symetrical, we count only innerEdges @@ -65,7 +91,11 @@ class Graph { } } - /** Delete cut-edges (bridge edges) + /** 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. */ deleteCutEdges() { this._computeNextCWEdges(); @@ -81,10 +111,10 @@ class Graph { } /** 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 is the previous one. + * 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 (optional) If no node is passed, the function calls itself for every node in the Graph + * @param node [Node]: (optional) If no node is passed, the function calls itself for every node in the Graph */ _computeNextCWEdges(node) { if (typeof(node) == "undefined") @@ -98,9 +128,10 @@ class Graph { /** 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 PolygonizeGraph::computeNextCCWEdges + * XXX: method literally transcribed from PolygonizeGraph::computeNextCCWEdges, could be written + * in a more javascript way * - * @param Node + * @param node [Node] * @param label */ _computeNextCCWEdges(node, label) { @@ -144,6 +175,7 @@ class Graph { /** Finds rings and labels edges according to which rings are. * The label is a number which is increased for each ring. + * * @return Array edges that start rings */ _findLabeledEdgeRings() { @@ -167,8 +199,9 @@ class Graph { return edgeRingStarts; } - /** Computes the EdgeRings formed by the edges in this graph - * @return Array TODO: + /** Computes the EdgeRings formed by the edges in this graph. + * + * @return Array */ getEdgeRings() { this._computeNextCWEdges(); @@ -215,9 +248,13 @@ class Graph { return intersectionNodes; } + /** Get the edge-ring which starts from the provided Edge + * @param startEdge [Edge]: starting edge of the edge ring + * @return [EdgeRing] + */ _findEdgeRing(startEdge) { let edge = startEdge; - const edgeRing = []; + const edgeRing = new EdgeRing(); do { edgeRing.push(edge); @@ -230,7 +267,7 @@ class Graph { /** Removes a node from the Graph. * It also removes edges asociated to that node - * @param Node + * @param node [Node] */ removeNode(node) { node.outerEdges.forEach(edge => this.removeEdge(edge)); @@ -239,7 +276,7 @@ class Graph { } /** Remove edge from the graph and deletes the edge - * @param Edge + * @param edge [Edge] */ removeEdge(edge) { this.edges = this.edges.filter(e => !e.isEqual(edge)); @@ -247,6 +284,8 @@ class Graph { } } +/** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge + */ class Edge { /** Creates or get the symetric Edge * @return Edge @@ -260,6 +299,10 @@ class Edge { return this.symetric; } + /** + * @param from [Node]: start node of the Edge + * @param to [Node]: end node of the edge + */ constructor(from, to) { this.from = from; //< start this.to = to; //< End @@ -273,6 +316,8 @@ class Edge { this.to.addInnerEdge(this); } + /** Removes edge from from and to nodes + */ deleteEdge() { this.from.removeOuterEdge(this); this.to.removeInnerEdge(this); @@ -281,6 +326,10 @@ class Edge { isEqual(edge) { return this.from.id == edge.from.id && this.to.id == edge.to.id; } + + toString() { + return `Edge { ${this.from.id} -> ${this.to.id} }`; + } } class Node { @@ -290,7 +339,7 @@ class Node { constructor(coordinates) { this.id = Node.buildId(coordinates); - this.coordinates = coordinates; + this.coordinates = coordinates; //< Array this.innerEdges = []; //< Array // We wil store to (out) edges in an CCW order @@ -306,6 +355,7 @@ class Node { } /** Outer edges are stored CCW order + * @param edge [Edge] */ addOuterEdge(edge) { this.outerEdges.push(edge); @@ -324,8 +374,7 @@ class Node { return bNode.coordinates[1] - aNode.coordinates[1]; } - const det = (aNode.coordinates[0] - this.coordinates[0]) * (bNode.coordinates[1] - this.coordinates[1]) - - (bNode.coordinates[0] - this.coordinates[0]) * (aNode.coordinates[1] - this.coordinates[1]); + const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); if (det < 0) return 1; if (det > 0) @@ -343,8 +392,26 @@ class Node { } } +/** Ring of edges which form a polygon. + * The ring may be either an outer shell or a hole. + * + * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing + */ +class EdgeRing extends Array { + + /** Tests whether this ring is a hole. + * + * A ring is a hole if it is oriented counter-clockwise. + * @return Boolean true: if it is a hole + */ + isHole() { + + + } +} + module.exports = { Graph, Node, - Edge + Edge, }; From fc43147e945e4cce72e2f4d25e3b59e96a6e5e0f Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 23 May 2017 03:25:56 -0300 Subject: [PATCH 04/36] Implemented EdgeRing.isHole --- test.js | 30 +++++++++++++++++++++++++++++- util.js | 20 +++++++++++++++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/test.js b/test.js index beed7fb5e7..bba695bca8 100644 --- a/test.js +++ b/test.js @@ -1,5 +1,5 @@ const test = require('tape'), - { Graph, Node, Edge } = require('./util'), + { Graph, Node, Edge, EdgeRing } = require('./util'), { featureCollection, lineString } = require('@turf/helpers'); test('graph.fromGeoJson', t => { @@ -106,3 +106,31 @@ test('getEdgeRings', t => { t.end(); }); + +test('EdgeRing.isHole', t => { + function getNextEdge(edge) { + return edge.to.outerEdges.find(e => !e.isEqual(edge.symetric)); + } + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + let edgeRing = new EdgeRing(); + edgeRing.push(graph.edges.find(e => e.from.id == Node.buildId([0,0]) && e.to.id == Node.buildId([1,0]))); + edgeRing.push(getNextEdge(edgeRing[0])); + edgeRing.push(getNextEdge(edgeRing[1])); + + t.ok(edgeRing.isHole()); + + edgeRing = new EdgeRing(); + edgeRing.push(graph.edges.find(e => e.from.id == Node.buildId([0,0]) && e.to.id == Node.buildId([0,1]))); + edgeRing.push(getNextEdge(edgeRing[0])); + edgeRing.push(getNextEdge(edgeRing[1])); + + t.notOk(edgeRing.isHole()); + + t.end(); +}); diff --git a/util.js b/util.js index 0cd7b01a4c..60693a597f 100644 --- a/util.js +++ b/util.js @@ -398,15 +398,28 @@ class Node { * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing */ class EdgeRing extends Array { + // XXX: isValid? /** Tests whether this ring is a hole. - * * A ring is a hole if it is oriented counter-clockwise. + * Similar implementation of geos::algorithm::CGAlgorithms::isCCW * @return Boolean true: if it is a hole */ isHole() { - - + // XXX: Assuming Ring is valid + // Find highest point + const hiIndex = this.reduce((high, edge, i) => { + if (edge.from.coordinates[1] > this[high].from.coordinates[1]) + high = i; + return high; + }, 0), + iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, + iNext = (hiIndex + 1) % this.length, + disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); + + if (disc == 0) + return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; + return disc > 0; } } @@ -414,4 +427,5 @@ module.exports = { Graph, Node, Edge, + EdgeRing }; From 067cb335bd294e006dfc1084b7d4c82254524040 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 23 May 2017 03:33:25 -0300 Subject: [PATCH 05/36] Added more comments --- index.js | 12 +++++++++--- test.js | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 296b7ecb65..b7bac19a91 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,14 @@ -const { Graph, findValidEdgeRings } = require('./util'); +const { Graph } = require('./util'); /** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) - * @param FeatureCollection: TODO: preconditions! - * @return FeatureCollection + * + * Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly + * noded, i.e., they must only meet at their endpoints. + * + * LineStrings must only have to coordinate points. + * + * @param geoJson [FeatureCollection]: Lines in order to polygonize + * @return [FeatureCollection] */ module.export = function polygonize(geoJson) { const graph = Graph.fromGeoJson(geoJson); diff --git a/test.js b/test.js index bba695bca8..1c341ee48a 100644 --- a/test.js +++ b/test.js @@ -123,14 +123,14 @@ test('EdgeRing.isHole', t => { edgeRing.push(getNextEdge(edgeRing[0])); edgeRing.push(getNextEdge(edgeRing[1])); - t.ok(edgeRing.isHole()); + t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); edgeRing = new EdgeRing(); edgeRing.push(graph.edges.find(e => e.from.id == Node.buildId([0,0]) && e.to.id == Node.buildId([0,1]))); edgeRing.push(getNextEdge(edgeRing[0])); edgeRing.push(getNextEdge(edgeRing[1])); - t.notOk(edgeRing.isHole()); + t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); t.end(); }); From acabf67a5c4b93699ffd892edcb9238530842a19 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 23 May 2017 15:45:11 -0300 Subject: [PATCH 06/36] Updating comments to JsDoc --- index.js | 8 ++++++-- util.js | 62 ++++++++++++++++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/index.js b/index.js index b7bac19a91..25e2c62f17 100644 --- a/index.js +++ b/index.js @@ -7,8 +7,8 @@ const { Graph } = require('./util'); * * LineStrings must only have to coordinate points. * - * @param geoJson [FeatureCollection]: Lines in order to polygonize - * @return [FeatureCollection] + * @param {FeatureCollection} geoJson - Lines in order to polygonize + * @return {FeatureCollection} */ module.export = function polygonize(geoJson) { const graph = Graph.fromGeoJson(geoJson); @@ -31,6 +31,10 @@ module.export = function polygonize(geoJson) { }); // 4. Assign Holes to Shells + holes.forEach(hole => { + if (!EdgeRing.findEdgeRingContaining(hole, shells)) + shells.push(hole); + }); // 5. EdgeRings to Polygons }; diff --git a/util.js b/util.js index 60693a597f..4c77bd19c6 100644 --- a/util.js +++ b/util.js @@ -1,9 +1,9 @@ /** Returns the direction of the point q relative to the vector p1 -> p2. * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() * - * @param p1 [Array]: the origin point of the vector - * @param p2 [Array]: the final point of the vector - * @param q [Array]: the point to compute the direction to + * @param {Number[]} p1 - the origin point of the vector + * @param {Number[]} p2 - the final point of the vector + * @param {Number[]} q - the point to compute the direction to * * @return 1 if q is ccw (left) from p1->p2, * -1 if q is cw (right) from p1->p2, @@ -41,8 +41,8 @@ class Graph { } /** Creates or get a Node - * @param coordinates [Array] - * @return [Node] + * @param {Number[]} coordinates + * @return {Node} */ getNode(coordinates) { const id = Node.buildId(coordinates); @@ -80,7 +80,7 @@ class Graph { /** 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] + * @param {Node} node */ _removeIfDangle(node) { // As edges are directed and symetrical, we count only innerEdges @@ -114,7 +114,7 @@ class Graph { * 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]: (optional) If no node is passed, the function calls itself for every node in the Graph + * @param {Node} [node] - If no node is passed, the function calls itself for every node in the Graph */ _computeNextCWEdges(node) { if (typeof(node) == "undefined") @@ -131,8 +131,8 @@ class Graph { * XXX: method literally transcribed from PolygonizeGraph::computeNextCCWEdges, could be written * in a more javascript way * - * @param node [Node] - * @param label + * @param {Node} node + * @param {Number} label */ _computeNextCCWEdges(node, label) { const edges = node.outerEdges; @@ -176,7 +176,7 @@ class Graph { /** Finds rings and labels edges according to which rings are. * The label is a number which is increased for each ring. * - * @return Array edges that start rings + * @return {Edge[]} edges that start rings */ _findLabeledEdgeRings() { const edgeRingStarts = []; @@ -201,7 +201,7 @@ class Graph { /** Computes the EdgeRings formed by the edges in this graph. * - * @return Array + * @return {EdgeRing[]} */ getEdgeRings() { this._computeNextCWEdges(); @@ -248,9 +248,10 @@ class Graph { return intersectionNodes; } - /** Get the edge-ring which starts from the provided Edge - * @param startEdge [Edge]: starting edge of the edge ring - * @return [EdgeRing] + /** Get the edge-ring which starts from the provided Edge. + * + * @param {Edge} startEdge - starting edge of the edge ring + * @return {EdgeRing} */ _findEdgeRing(startEdge) { let edge = startEdge; @@ -266,8 +267,9 @@ class Graph { } /** Removes a node from the Graph. + * * It also removes edges asociated to that node - * @param node [Node] + * @param {Node} node */ removeNode(node) { node.outerEdges.forEach(edge => this.removeEdge(edge)); @@ -276,7 +278,8 @@ class Graph { } /** Remove edge from the graph and deletes the edge - * @param edge [Edge] + * + * @param {Edge} edge */ removeEdge(edge) { this.edges = this.edges.filter(e => !e.isEqual(edge)); @@ -288,7 +291,8 @@ class Graph { */ class Edge { /** Creates or get the symetric Edge - * @return Edge + * + * @return {Edge} */ getSymetric() { if (! this.symetric) { @@ -300,8 +304,8 @@ class Edge { } /** - * @param from [Node]: start node of the Edge - * @param to [Node]: end node of the edge + * @param {Node} from - start node of the Edge + * @param {Node} to - end node of the edge */ constructor(from, to) { this.from = from; //< start @@ -339,11 +343,11 @@ class Node { constructor(coordinates) { this.id = Node.buildId(coordinates); - this.coordinates = coordinates; //< Array - this.innerEdges = []; //< Array + this.coordinates = coordinates; //< Number[] + this.innerEdges = []; //< Edge[] // We wil store to (out) edges in an CCW order - this.outerEdges = []; //< Array + this.outerEdges = []; //< Edge[] } removeInnerEdge(edge) { @@ -355,7 +359,7 @@ class Node { } /** Outer edges are stored CCW order - * @param edge [Edge] + * @param {Edge} edge */ addOuterEdge(edge) { this.outerEdges.push(edge); @@ -403,7 +407,7 @@ class EdgeRing extends Array { /** Tests whether this ring is a hole. * A ring is a hole if it is oriented counter-clockwise. * Similar implementation of geos::algorithm::CGAlgorithms::isCCW - * @return Boolean true: if it is a hole + * @return {Boolean} - true: if it is a hole */ isHole() { // XXX: Assuming Ring is valid @@ -421,6 +425,16 @@ class EdgeRing extends Array { return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; return disc > 0; } + + /** + * + * @param {EdgeRing} edgeRing + * @param {EdgeRing[]} shellList + * + * @return {EdgeRing} + */ + static findEdgeRingContaining(edgeRing, shellList) { + } } module.exports = { From fb6a2e5d94d8d0b528a9a28c3f68db236ae93127 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 23 May 2017 19:33:03 -0300 Subject: [PATCH 07/36] Adding findEdgeRingContaining: to be finished --- package.json | 7 +++-- util.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7ee23ec3a6..ff6cfb0134 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,11 @@ }, "homepage": "https://github.com/Turfjs/turf#readme", "devDependencies": { - "@turf/helpers": "^4.3.0", "tape": "^4.6.3" }, - "dependencies": {} + "dependencies": { + "@turf/envelope": "^4.3.0", + "@turf/helpers": "^4.3.0", + "@turf/inside": "^4.3.0" + } } diff --git a/util.js b/util.js index 4c77bd19c6..9c6a092449 100644 --- a/util.js +++ b/util.js @@ -1,3 +1,7 @@ +const { multiPoint, lineString, point } = require('@turf/helpers'), + envelope = require('@turf/envelope'), + inside = require('@turf/inside'); + /** Returns the direction of the point q relative to the vector p1 -> p2. * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() * @@ -18,6 +22,36 @@ function orientationIndex(p1, p2, q) { return Math.sign(dx1 * dy2 - dx2 * dy1); } +/** Checks if two envelopes are equal + * The function assumes that the arguments are envelopes, i.e.: Rectangular polygons + * + * @param {Feature} env1 - Envelope + * @param {Feature} env2 - Envelope + * @return {Boolean} - True if the envelopes are equal + */ +function envelopeIsEqual(env1, env2) { + const envX1 = env1.geometry.coordinates.map(c => c[0]), + envY1 = env1.geometry.coordinates.map(c => c[1]), + envX2 = env2.geometry.coordinates.map(c => c[0]), + envY2 = env2.geometry.coordinates.map(c => c[1]); + + return Math.max(null, envX1) == Math.max(null, envX2) && + Math.max(null, envY1) == Math.max(null, envY2) && + Math.min(null, envX1) == Math.min(null, envX2) && + Math.min(null, envY1) == Math.min(null, envY2); +} + +/** Check if a envelope is contained in other one + * The function assumes that the arguments are envelopes, i.e.: Rectangular polygons + * + * @param {Feature} self - Envelope + * @param {Feature} env - Envelope + * @return {Boolean} - True if env is contained in self + */ +function envelopeContains(self, env) { + return env.geometry.coordinates.every(c => inside(point(c), self)); +} + /** Represents a planar graph of edges and nodes that can be used to compute a * polygonization. * @@ -334,6 +368,13 @@ class Edge { toString() { return `Edge { ${this.from.id} -> ${this.to.id} }`; } + + /** + * @return {Feature} + */ + toLineString() { + return lineString([this.from.coordinates, this.to.coordinates]); + } } class Node { @@ -426,14 +467,51 @@ class EdgeRing extends Array { return disc > 0; } + /** Creates a MultiPoint representing the EdgeRing (discarts edges directions) + * @return {Feature} + */ + toMultiPoint() { + return multiPoint(this.map(edge => edge.from.coordinates)); + } + + /** + * @return {Feature} envelope + */ + getEnvelope() { + return envelope(this.toMultiPoint()); + } + /** + * geos::operation::polygonize::EdgeRing::findEdgeRingContaining * * @param {EdgeRing} edgeRing * @param {EdgeRing[]} shellList * * @return {EdgeRing} */ - static findEdgeRingContaining(edgeRing, shellList) { + static findEdgeRingContaining(testEdgeRing, shellList) { + const testEnvelope = testEdgeRing.getEnvelope(), + testPoint = testEnvelope.geometry.coordinates[0]; + + let minEnvelope, + minShell; + shellList.forEach(shell => { + const tryEnvelope = shell.getEnvelope(); + + if (minShell) + minEnvelope = minShell.getEnvelope(); + + // the hole envelope cannot equal the shell envelope + if (envelopeIsEqual(tryEnvelope, testEnvelope)) + return; + + // TODO: + if (envelopeContains(tryEnvelope, testEnvelope)) { + + } + + }); + } } From b1593b06449596a89efa66fd3210f19bcb8498b2 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Wed, 24 May 2017 21:28:35 -0300 Subject: [PATCH 08/36] Finished algorthm, not working correctly --- index.js | 9 +++++++-- test.js | 21 ++++++++++++++++++++- util.js | 40 ++++++++++++++++++++++++++++++++++------ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 25e2c62f17..f616f268a9 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ -const { Graph } = require('./util'); +const { Graph, EdgeRing } = require('./util'), + { featureCollection } = require('@turf/helpers'); /** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) * @@ -10,7 +11,7 @@ const { Graph } = require('./util'); * @param {FeatureCollection} geoJson - Lines in order to polygonize * @return {FeatureCollection} */ -module.export = function polygonize(geoJson) { +module.exports = function polygonize(geoJson) { const graph = Graph.fromGeoJson(geoJson); // 1. Remove dangle node @@ -30,6 +31,9 @@ module.export = function polygonize(geoJson) { shells.push(edgeRing); }); + console.log(JSON.stringify(featureCollection(holes.map(h => h.toPolygon())))); + console.log(JSON.stringify(featureCollection(shells.map(s => s.toPolygon())))); + // 4. Assign Holes to Shells holes.forEach(hole => { if (!EdgeRing.findEdgeRingContaining(hole, shells)) @@ -37,4 +41,5 @@ module.export = function polygonize(geoJson) { }); // 5. EdgeRings to Polygons + return featureCollection(shells.map(shell => shell.toPolygon())); }; diff --git a/test.js b/test.js index 1c341ee48a..ef73aea2d6 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,7 @@ const test = require('tape'), { Graph, Node, Edge, EdgeRing } = require('./util'), - { featureCollection, lineString } = require('@turf/helpers'); + { featureCollection, lineString } = require('@turf/helpers'), + polygonize = require('./index.js'); test('graph.fromGeoJson', t => { const geoJson = featureCollection([ @@ -134,3 +135,21 @@ test('EdgeRing.isHole', t => { t.end(); }); + +test('Full', t => { + const geoJson = featureCollection([ + lineString([[-58.3959417, -34.8036499], [-58.395087, -34.8031464]]), + lineString([[-58.3964727, -34.8029764], [-58.3959417, -34.8036499]]), + lineString([[-58.395087, -34.8031464], [-58.3942164, -34.8042266]]), + lineString([[-58.3942164, -34.8042266], [-58.3949969, -34.8047067]]), + lineString([[-58.3949969, -34.8047067], [-58.3957427, -34.8051655]]), + lineString([[-58.396618, -34.8040484], [-58.3957427, -34.8051655]]), + lineString([[-58.3976747, -34.8036356], [-58.3971168, -34.8043422]]), + lineString([[-58.3976747, -34.8036356], [-58.3964727, -34.8029764]]), + lineString([[-58.3971168, -34.8043422], [-58.396618, -34.8040484]]), + lineString([[-58.396618, -34.8040484], [-58.3959417, -34.8036499]]), + ]), + polygons = polygonize(geoJson); + console.log(JSON.stringify(polygons)); + t.end(); +}); diff --git a/util.js b/util.js index 9c6a092449..c8e81291a6 100644 --- a/util.js +++ b/util.js @@ -1,4 +1,4 @@ -const { multiPoint, lineString, point } = require('@turf/helpers'), +const { multiPoint, lineString, point, polygon } = require('@turf/helpers'), envelope = require('@turf/envelope'), inside = require('@turf/inside'); @@ -43,13 +43,14 @@ function envelopeIsEqual(env1, env2) { /** Check if a envelope is contained in other one * The function assumes that the arguments are envelopes, i.e.: Rectangular polygons + * XXX: Envelopes are rectangular, it could be optimized by using that assumption * * @param {Feature} self - Envelope * @param {Feature} env - Envelope * @return {Boolean} - True if env is contained in self */ function envelopeContains(self, env) { - return env.geometry.coordinates.every(c => inside(point(c), self)); + return env.geometry.coordinates[0].every(c => inside(point(c), self)); } /** Represents a planar graph of edges and nodes that can be used to compute a @@ -474,6 +475,14 @@ class EdgeRing extends Array { return multiPoint(this.map(edge => edge.from.coordinates)); } + /** Creates a Polygon representing the EdgeRing + */ + toPolygon() { + const coordinates = this.map(edge => edge.from.coordinates); + coordinates.push(this[0].from.coordinates); + return polygon([coordinates]); + } + /** * @return {Feature} envelope */ @@ -490,8 +499,7 @@ class EdgeRing extends Array { * @return {EdgeRing} */ static findEdgeRingContaining(testEdgeRing, shellList) { - const testEnvelope = testEdgeRing.getEnvelope(), - testPoint = testEnvelope.geometry.coordinates[0]; + const testEnvelope = testEdgeRing.getEnvelope(); let minEnvelope, minShell; @@ -507,11 +515,31 @@ class EdgeRing extends Array { // TODO: if (envelopeContains(tryEnvelope, testEnvelope)) { - + const testPoint = testEdgeRing.find(edge => { + return shell.find(e => { + if (e.from.coordinates[0] == edge.from.coordinates[0] && e.from.coordinates[1] == edge.from.coordinates[1]) + return true; + return false; + }); + }); + + if (testPoint && shell.inside(point(testPoint.from.coordinates))) { + if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) + minShell = shell; + } } - }); + return minShell; + } + + /** Checks if the point is inside the edgeRing + * + * @param {Feature} point + * @return {Boolean} + */ + inside(point) { + return inside(point, this.toPolygon()); } } From 365f6fcb9046ada6a4811e62cec0f49bd96ef8ef Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 03:32:06 -0300 Subject: [PATCH 09/36] Works as expected :) --- index.js | 2 +- util.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index f616f268a9..09d3a6d3e9 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ module.exports = function polygonize(geoJson) { // 4. Assign Holes to Shells holes.forEach(hole => { - if (!EdgeRing.findEdgeRingContaining(hole, shells)) + if (EdgeRing.findEdgeRingContaining(hole, shells)) shells.push(hole); }); diff --git a/util.js b/util.js index c8e81291a6..9ba5431bfe 100644 --- a/util.js +++ b/util.js @@ -4,6 +4,7 @@ const { multiPoint, lineString, point, polygon } = require('@turf/helpers'), /** Returns the direction of the point q relative to the vector p1 -> p2. * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() + * (same as geos::algorithm::CGAlgorithm::computeOrientation()) * * @param {Number[]} p1 - the origin point of the vector * @param {Number[]} p2 - the final point of the vector @@ -99,7 +100,7 @@ class Graph { } constructor() { - this.edges = []; + this.edges = []; //< {Edge[]} dirEdges // The key is the `id` of the Node (ie: coordinates.join(',')) this.nodes = {}; @@ -156,7 +157,7 @@ class Graph { return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); node.outerEdges.forEach((edge, i) => { - edge.symetric.next = node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1]; + node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1].symetric.next = edge; }); } @@ -244,8 +245,9 @@ class Graph { // Clear labels this.edges.forEach(edge => edge.label = undefined); - // convertMaximalToMinimalEdgeRings this._findLabeledEdgeRings().forEach(edge => { + // convertMaximalToMinimalEdgeRings + // XXX: revisar! this._findIntersectionNodes(edge).forEach(node => { this._computeNextCCWEdges(node, edge.label) }); @@ -263,6 +265,12 @@ class Graph { return edgeRingList; } + /** Find all nodes in a Maxima EdgeRing which are self-intersection nodes + * XXX: anda mal! + * + * @param {Node} startEdge + * @return {Node[]} - intersection nodes + */ _findIntersectionNodes(startEdge) { const intersectionNodes = []; let edge = startEdge; @@ -270,7 +278,7 @@ class Graph { // getDegree let degree = 0; edge.from.outerEdges.forEach(e => { - if (startEdge.label == e.label) + if (e.label == startEdge.label) ++degree; }); @@ -376,6 +384,18 @@ class Edge { toLineString() { return lineString([this.from.coordinates, this.to.coordinates]); } + + /** Comparator of two edges. + * Implementation of geos::planargraph::DirectedEdge::compareTo. + * + * @param {Edge} other + * @return -1 if this Edge has a greater angle with the positive x-axis than b, + * 0 if the Edges are colinear, + * 1 otherwise + */ + compareTo(edge) { + return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); + } } class Node { @@ -405,6 +425,7 @@ class Node { */ addOuterEdge(edge) { this.outerEdges.push(edge); + //this.outerEdges.sort((a, b) => a.compareTo(b)); this.outerEdges.sort((a, b) => { const aNode = a.to, bNode = b.to; @@ -444,7 +465,17 @@ class Node { * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing */ class EdgeRing extends Array { - // XXX: isValid? + /** Check if the ring is valid in geomtry terms. + * A ring must have either 0 or 4 or more points. The first and the last must be + * equal (in 2D) + * geos::geom::LinearRing::validateConstruction + * + * @return {Boolean} + */ + isValid() { + // TODO: stub + return true; + } /** Tests whether this ring is a hole. * A ring is a hole if it is oriented counter-clockwise. @@ -515,17 +546,23 @@ class EdgeRing extends Array { // TODO: if (envelopeContains(tryEnvelope, testEnvelope)) { - const testPoint = testEdgeRing.find(edge => { - return shell.find(e => { + /*const testPoint = testEdgeRing.find(edge => { + const testPt = edge.from.coordinates; + return !shell.every(e => { if (e.from.coordinates[0] == edge.from.coordinates[0] && e.from.coordinates[1] == edge.from.coordinates[1]) return true; - return false; }); }); if (testPoint && shell.inside(point(testPoint.from.coordinates))) { if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) minShell = shell; + }*/ + + const testPoint = this.ptNotInList(testEdgeRing, shell); + if (testPoint && shell.inside(point(testPoint))) { + if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) + minShell = shell; } } }); @@ -533,6 +570,23 @@ class EdgeRing extends Array { return minShell; } + ptNotInList(testPts, pts) { + for (let i=0; i < testPts.length; ++i) { + const coordinate = testPts[i].from.coordinates; + if (this.isInList(coordinate, pts)) + return coordinate; + } + } + + isInList(testPt, pts) { + for (let i=0; i < pts.length; ++i) { + if (testPt[0] == pts[i].from.coordinates[0] && testPt[1] == pts[i].from.coordinates[1]) + return false; + } + + return true; + } + /** Checks if the point is inside the edgeRing * * @param {Feature} point From 20372ac4f1dfb60172e31d8ae3ccd25570cdac08 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 04:10:04 -0300 Subject: [PATCH 10/36] Cleaned comments and added test --- index.js | 7 ++-- test.js | 12 +++++-- util.js | 100 ++++++++++++++++++++++++++----------------------------- 3 files changed, 59 insertions(+), 60 deletions(-) diff --git a/index.js b/index.js index 09d3a6d3e9..bff6196a8e 100644 --- a/index.js +++ b/index.js @@ -24,16 +24,15 @@ module.exports = function polygonize(geoJson) { const holes = [], shells = []; - graph.getEdgeRings().forEach(edgeRing => { + graph.getEdgeRings() + .filter(edgeRing => edgeRing.isValid()) + .forEach(edgeRing => { if (edgeRing.isHole()) holes.push(edgeRing); else shells.push(edgeRing); }); - console.log(JSON.stringify(featureCollection(holes.map(h => h.toPolygon())))); - console.log(JSON.stringify(featureCollection(shells.map(s => s.toPolygon())))); - // 4. Assign Holes to Shells holes.forEach(hole => { if (EdgeRing.findEdgeRingContaining(hole, shells)) diff --git a/test.js b/test.js index ef73aea2d6..28e0b943ba 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,6 @@ const test = require('tape'), { Graph, Node, Edge, EdgeRing } = require('./util'), - { featureCollection, lineString } = require('@turf/helpers'), + { featureCollection, lineString, polygon } = require('@turf/helpers'), polygonize = require('./index.js'); test('graph.fromGeoJson', t => { @@ -136,7 +136,7 @@ test('EdgeRing.isHole', t => { t.end(); }); -test('Full', t => { +test('Polygonize', t => { const geoJson = featureCollection([ lineString([[-58.3959417, -34.8036499], [-58.395087, -34.8031464]]), lineString([[-58.3964727, -34.8029764], [-58.3959417, -34.8036499]]), @@ -149,7 +149,13 @@ test('Full', t => { lineString([[-58.3971168, -34.8043422], [-58.396618, -34.8040484]]), lineString([[-58.396618, -34.8040484], [-58.3959417, -34.8036499]]), ]), + expected = featureCollection([ + polygon([[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]), + polygon([[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]), + ]), polygons = polygonize(geoJson); - console.log(JSON.stringify(polygons)); + + t.deepEqual(polygons, expected); + t.end(); }); diff --git a/util.js b/util.js index 9ba5431bfe..7c4da3f56f 100644 --- a/util.js +++ b/util.js @@ -23,8 +23,8 @@ function orientationIndex(p1, p2, q) { return Math.sign(dx1 * dy2 - dx2 * dy1); } -/** Checks if two envelopes are equal - * The function assumes that the arguments are envelopes, i.e.: Rectangular polygons +/** Checks if two envelopes are equal. + * The function assumes that the arguments are envelopes, i.e.: Rectangular polygon * * @param {Feature} env1 - Envelope * @param {Feature} env2 - Envelope @@ -42,9 +42,10 @@ function envelopeIsEqual(env1, env2) { Math.min(null, envY1) == Math.min(null, envY2); } -/** Check if a envelope is contained in other one - * The function assumes that the arguments are envelopes, i.e.: Rectangular polygons - * XXX: Envelopes are rectangular, it could be optimized by using that assumption +/** Check if a envelope is contained in other one. + * The function assumes that the arguments are envelopes, i.e.: Convex polygon + * XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, + * this could be further improved. * * @param {Feature} self - Envelope * @param {Feature} env - Envelope @@ -54,6 +55,16 @@ function envelopeContains(self, env) { return env.geometry.coordinates[0].every(c => inside(point(c), self)); } +/** Checks if two coordinates are equal. + * + * @param {Number[]} + * @param {Number[]} + * @return {Boolean} - True if coordinates are equal + */ +function coordinatesEqual(coord1, coord2) { + return coord1[0] == coord2[0] && coord1[1] == coord2[1]; +} + /** Represents a planar graph of edges and nodes that can be used to compute a * polygonization. * @@ -76,7 +87,8 @@ class Graph { return graph; } - /** Creates or get a Node + /** Creates or get a Node. + * * @param {Number[]} coordinates * @return {Node} */ @@ -89,7 +101,11 @@ class Graph { return node; } - /** Edges are added symetrically + /** 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 */ addEdge(from, to) { const edge = new Edge(from, to), @@ -106,7 +122,7 @@ class Graph { this.nodes = {}; } - /** Remove Dangle Nodes (nodes with grade 1) + /** Removes Dangle Nodes (nodes with grade 1). */ deleteDangles() { Object.values(this.nodes) @@ -247,7 +263,6 @@ class Graph { this._findLabeledEdgeRings().forEach(edge => { // convertMaximalToMinimalEdgeRings - // XXX: revisar! this._findIntersectionNodes(edge).forEach(node => { this._computeNextCCWEdges(node, edge.label) }); @@ -265,8 +280,7 @@ class Graph { return edgeRingList; } - /** Find all nodes in a Maxima EdgeRing which are self-intersection nodes - * XXX: anda mal! + /** Find all nodes in a Maxima EdgeRing which are self-intersection nodes. * * @param {Node} startEdge * @return {Node[]} - intersection nodes @@ -320,7 +334,7 @@ class Graph { delete this.nodes[node.id]; } - /** Remove edge from the graph and deletes the edge + /** Remove edge from the graph and deletes the edge. * * @param {Edge} edge */ @@ -333,7 +347,7 @@ class Graph { /** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge */ class Edge { - /** Creates or get the symetric Edge + /** Creates or get the symetric Edge. * * @return {Edge} */ @@ -357,19 +371,23 @@ class Edge { this.next = undefined; //< The edge to be computed after this.label = undefined; //< Used in order to detect Cut Edges (Bridges) this.symetric = undefined; //< The symetric edge of this - this.ring = undefined; //< TODO: explanation + this.ring = undefined; //< EdgeRing in which the Edge is this.from.addOuterEdge(this); this.to.addInnerEdge(this); } - /** Removes edge from from and to nodes + /** Removes edge from from and to nodes. */ deleteEdge() { this.from.removeOuterEdge(this); this.to.removeInnerEdge(this); } + /** Compares Edge equallity. + * An edge is equal to another, if the from and to nodes are the same. + * @return {Boolean} + */ isEqual(edge) { return this.from.id == edge.from.id && this.to.id == edge.to.id; } @@ -378,7 +396,7 @@ class Edge { return `Edge { ${this.from.id} -> ${this.to.id} }`; } - /** + /** Returns a LineString representation of the Edge * @return {Feature} */ toLineString() { @@ -408,7 +426,7 @@ class Node { this.coordinates = coordinates; //< Number[] this.innerEdges = []; //< Edge[] - // We wil store to (out) edges in an CCW order + // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does this.outerEdges = []; //< Edge[] } @@ -420,12 +438,14 @@ class Node { this.outerEdges = this.outerEdges.filter(e => e.to.id != edge.to.id); } - /** Outer edges are stored CCW order + /** Outer edges are stored CCW order. + * XXX: on each add we are ordering, this could be optimized * @param {Edge} edge */ addOuterEdge(edge) { this.outerEdges.push(edge); //this.outerEdges.sort((a, b) => a.compareTo(b)); + // Using this comparator in order to be deterministic this.outerEdges.sort((a, b) => { const aNode = a.to, bNode = b.to; @@ -499,14 +519,16 @@ class EdgeRing extends Array { return disc > 0; } - /** Creates a MultiPoint representing the EdgeRing (discarts edges directions) + /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). * @return {Feature} */ toMultiPoint() { return multiPoint(this.map(edge => edge.from.coordinates)); } - /** Creates a Polygon representing the EdgeRing + /** Creates a Polygon representing the EdgeRing. + * XXX: the polygon could be cached + * @return {Feature} */ toPolygon() { const coordinates = this.map(edge => edge.from.coordinates); @@ -514,8 +536,9 @@ class EdgeRing extends Array { return polygon([coordinates]); } - /** - * @return {Feature} envelope + /** Calculates the envelope of the EdgeRing. + * XXX: the envelope could be cached + * @return {Feature} - envelope */ getEnvelope() { return envelope(this.toMultiPoint()); @@ -544,22 +567,10 @@ class EdgeRing extends Array { if (envelopeIsEqual(tryEnvelope, testEnvelope)) return; - // TODO: if (envelopeContains(tryEnvelope, testEnvelope)) { - /*const testPoint = testEdgeRing.find(edge => { - const testPt = edge.from.coordinates; - return !shell.every(e => { - if (e.from.coordinates[0] == edge.from.coordinates[0] && e.from.coordinates[1] == edge.from.coordinates[1]) - return true; - }); - }); - - if (testPoint && shell.inside(point(testPoint.from.coordinates))) { - if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) - minShell = shell; - }*/ + const testPoint = testEdgeRing.map(edge => edge.from.coodinates) + .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); - const testPoint = this.ptNotInList(testEdgeRing, shell); if (testPoint && shell.inside(point(testPoint))) { if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) minShell = shell; @@ -570,23 +581,6 @@ class EdgeRing extends Array { return minShell; } - ptNotInList(testPts, pts) { - for (let i=0; i < testPts.length; ++i) { - const coordinate = testPts[i].from.coordinates; - if (this.isInList(coordinate, pts)) - return coordinate; - } - } - - isInList(testPt, pts) { - for (let i=0; i < pts.length; ++i) { - if (testPt[0] == pts[i].from.coordinates[0] && testPt[1] == pts[i].from.coordinates[1]) - return false; - } - - return true; - } - /** Checks if the point is inside the edgeRing * * @param {Feature} point From 3c41abdba9ceb2b7924f19a42360b77faf7340c5 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 15:45:29 -0300 Subject: [PATCH 11/36] Move stuff to packages/turf-polygonize --- README.md => packages/turf-polygonize/README.md | 0 index.js => packages/turf-polygonize/index.js | 0 package.json => packages/turf-polygonize/package.json | 0 test.js => packages/turf-polygonize/test.js | 0 util.js => packages/turf-polygonize/util.js | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename README.md => packages/turf-polygonize/README.md (100%) rename index.js => packages/turf-polygonize/index.js (100%) rename package.json => packages/turf-polygonize/package.json (100%) rename test.js => packages/turf-polygonize/test.js (100%) rename util.js => packages/turf-polygonize/util.js (100%) diff --git a/README.md b/packages/turf-polygonize/README.md similarity index 100% rename from README.md rename to packages/turf-polygonize/README.md diff --git a/index.js b/packages/turf-polygonize/index.js similarity index 100% rename from index.js rename to packages/turf-polygonize/index.js diff --git a/package.json b/packages/turf-polygonize/package.json similarity index 100% rename from package.json rename to packages/turf-polygonize/package.json diff --git a/test.js b/packages/turf-polygonize/test.js similarity index 100% rename from test.js rename to packages/turf-polygonize/test.js diff --git a/util.js b/packages/turf-polygonize/util.js similarity index 100% rename from util.js rename to packages/turf-polygonize/util.js From 2e46b12db3f57e95c96fa05d980e0aac18d55393 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 17:06:08 -0300 Subject: [PATCH 12/36] Refactor: Separate classes and tests in files --- packages/turf-polygonize/index.js | 3 +- packages/turf-polygonize/package.json | 2 +- packages/turf-polygonize/src/Edge.js | 76 +++ packages/turf-polygonize/src/EdgeRing.js | 118 ++++ packages/turf-polygonize/src/EdgeRing.test.js | 22 + packages/turf-polygonize/src/Graph.js | 284 +++++++++ packages/turf-polygonize/src/Graph.test.js | 86 +++ packages/turf-polygonize/src/Node.js | 66 ++ packages/turf-polygonize/src/Node.test.js | 25 + packages/turf-polygonize/src/util.js | 72 +++ packages/turf-polygonize/test.js | 134 ---- packages/turf-polygonize/util.js | 599 ------------------ 12 files changed, 752 insertions(+), 735 deletions(-) create mode 100644 packages/turf-polygonize/src/Edge.js create mode 100644 packages/turf-polygonize/src/EdgeRing.js create mode 100644 packages/turf-polygonize/src/EdgeRing.test.js create mode 100644 packages/turf-polygonize/src/Graph.js create mode 100644 packages/turf-polygonize/src/Graph.test.js create mode 100644 packages/turf-polygonize/src/Node.js create mode 100644 packages/turf-polygonize/src/Node.test.js create mode 100644 packages/turf-polygonize/src/util.js delete mode 100644 packages/turf-polygonize/util.js diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index bff6196a8e..1a5dc18a55 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,4 +1,5 @@ -const { Graph, EdgeRing } = require('./util'), +const Graph = require('./src/Graph'), + EdgeRing = require('./src/EdgeRing'), { featureCollection } = require('@turf/helpers'); /** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index ff6cfb0134..2bb50e437d 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -4,7 +4,7 @@ "description": "turf polygonize module", "main": "index.js", "scripts": { - "test": "node test.js" + "test": "tape src/*.test.js test.js" }, "repository": { "type": "git", diff --git a/packages/turf-polygonize/src/Edge.js b/packages/turf-polygonize/src/Edge.js new file mode 100644 index 0000000000..e0a7f3a995 --- /dev/null +++ b/packages/turf-polygonize/src/Edge.js @@ -0,0 +1,76 @@ +const { lineString } = require('@turf/helpers'), + { orientationIndex } = require('./util'); + +/** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge + */ +class Edge { + /** Creates or get the symetric Edge. + * + * @return {Edge} + */ + getSymetric() { + if (! this.symetric) { + this.symetric = new Edge(this.to, this.from); + this.symetric.symetric = this; + } + + return this.symetric; + } + + /** + * @param {Node} from - start node of the Edge + * @param {Node} to - end node of the edge + */ + constructor(from, to) { + this.from = from; //< start + this.to = to; //< End + + this.next = undefined; //< The edge to be computed after + this.label = undefined; //< Used in order to detect Cut Edges (Bridges) + this.symetric = undefined; //< The symetric edge of this + this.ring = undefined; //< EdgeRing in which the Edge is + + this.from.addOuterEdge(this); + this.to.addInnerEdge(this); + } + + /** Removes edge from from and to nodes. + */ + deleteEdge() { + this.from.removeOuterEdge(this); + this.to.removeInnerEdge(this); + } + + /** Compares Edge equallity. + * An edge is equal to another, if the from and to nodes are the same. + * @return {Boolean} + */ + isEqual(edge) { + return this.from.id == edge.from.id && this.to.id == edge.to.id; + } + + toString() { + return `Edge { ${this.from.id} -> ${this.to.id} }`; + } + + /** Returns a LineString representation of the Edge + * @return {Feature} + */ + toLineString() { + return lineString([this.from.coordinates, this.to.coordinates]); + } + + /** Comparator of two edges. + * Implementation of geos::planargraph::DirectedEdge::compareTo. + * + * @param {Edge} other + * @return -1 if this Edge has a greater angle with the positive x-axis than b, + * 0 if the Edges are colinear, + * 1 otherwise + */ + compareTo(edge) { + return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); + } +} + +module.exports = Edge; diff --git a/packages/turf-polygonize/src/EdgeRing.js b/packages/turf-polygonize/src/EdgeRing.js new file mode 100644 index 0000000000..ebe852969a --- /dev/null +++ b/packages/turf-polygonize/src/EdgeRing.js @@ -0,0 +1,118 @@ +const { orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual } = require('./util'), + { multiPoint, polygon } = require('@turf/helpers'), + envelope = require('@turf/envelope'), + inside = require('@turf/inside'); + +/** Ring of edges which form a polygon. + * The ring may be either an outer shell or a hole. + * + * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing + */ +class EdgeRing extends Array { + /** Check if the ring is valid in geomtry terms. + * A ring must have either 0 or 4 or more points. The first and the last must be + * equal (in 2D) + * geos::geom::LinearRing::validateConstruction + * + * @return {Boolean} + */ + isValid() { + // TODO: stub + return true; + } + + /** Tests whether this ring is a hole. + * A ring is a hole if it is oriented counter-clockwise. + * Similar implementation of geos::algorithm::CGAlgorithms::isCCW + * @return {Boolean} - true: if it is a hole + */ + isHole() { + // XXX: Assuming Ring is valid + // Find highest point + const hiIndex = this.reduce((high, edge, i) => { + if (edge.from.coordinates[1] > this[high].from.coordinates[1]) + high = i; + return high; + }, 0), + iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, + iNext = (hiIndex + 1) % this.length, + disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); + + if (disc == 0) + return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; + return disc > 0; + } + + /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). + * @return {Feature} + */ + toMultiPoint() { + return multiPoint(this.map(edge => edge.from.coordinates)); + } + + /** Creates a Polygon representing the EdgeRing. + * XXX: the polygon could be cached + * @return {Feature} + */ + toPolygon() { + const coordinates = this.map(edge => edge.from.coordinates); + coordinates.push(this[0].from.coordinates); + return polygon([coordinates]); + } + + /** Calculates the envelope of the EdgeRing. + * XXX: the envelope could be cached + * @return {Feature} - envelope + */ + getEnvelope() { + return envelope(this.toMultiPoint()); + } + + /** + * geos::operation::polygonize::EdgeRing::findEdgeRingContaining + * + * @param {EdgeRing} edgeRing + * @param {EdgeRing[]} shellList + * + * @return {EdgeRing} + */ + static findEdgeRingContaining(testEdgeRing, shellList) { + const testEnvelope = testEdgeRing.getEnvelope(); + + let minEnvelope, + minShell; + shellList.forEach(shell => { + const tryEnvelope = shell.getEnvelope(); + + if (minShell) + minEnvelope = minShell.getEnvelope(); + + // the hole envelope cannot equal the shell envelope + if (envelopeIsEqual(tryEnvelope, testEnvelope)) + return; + + if (envelopeContains(tryEnvelope, testEnvelope)) { + const testPoint = testEdgeRing.map(edge => edge.from.coodinates) + .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); + + if (testPoint && shell.inside(point(testPoint))) { + if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) + minShell = shell; + } + } + }); + + return minShell; + } + + /** Checks if the point is inside the edgeRing + * + * @param {Feature} point + * @return {Boolean} + */ + inside(point) { + return inside(point, this.toPolygon()); + } +} + +module.exports = EdgeRing; diff --git a/packages/turf-polygonize/src/EdgeRing.test.js b/packages/turf-polygonize/src/EdgeRing.test.js new file mode 100644 index 0000000000..c912ffdc24 --- /dev/null +++ b/packages/turf-polygonize/src/EdgeRing.test.js @@ -0,0 +1,22 @@ +const test = require('tape'), + EdgeRing = require('./EdgeRing'), + Edge = require('./Edge'), + Node = require('./Node'); + +test('EdgeRing.isHole', t => { + let edgeRing = new EdgeRing(); + edgeRing.push(new Edge(new Node([0, 0]), new Node([1, 0]))); + edgeRing.push(new Edge(edgeRing[0].to, new Node([0, 1]))); + edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); + + t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); + + edgeRing = new EdgeRing(); + edgeRing.push(new Edge(new Node([0, 0]), new Node([0, 1]))); + edgeRing.push(new Edge(edgeRing[0].to, new Node([1, 0]))); + edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); + + t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); + + t.end(); +}); diff --git a/packages/turf-polygonize/src/Graph.js b/packages/turf-polygonize/src/Graph.js new file mode 100644 index 0000000000..cae44ab4c8 --- /dev/null +++ b/packages/turf-polygonize/src/Graph.js @@ -0,0 +1,284 @@ +const Node = require('./Node'), + Edge = require('./Edge'), + EdgeRing = require('./EdgeRing'); + +/** 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) + */ +class Graph { + static fromGeoJson(geoJson) { + const graph = new Graph(); + geoJson.features.forEach(feature => { + const start = graph.getNode(feature.geometry.coordinates[0]), + end = graph.getNode(feature.geometry.coordinates[1]); + + graph.addEdge(start, end); + }); + + return graph; + } + + /** Creates or get a Node. + * + * @param {Number[]} coordinates + * @return {Node} + */ + getNode(coordinates) { + const id = Node.buildId(coordinates); + let node = this.nodes[id]; + if (!node) + node = this.nodes[id] = new Node(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 + */ + addEdge(from, to) { + const edge = new Edge(from, to), + symetricEdge = edge.getSymetric(); + + this.edges.push(edge); + this.edges.push(symetricEdge); + } + + constructor() { + this.edges = []; //< {Edge[]} dirEdges + + // The key is the `id` of the Node (ie: coordinates.join(',')) + this.nodes = {}; + } + + /** Removes Dangle Nodes (nodes with grade 1). + */ + deleteDangles() { + Object.values(this.nodes) + .forEach(node => 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 + */ + _removeIfDangle(node) { + // As edges are directed and symetrical, we count only innerEdges + if (node.innerEdges.length <= 1) { + const outerNodes = node.outerEdges.map(e => e.to); + this.removeNode(node); + outerNodes.forEach(n => 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. + */ + deleteCutEdges() { + this._computeNextCWEdges(); + this._findLabeledEdgeRings(); + + // Cut-edges (bridges) are edges where both edges have the same label + this.edges.forEach(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 + */ + _computeNextCWEdges(node) { + if (typeof(node) == "undefined") + return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + + node.outerEdges.forEach((edge, i) => { + node.outerEdges[(i === 0 ? node.outerEdges.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 PolygonizeGraph::computeNextCCWEdges, could be written + * in a more javascript way + * + * @param {Node} node + * @param {Number} label + */ + _computeNextCCWEdges(node, label) { + const edges = node.outerEdges; + let firstOutDE, + prevInDE; + + for (let i = edges.length - 1; i >= 0; --i) { + let de = edges[i], + sym = de.symetric, + outDE, + inDE; + + 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. + * + * @return {Edge[]} edges that start rings + */ + _findLabeledEdgeRings() { + const edgeRingStarts = []; + let label = 0; + this.edges.forEach(edge => { + if (edge.label >= 0) + return; + + edgeRingStarts.push(edge); + + let 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. + * + * @return {EdgeRing[]} + */ + getEdgeRings() { + this._computeNextCWEdges(); + + // Clear labels + this.edges.forEach(edge => edge.label = undefined); + + this._findLabeledEdgeRings().forEach(edge => { + // convertMaximalToMinimalEdgeRings + this._findIntersectionNodes(edge).forEach(node => { + this._computeNextCCWEdges(node, edge.label) + }); + }); + + const edgeRingList = []; + + // find all edgerings + this.edges.forEach(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 + * @return {Node[]} - intersection nodes + */ + _findIntersectionNodes(startEdge) { + const intersectionNodes = []; + let edge = startEdge; + do { + // getDegree + let degree = 0; + edge.from.outerEdges.forEach(e => { + if (e.label == startEdge.label) + ++degree; + }); + + if (degree > 1) + intersectionNodes.push(edge.from); + + edge = edge.next; + } 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 + * @return {EdgeRing} + */ + _findEdgeRing(startEdge) { + let edge = startEdge; + const edgeRing = new EdgeRing(); + + 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 + */ + removeNode(node) { + node.outerEdges.forEach(edge => this.removeEdge(edge)); + node.innerEdges.forEach(edge => this.removeEdge(edge)); + delete this.nodes[node.id]; + } + + /** Remove edge from the graph and deletes the edge. + * + * @param {Edge} edge + */ + removeEdge(edge) { + this.edges = this.edges.filter(e => !e.isEqual(edge)); + edge.deleteEdge(); + } +} + +module.exports = Graph; diff --git a/packages/turf-polygonize/src/Graph.test.js b/packages/turf-polygonize/src/Graph.test.js new file mode 100644 index 0000000000..c41907ad82 --- /dev/null +++ b/packages/turf-polygonize/src/Graph.test.js @@ -0,0 +1,86 @@ +const test = require('tape'), + Graph = require('./Graph'), + Node = require('./Node'), + { featureCollection, lineString } = require('@turf/helpers'); + +test('graph.fromGeoJson', t => { + const geoJson = featureCollection([ + lineString([[0, 1], [0, 0]]), + lineString([[1, 1], [0, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); + + // Edges are symetric + t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); + + t.end(); +}); + +test('deleteDangles', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [0, 2]]), + lineString([[0, 1], [1, 1]]), + lineString([[1, 1], [1, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteDangles(); + + t.equal(Object.keys(graph.nodes).length, 4); + + t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); + + t.end(); +}); + +test('deleteCutEdges', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + + t.equal(Object.keys(graph.nodes).length, 6); + t.equal(graph.edges.length, 12); + + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); + + t.end(); +}); + +test('getEdgeRings', t => { + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + const edgeRings = graph.getEdgeRings(); + + t.equal(edgeRings.length, 4); + + edgeRings.forEach(edgeRing => { + t.equal(edgeRing.length, 3); + }); + + t.end(); +}); diff --git a/packages/turf-polygonize/src/Node.js b/packages/turf-polygonize/src/Node.js new file mode 100644 index 0000000000..f42ec1fe57 --- /dev/null +++ b/packages/turf-polygonize/src/Node.js @@ -0,0 +1,66 @@ +const { orientationIndex } = require('./util'); + +class Node { + static buildId(coordinates) { + return coordinates.join(','); + } + + constructor(coordinates) { + this.id = Node.buildId(coordinates); + this.coordinates = coordinates; //< Number[] + this.innerEdges = []; //< Edge[] + + // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does + this.outerEdges = []; //< Edge[] + } + + removeInnerEdge(edge) { + this.innerEdges = this.innerEdges.filter(e => e.from.id != edge.from.id); + } + + removeOuterEdge(edge) { + this.outerEdges = this.outerEdges.filter(e => e.to.id != edge.to.id); + } + + /** Outer edges are stored CCW order. + * XXX: on each add we are ordering, this could be optimized + * @param {Edge} edge + */ + addOuterEdge(edge) { + this.outerEdges.push(edge); + //this.outerEdges.sort((a, b) => a.compareTo(b)); + // Using this comparator in order to be deterministic + this.outerEdges.sort((a, b) => { + const aNode = a.to, + bNode = b.to; + + if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) + return 1; + if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) + return -1; + + if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { + if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) + return aNode.coordinates[1] - bNode.coordinates[1]; + return bNode.coordinates[1] - aNode.coordinates[1]; + } + + const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); + if (det < 0) + return 1; + if (det > 0) + return -1; + + const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), + d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); + + return d1 - d2; + }); + } + + addInnerEdge(edge) { + this.innerEdges.push(edge); + } +} + +module.exports = Node; diff --git a/packages/turf-polygonize/src/Node.test.js b/packages/turf-polygonize/src/Node.test.js new file mode 100644 index 0000000000..69e1c06b38 --- /dev/null +++ b/packages/turf-polygonize/src/Node.test.js @@ -0,0 +1,25 @@ +const test = require('tape'), + Node = require('./Node'), + Edge = require('./Edge'); + +test('Node.outerEdges CCW order', t => { + const center = new Node([0, 0]), + addNode = c => new Edge(center, new Node(c)); + + addNode([0, 1]); + addNode([1, 1]); + addNode([1, 0]); + addNode([1, -1]); + addNode([0, -1]); + addNode([-1, -1]); + addNode([-1, 0]); + addNode([-1, 1]); + + t.deepEqual( + center.outerEdges.map(e => e.to.coordinates), + [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], + 'Outernodes have to be in CCW order' + ); + + t.end(); +}); diff --git a/packages/turf-polygonize/src/util.js b/packages/turf-polygonize/src/util.js new file mode 100644 index 0000000000..359f035dbf --- /dev/null +++ b/packages/turf-polygonize/src/util.js @@ -0,0 +1,72 @@ +const inside = require('@turf/inside'), + { point } = require('@turf/helpers'); + +/** Returns the direction of the point q relative to the vector p1 -> p2. + * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() + * (same as geos::algorithm::CGAlgorithm::computeOrientation()) + * + * @param {Number[]} p1 - the origin point of the vector + * @param {Number[]} p2 - the final point of the vector + * @param {Number[]} q - the point to compute the direction to + * + * @return 1 if q is ccw (left) from p1->p2, + * -1 if q is cw (right) from p1->p2, + * 0 if q is colinear with p1->p2 + */ +function orientationIndex(p1, p2, q) { + const dx1 = p2[0] - p1[0], + dy1 = p2[1] - p1[1], + dx2 = q[0] - p2[0], + dy2 = q[1] - p2[1]; + + return Math.sign(dx1 * dy2 - dx2 * dy1); +} + +/** Checks if two envelopes are equal. + * The function assumes that the arguments are envelopes, i.e.: Rectangular polygon + * + * @param {Feature} env1 - Envelope + * @param {Feature} env2 - Envelope + * @return {Boolean} - True if the envelopes are equal + */ +function envelopeIsEqual(env1, env2) { + const envX1 = env1.geometry.coordinates.map(c => c[0]), + envY1 = env1.geometry.coordinates.map(c => c[1]), + envX2 = env2.geometry.coordinates.map(c => c[0]), + envY2 = env2.geometry.coordinates.map(c => c[1]); + + return Math.max(null, envX1) == Math.max(null, envX2) && + Math.max(null, envY1) == Math.max(null, envY2) && + Math.min(null, envX1) == Math.min(null, envX2) && + Math.min(null, envY1) == Math.min(null, envY2); +} + +/** Check if a envelope is contained in other one. + * The function assumes that the arguments are envelopes, i.e.: Convex polygon + * XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, + * this could be further improved. + * + * @param {Feature} self - Envelope + * @param {Feature} env - Envelope + * @return {Boolean} - True if env is contained in self + */ +function envelopeContains(self, env) { + return env.geometry.coordinates[0].every(c => inside(point(c), self)); +} + +/** Checks if two coordinates are equal. + * + * @param {Number[]} + * @param {Number[]} + * @return {Boolean} - True if coordinates are equal + */ +function coordinatesEqual(coord1, coord2) { + return coord1[0] == coord2[0] && coord1[1] == coord2[1]; +} + +module.exports = { + orientationIndex, + envelopeIsEqual, + envelopeContains, + coordinatesEqual, +}; diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 28e0b943ba..824555ffa5 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -1,141 +1,7 @@ const test = require('tape'), - { Graph, Node, Edge, EdgeRing } = require('./util'), { featureCollection, lineString, polygon } = require('@turf/helpers'), polygonize = require('./index.js'); -test('graph.fromGeoJson', t => { - const geoJson = featureCollection([ - lineString([[0, 1], [0, 0]]), - lineString([[1, 1], [0, 0]]), - lineString([[1, 0], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); - - // Edges are symetric - t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); - - t.end(); -}); - -test('node.outerEdges CCW order', t => { - const geoJson = featureCollection([ - lineString([[0, 1], [0, 0]]), - lineString([[1, 1], [0, 0]]), - lineString([[1, 0], [0, 0]]), - lineString([[1, -1], [0, 0]]), - lineString([[0, -1], [0, 0]]), - lineString([[-1, -1], [0, 0]]), - lineString([[-1, 0], [0, 0]]), - lineString([[-1, 1], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson), - node = graph.getNode([0, 0]); - - t.deepEqual( - node.outerEdges.map(e => e.to.coordinates), - [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], - 'Outernodes have to ve in CCW order' - ); - - t.end(); -}); - -test('deleteDangles', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [0, 2]]), - lineString([[0, 1], [1, 1]]), - lineString([[1, 1], [1, 0]]), - lineString([[1, 0], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteDangles(); - - t.equal(Object.keys(graph.nodes).length, 4); - - t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); - - t.end(); -}); - -test('deleteCutEdges', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - - t.equal(Object.keys(graph.nodes).length, 6); - t.equal(graph.edges.length, 12); - - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); - - t.end(); -}); - -test('getEdgeRings', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - const edgeRings = graph.getEdgeRings(); - - t.equal(edgeRings.length, 4); - - edgeRings.forEach(edgeRing => { - t.equal(edgeRing.length, 3); - }); - - t.end(); -}); - -test('EdgeRing.isHole', t => { - function getNextEdge(edge) { - return edge.to.outerEdges.find(e => !e.isEqual(edge.symetric)); - } - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 0]]), - lineString([[1, 0], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - let edgeRing = new EdgeRing(); - edgeRing.push(graph.edges.find(e => e.from.id == Node.buildId([0,0]) && e.to.id == Node.buildId([1,0]))); - edgeRing.push(getNextEdge(edgeRing[0])); - edgeRing.push(getNextEdge(edgeRing[1])); - - t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); - - edgeRing = new EdgeRing(); - edgeRing.push(graph.edges.find(e => e.from.id == Node.buildId([0,0]) && e.to.id == Node.buildId([0,1]))); - edgeRing.push(getNextEdge(edgeRing[0])); - edgeRing.push(getNextEdge(edgeRing[1])); - - t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); - - t.end(); -}); - test('Polygonize', t => { const geoJson = featureCollection([ lineString([[-58.3959417, -34.8036499], [-58.395087, -34.8031464]]), diff --git a/packages/turf-polygonize/util.js b/packages/turf-polygonize/util.js deleted file mode 100644 index 7c4da3f56f..0000000000 --- a/packages/turf-polygonize/util.js +++ /dev/null @@ -1,599 +0,0 @@ -const { multiPoint, lineString, point, polygon } = require('@turf/helpers'), - envelope = require('@turf/envelope'), - inside = require('@turf/inside'); - -/** Returns the direction of the point q relative to the vector p1 -> p2. - * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() - * (same as geos::algorithm::CGAlgorithm::computeOrientation()) - * - * @param {Number[]} p1 - the origin point of the vector - * @param {Number[]} p2 - the final point of the vector - * @param {Number[]} q - the point to compute the direction to - * - * @return 1 if q is ccw (left) from p1->p2, - * -1 if q is cw (right) from p1->p2, - * 0 if q is colinear with p1->p2 - */ -function orientationIndex(p1, p2, q) { - const dx1 = p2[0] - p1[0], - dy1 = p2[1] - p1[1], - dx2 = q[0] - p2[0], - dy2 = q[1] - p2[1]; - - return Math.sign(dx1 * dy2 - dx2 * dy1); -} - -/** Checks if two envelopes are equal. - * The function assumes that the arguments are envelopes, i.e.: Rectangular polygon - * - * @param {Feature} env1 - Envelope - * @param {Feature} env2 - Envelope - * @return {Boolean} - True if the envelopes are equal - */ -function envelopeIsEqual(env1, env2) { - const envX1 = env1.geometry.coordinates.map(c => c[0]), - envY1 = env1.geometry.coordinates.map(c => c[1]), - envX2 = env2.geometry.coordinates.map(c => c[0]), - envY2 = env2.geometry.coordinates.map(c => c[1]); - - return Math.max(null, envX1) == Math.max(null, envX2) && - Math.max(null, envY1) == Math.max(null, envY2) && - Math.min(null, envX1) == Math.min(null, envX2) && - Math.min(null, envY1) == Math.min(null, envY2); -} - -/** Check if a envelope is contained in other one. - * The function assumes that the arguments are envelopes, i.e.: Convex polygon - * XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, - * this could be further improved. - * - * @param {Feature} self - Envelope - * @param {Feature} env - Envelope - * @return {Boolean} - True if env is contained in self - */ -function envelopeContains(self, env) { - return env.geometry.coordinates[0].every(c => inside(point(c), self)); -} - -/** Checks if two coordinates are equal. - * - * @param {Number[]} - * @param {Number[]} - * @return {Boolean} - True if coordinates are equal - */ -function coordinatesEqual(coord1, coord2) { - return coord1[0] == coord2[0] && coord1[1] == coord2[1]; -} - -/** 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) - */ -class Graph { - static fromGeoJson(geoJson) { - const graph = new Graph(); - geoJson.features.forEach(feature => { - const start = graph.getNode(feature.geometry.coordinates[0]), - end = graph.getNode(feature.geometry.coordinates[1]); - - graph.addEdge(start, end); - }); - - return graph; - } - - /** Creates or get a Node. - * - * @param {Number[]} coordinates - * @return {Node} - */ - getNode(coordinates) { - const id = Node.buildId(coordinates); - let node = this.nodes[id]; - if (!node) - node = this.nodes[id] = new Node(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 - */ - addEdge(from, to) { - const edge = new Edge(from, to), - symetricEdge = edge.getSymetric(); - - this.edges.push(edge); - this.edges.push(symetricEdge); - } - - constructor() { - this.edges = []; //< {Edge[]} dirEdges - - // The key is the `id` of the Node (ie: coordinates.join(',')) - this.nodes = {}; - } - - /** Removes Dangle Nodes (nodes with grade 1). - */ - deleteDangles() { - Object.values(this.nodes) - .forEach(node => 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 - */ - _removeIfDangle(node) { - // As edges are directed and symetrical, we count only innerEdges - if (node.innerEdges.length <= 1) { - const outerNodes = node.outerEdges.map(e => e.to); - this.removeNode(node); - outerNodes.forEach(n => 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. - */ - deleteCutEdges() { - this._computeNextCWEdges(); - this._findLabeledEdgeRings(); - - // Cut-edges (bridges) are edges where both edges have the same label - this.edges.forEach(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 - */ - _computeNextCWEdges(node) { - if (typeof(node) == "undefined") - return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); - - node.outerEdges.forEach((edge, i) => { - node.outerEdges[(i === 0 ? node.outerEdges.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 PolygonizeGraph::computeNextCCWEdges, could be written - * in a more javascript way - * - * @param {Node} node - * @param {Number} label - */ - _computeNextCCWEdges(node, label) { - const edges = node.outerEdges; - let firstOutDE, - prevInDE; - - for (let i = edges.length - 1; i >= 0; --i) { - let de = edges[i], - sym = de.symetric, - outDE, - inDE; - - 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. - * - * @return {Edge[]} edges that start rings - */ - _findLabeledEdgeRings() { - const edgeRingStarts = []; - let label = 0; - this.edges.forEach(edge => { - if (edge.label >= 0) - return; - - edgeRingStarts.push(edge); - - let 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. - * - * @return {EdgeRing[]} - */ - getEdgeRings() { - this._computeNextCWEdges(); - - // Clear labels - this.edges.forEach(edge => edge.label = undefined); - - this._findLabeledEdgeRings().forEach(edge => { - // convertMaximalToMinimalEdgeRings - this._findIntersectionNodes(edge).forEach(node => { - this._computeNextCCWEdges(node, edge.label) - }); - }); - - const edgeRingList = []; - - // find all edgerings - this.edges.forEach(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 - * @return {Node[]} - intersection nodes - */ - _findIntersectionNodes(startEdge) { - const intersectionNodes = []; - let edge = startEdge; - do { - // getDegree - let degree = 0; - edge.from.outerEdges.forEach(e => { - if (e.label == startEdge.label) - ++degree; - }); - - if (degree > 1) - intersectionNodes.push(edge.from); - - edge = edge.next; - } 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 - * @return {EdgeRing} - */ - _findEdgeRing(startEdge) { - let edge = startEdge; - const edgeRing = new EdgeRing(); - - 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 - */ - removeNode(node) { - node.outerEdges.forEach(edge => this.removeEdge(edge)); - node.innerEdges.forEach(edge => this.removeEdge(edge)); - delete this.nodes[node.id]; - } - - /** Remove edge from the graph and deletes the edge. - * - * @param {Edge} edge - */ - removeEdge(edge) { - this.edges = this.edges.filter(e => !e.isEqual(edge)); - edge.deleteEdge(); - } -} - -/** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge - */ -class Edge { - /** Creates or get the symetric Edge. - * - * @return {Edge} - */ - getSymetric() { - if (! this.symetric) { - this.symetric = new Edge(this.to, this.from); - this.symetric.symetric = this; - } - - return this.symetric; - } - - /** - * @param {Node} from - start node of the Edge - * @param {Node} to - end node of the edge - */ - constructor(from, to) { - this.from = from; //< start - this.to = to; //< End - - this.next = undefined; //< The edge to be computed after - this.label = undefined; //< Used in order to detect Cut Edges (Bridges) - this.symetric = undefined; //< The symetric edge of this - this.ring = undefined; //< EdgeRing in which the Edge is - - this.from.addOuterEdge(this); - this.to.addInnerEdge(this); - } - - /** Removes edge from from and to nodes. - */ - deleteEdge() { - this.from.removeOuterEdge(this); - this.to.removeInnerEdge(this); - } - - /** Compares Edge equallity. - * An edge is equal to another, if the from and to nodes are the same. - * @return {Boolean} - */ - isEqual(edge) { - return this.from.id == edge.from.id && this.to.id == edge.to.id; - } - - toString() { - return `Edge { ${this.from.id} -> ${this.to.id} }`; - } - - /** Returns a LineString representation of the Edge - * @return {Feature} - */ - toLineString() { - return lineString([this.from.coordinates, this.to.coordinates]); - } - - /** Comparator of two edges. - * Implementation of geos::planargraph::DirectedEdge::compareTo. - * - * @param {Edge} other - * @return -1 if this Edge has a greater angle with the positive x-axis than b, - * 0 if the Edges are colinear, - * 1 otherwise - */ - compareTo(edge) { - return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); - } -} - -class Node { - static buildId(coordinates) { - return coordinates.join(','); - } - - constructor(coordinates) { - this.id = Node.buildId(coordinates); - this.coordinates = coordinates; //< Number[] - this.innerEdges = []; //< Edge[] - - // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does - this.outerEdges = []; //< Edge[] - } - - removeInnerEdge(edge) { - this.innerEdges = this.innerEdges.filter(e => e.from.id != edge.from.id); - } - - removeOuterEdge(edge) { - this.outerEdges = this.outerEdges.filter(e => e.to.id != edge.to.id); - } - - /** Outer edges are stored CCW order. - * XXX: on each add we are ordering, this could be optimized - * @param {Edge} edge - */ - addOuterEdge(edge) { - this.outerEdges.push(edge); - //this.outerEdges.sort((a, b) => a.compareTo(b)); - // Using this comparator in order to be deterministic - this.outerEdges.sort((a, b) => { - const aNode = a.to, - bNode = b.to; - - if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) - return 1; - if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) - return -1; - - if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { - if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) - return aNode.coordinates[1] - bNode.coordinates[1]; - return bNode.coordinates[1] - aNode.coordinates[1]; - } - - const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); - if (det < 0) - return 1; - if (det > 0) - return -1; - - const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), - d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); - - return d1 - d2; - }); - } - - addInnerEdge(edge) { - this.innerEdges.push(edge); - } -} - -/** Ring of edges which form a polygon. - * The ring may be either an outer shell or a hole. - * - * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing - */ -class EdgeRing extends Array { - /** Check if the ring is valid in geomtry terms. - * A ring must have either 0 or 4 or more points. The first and the last must be - * equal (in 2D) - * geos::geom::LinearRing::validateConstruction - * - * @return {Boolean} - */ - isValid() { - // TODO: stub - return true; - } - - /** Tests whether this ring is a hole. - * A ring is a hole if it is oriented counter-clockwise. - * Similar implementation of geos::algorithm::CGAlgorithms::isCCW - * @return {Boolean} - true: if it is a hole - */ - isHole() { - // XXX: Assuming Ring is valid - // Find highest point - const hiIndex = this.reduce((high, edge, i) => { - if (edge.from.coordinates[1] > this[high].from.coordinates[1]) - high = i; - return high; - }, 0), - iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, - iNext = (hiIndex + 1) % this.length, - disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); - - if (disc == 0) - return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; - return disc > 0; - } - - /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). - * @return {Feature} - */ - toMultiPoint() { - return multiPoint(this.map(edge => edge.from.coordinates)); - } - - /** Creates a Polygon representing the EdgeRing. - * XXX: the polygon could be cached - * @return {Feature} - */ - toPolygon() { - const coordinates = this.map(edge => edge.from.coordinates); - coordinates.push(this[0].from.coordinates); - return polygon([coordinates]); - } - - /** Calculates the envelope of the EdgeRing. - * XXX: the envelope could be cached - * @return {Feature} - envelope - */ - getEnvelope() { - return envelope(this.toMultiPoint()); - } - - /** - * geos::operation::polygonize::EdgeRing::findEdgeRingContaining - * - * @param {EdgeRing} edgeRing - * @param {EdgeRing[]} shellList - * - * @return {EdgeRing} - */ - static findEdgeRingContaining(testEdgeRing, shellList) { - const testEnvelope = testEdgeRing.getEnvelope(); - - let minEnvelope, - minShell; - shellList.forEach(shell => { - const tryEnvelope = shell.getEnvelope(); - - if (minShell) - minEnvelope = minShell.getEnvelope(); - - // the hole envelope cannot equal the shell envelope - if (envelopeIsEqual(tryEnvelope, testEnvelope)) - return; - - if (envelopeContains(tryEnvelope, testEnvelope)) { - const testPoint = testEdgeRing.map(edge => edge.from.coodinates) - .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); - - if (testPoint && shell.inside(point(testPoint))) { - if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) - minShell = shell; - } - } - }); - - return minShell; - } - - /** Checks if the point is inside the edgeRing - * - * @param {Feature} point - * @return {Boolean} - */ - inside(point) { - return inside(point, this.toPolygon()); - } -} - -module.exports = { - Graph, - Node, - Edge, - EdgeRing -}; From 499eceef6c4b741309610d5b1168520608f1d1f7 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 22:38:43 -0300 Subject: [PATCH 13/36] Added Test. Updated documentation. Generated Readme --- packages/turf-polygonize/README.md | 365 +- packages/turf-polygonize/index.js | 11 +- packages/turf-polygonize/src/EdgeRing.js | 2 +- packages/turf-polygonize/src/Graph.js | 10 +- packages/turf-polygonize/src/Graph.test.js | 8 +- packages/turf-polygonize/test.js | 68 +- .../turf-polygonize/test/in/complex.geojson | 4109 +++++++++++++++++ .../turf-polygonize/test/in/cutedge.geojson | 209 + .../turf-polygonize/test/in/dangle.geojson | 158 + .../test/in/two-polygons.geojson | 195 + .../turf-polygonize/test/out/complex.geojson | 1 + .../turf-polygonize/test/out/cutedge.geojson | 1 + .../turf-polygonize/test/out/dangle.geojson | 1 + .../test/out/two-polygons.geojson | 1 + 14 files changed, 5104 insertions(+), 35 deletions(-) create mode 100644 packages/turf-polygonize/test/in/complex.geojson create mode 100644 packages/turf-polygonize/test/in/cutedge.geojson create mode 100644 packages/turf-polygonize/test/in/dangle.geojson create mode 100644 packages/turf-polygonize/test/in/two-polygons.geojson create mode 100644 packages/turf-polygonize/test/out/complex.geojson create mode 100644 packages/turf-polygonize/test/out/cutedge.geojson create mode 100644 packages/turf-polygonize/test/out/dangle.geojson create mode 100644 packages/turf-polygonize/test/out/two-polygons.geojson diff --git a/packages/turf-polygonize/README.md b/packages/turf-polygonize/README.md index 7cd896a79a..911766ff1b 100644 --- a/packages/turf-polygonize/README.md +++ b/packages/turf-polygonize/README.md @@ -1,3 +1,364 @@ -# @turf/polyonize +# @turf/polygonize -# polygonize +# index + +Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). + +Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly +noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate +points. + +The implementation correctly handles: + +- Dangles: edges which have one or both ends which are not incident on another edge endpoint. +- Cut Edges (bridges): edges that are connected at both ends but which do not form part + of a polygon. + +**Parameters** + +- `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** Lines in order to polygonize + +Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** + +# Graph + +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) + +## getNode + +Creates or get a Node. + +**Parameters** + +- `coordinates` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** + +Returns **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** + +## addEdge + +Adds an Edge and its symetricall. +Edges are added symetrically, i.e.: we also add its symetric + +**Parameters** + +- `from` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node which starts the Edge +- `to` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node which ends the Edge + +## deleteDangles + +Removes Dangle Nodes (nodes with grade 1). + +## \_removeIfDangle + +Check if node is dangle, if so, remove it. +It calls itself recursively, removing a dangling node might cause another dangling node + +**Parameters** + +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** + +## deleteCutEdges + +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. + +## \_computeNextCWEdges + +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. + +**Parameters** + +- `node` **\[[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)]** If no node is passed, the function calls itself for every node in the Graph + +## \_computeNextCCWEdges + +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. + +**Parameters** + +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** +- `label` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** + +## \_findLabeledEdgeRings + +Finds rings and labels edges according to which rings are. +The label is a number which is increased for each ring. + +Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Edge](#edge)>** edges that start rings + +## getEdgeRings + +Computes the EdgeRings formed by the edges in this graph. + +Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** + +## \_findIntersectionNodes + +Find all nodes in a Maxima EdgeRing which are self-intersection nodes. + +**Parameters** + +- `startEdge` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** + +Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)>** intersection nodes + +## \_findEdgeRing + +Get the edge-ring which starts from the provided Edge. + +**Parameters** + +- `startEdge` **[Edge](#edge)** starting edge of the edge ring + +Returns **[EdgeRing](#edgering)** + +## removeNode + +Removes a node from the Graph. + +It also removes edges asociated to that node + +**Parameters** + +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** + +## removeEdge + +Remove edge from the graph and deletes the edge. + +**Parameters** + +- `edge` **[Edge](#edge)** + +## fromGeoJson + +Creates a graph from a GeoJSON. + +**Parameters** + +- `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** it must comply with the restrictions detailed in the index + +Returns **[Graph](#graph)** + +# addOuterEdge + +Outer edges are stored CCW order. +XXX: on each add we are ordering, this could be optimized + +**Parameters** + +- `edge` **[Edge](#edge)** + +# Edge + +This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge + +## getSymetric + +Creates or get the symetric Edge. + +Returns **[Edge](#edge)** + +## constructor + +**Parameters** + +- `from` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** start node of the Edge +- `to` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** end node of the edge + +## deleteEdge + +Removes edge from from and to nodes. + +## isEqual + +Compares Edge equallity. +An edge is equal to another, if the from and to nodes are the same. + +**Parameters** + +- `edge` + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** + +## toLineString + +Returns a LineString representation of the Edge + +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** + +## compareTo + +Comparator of two edges. +Implementation of geos::planargraph::DirectedEdge::compareTo. + +**Parameters** + +- `other` **[Edge](#edge)** +- `edge` + +Returns **Any** \-1 if this Edge has a greater angle with the positive x-axis than b, + 0 if the Edges are colinear, + 1 otherwise + +# EdgeRing + +**Extends Array** + +Ring of edges which form a polygon. +The ring may be either an outer shell or a hole. + +This class is inspired in GEOS's geos::operation::polygonize::EdgeRing + +## isValid + +Check if the ring is valid in geomtry terms. +A ring must have either 0 or 4 or more points. The first and the last must be +equal (in 2D) +geos::geom::LinearRing::validateConstruction + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** + +## isHole + +Tests whether this ring is a hole. +A ring is a hole if it is oriented counter-clockwise. +Similar implementation of geos::algorithm::CGAlgorithms::isCCW + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true: if it is a hole + +## toMultiPoint + +Creates a MultiPoint representing the EdgeRing (discarts edges directions). + +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[MultiPoint](http://geojson.org/geojson-spec.html#multipoint)>** + +## toPolygon + +Creates a Polygon representing the EdgeRing. +XXX: the polygon could be cached + +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** + +## getEnvelope + +Calculates the envelope of the EdgeRing. +XXX: the envelope could be cached + +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** envelope + +## inside + +Checks if the point is inside the edgeRing + +**Parameters** + +- `point` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Point](http://geojson.org/geojson-spec.html#point)>** + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** + +## findEdgeRingContaining + +geos::operation::polygonize::EdgeRing::findEdgeRingContaining + +**Parameters** + +- `edgeRing` **[EdgeRing](#edgering)** +- `testEdgeRing` +- `shellList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** + +Returns **[EdgeRing](#edgering)** + +# orientationIndex + +Returns the direction of the point q relative to the vector p1 -> p2. +Implementation of geos::algorithm::CGAlgorithm::orientationIndex() +(same as geos::algorithm::CGAlgorithm::computeOrientation()) + +**Parameters** + +- `p1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the origin point of the vector +- `p2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the final point of the vector +- `q` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the point to compute the direction to + +Returns **Any** 1 if q is ccw (left) from p1->p2, + \-1 if q is cw (right) from p1->p2, + 0 if q is colinear with p1->p2 + +# envelopeIsEqual + +Checks if two envelopes are equal. +The function assumes that the arguments are envelopes, i.e.: Rectangular polygon + +**Parameters** + +- `env1` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope +- `env2` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if the envelopes are equal + +# envelopeContains + +Check if a envelope is contained in other one. +The function assumes that the arguments are envelopes, i.e.: Convex polygon +XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, +this could be further improved. + +**Parameters** + +- `self` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope +- `env` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if env is contained in self + +# coordinatesEqual + +Checks if two coordinates are equal. + +**Parameters** + +- `coord1` +- `coord2` + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if coordinates are equal + + + +--- + +This module is part of the [Turfjs project](http://turfjs.org/), an open source +module collection dedicated to geographic algorithms. It is maintained in the +[Turfjs/turf](https://github.com/Turfjs/turf) repository, where you can create +PRs and issues. + +### Installation + +Install this module individually: + +```sh +$ npm install @turf/polygonize +``` + +Or install the Turf module that includes it as a function: + +```sh +$ npm install @turf/turf +``` diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 1a5dc18a55..0b97266198 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -2,12 +2,17 @@ const Graph = require('./src/Graph'), EdgeRing = require('./src/EdgeRing'), { featureCollection } = require('@turf/helpers'); -/** Implementation of GEOSPolygonizel function (geos::operation::polygonize::Polygonizer) +/** Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * * Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly - * noded, i.e., they must only meet at their endpoints. + * noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate + * points. * - * LineStrings must only have to coordinate points. + * The implementation correctly handles: + * + * - Dangles: edges which have one or both ends which are not incident on another edge endpoint. + * - Cut Edges (bridges): edges that are connected at both ends but which do not form part + * of a polygon. * * @param {FeatureCollection} geoJson - Lines in order to polygonize * @return {FeatureCollection} diff --git a/packages/turf-polygonize/src/EdgeRing.js b/packages/turf-polygonize/src/EdgeRing.js index ebe852969a..4041856b3d 100644 --- a/packages/turf-polygonize/src/EdgeRing.js +++ b/packages/turf-polygonize/src/EdgeRing.js @@ -92,7 +92,7 @@ class EdgeRing extends Array { return; if (envelopeContains(tryEnvelope, testEnvelope)) { - const testPoint = testEdgeRing.map(edge => edge.from.coodinates) + const testPoint = testEdgeRing.map(edge => edge.from.coordinates) .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); if (testPoint && shell.inside(point(testPoint))) { diff --git a/packages/turf-polygonize/src/Graph.js b/packages/turf-polygonize/src/Graph.js index cae44ab4c8..796f950797 100644 --- a/packages/turf-polygonize/src/Graph.js +++ b/packages/turf-polygonize/src/Graph.js @@ -5,13 +5,17 @@ const Node = require('./Node'), /** 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, + * 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) */ class Graph { + /** Creates a graph from a GeoJSON. + * @param {FeatureCollection} geoJson - it must comply with the restrictions detailed in the index + * @return {Graph} + */ static fromGeoJson(geoJson) { const graph = new Graph(); geoJson.features.forEach(feature => { @@ -117,8 +121,8 @@ class Graph { /** 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 PolygonizeGraph::computeNextCCWEdges, could be written - * in a more javascript way + * XXX: method literally transcribed from `geos::operation::polygonize::PolygonizeGraph::computeNextCCWEdges`, + * could be written in a more javascript way. * * @param {Node} node * @param {Number} label diff --git a/packages/turf-polygonize/src/Graph.test.js b/packages/turf-polygonize/src/Graph.test.js index c41907ad82..71c484be26 100644 --- a/packages/turf-polygonize/src/Graph.test.js +++ b/packages/turf-polygonize/src/Graph.test.js @@ -3,7 +3,7 @@ const test = require('tape'), Node = require('./Node'), { featureCollection, lineString } = require('@turf/helpers'); -test('graph.fromGeoJson', t => { +test('Graph :: fromGeoJson', t => { const geoJson = featureCollection([ lineString([[0, 1], [0, 0]]), lineString([[1, 1], [0, 0]]), @@ -19,7 +19,7 @@ test('graph.fromGeoJson', t => { t.end(); }); -test('deleteDangles', t => { +test('Graph :: deleteDangles', t => { const geoJson = featureCollection([ lineString([[0, 0], [0, 1]]), lineString([[0, 1], [0, 2]]), @@ -38,7 +38,7 @@ test('deleteDangles', t => { t.end(); }); -test('deleteCutEdges', t => { +test('Graph :: deleteCutEdges', t => { const geoJson = featureCollection([ lineString([[0, 0], [0, 1]]), lineString([[0, 1], [1, 1]]), @@ -61,7 +61,7 @@ test('deleteCutEdges', t => { t.end(); }); -test('getEdgeRings', t => { +test('Graph :: getEdgeRings', t => { const geoJson = featureCollection([ lineString([[0, 0], [0, 1]]), lineString([[0, 1], [1, 1]]), diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 824555ffa5..9e51e47198 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -1,27 +1,51 @@ const test = require('tape'), { featureCollection, lineString, polygon } = require('@turf/helpers'), - polygonize = require('./index.js'); + polygonize = require('./'), + fs = require('fs'), + path = require('path'); -test('Polygonize', t => { - const geoJson = featureCollection([ - lineString([[-58.3959417, -34.8036499], [-58.395087, -34.8031464]]), - lineString([[-58.3964727, -34.8029764], [-58.3959417, -34.8036499]]), - lineString([[-58.395087, -34.8031464], [-58.3942164, -34.8042266]]), - lineString([[-58.3942164, -34.8042266], [-58.3949969, -34.8047067]]), - lineString([[-58.3949969, -34.8047067], [-58.3957427, -34.8051655]]), - lineString([[-58.396618, -34.8040484], [-58.3957427, -34.8051655]]), - lineString([[-58.3976747, -34.8036356], [-58.3971168, -34.8043422]]), - lineString([[-58.3976747, -34.8036356], [-58.3964727, -34.8029764]]), - lineString([[-58.3971168, -34.8043422], [-58.396618, -34.8040484]]), - lineString([[-58.396618, -34.8040484], [-58.3959417, -34.8036499]]), - ]), - expected = featureCollection([ - polygon([[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]), - polygon([[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]), - ]), - polygons = polygonize(geoJson); +const directories = { + in: path.join(__dirname, 'test', 'in') + path.sep, + out: path.join(__dirname, 'test', 'out') + path.sep, +}; - t.deepEqual(polygons, expected); +function getFullPath(filename, type='in') { + return path.join(directories[type], filename); +} - t.end(); -}); +function readJsonFile(filename, type='in') { + try { + return JSON.parse(fs.readFileSync(getFullPath(filename, type))); + } catch(e) { + if (e.code !== 'ENOENT') + throw e; + return undefined; + } +} + +function writeJsonFile(filename, data, type='out') { + fs.writeFileSync(getFullPath(filename, type), JSON.stringify(data)); +} + +fs.readdirSync(directories.in) + .filter(filename => !filename.startsWith('.')) + .map(filename => { + return { + filename, + name: path.parse(filename).name, + input: readJsonFile(filename, 'in'), + output: readJsonFile(filename, 'out'), + } + }) + .forEach(({filename, name, input, output}) => { + test(`turf-polygonize :: ${name}`, t => { + const result = polygonize(input); + if (output) { + t.deepEqual(result, output); + } else { + t.skip(`${name} not found :: writing`); + writeJsonFile(filename, result); + } + t.end(); + }); + }); diff --git a/packages/turf-polygonize/test/in/complex.geojson b/packages/turf-polygonize/test/in/complex.geojson new file mode 100644 index 0000000000..f29a20fe43 --- /dev/null +++ b/packages/turf-polygonize/test/in/complex.geojson @@ -0,0 +1,4109 @@ +{ + "type":"FeatureCollection", + "features":[ + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4012571, + -34.8013012 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4020709, + -34.8002564 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4020709, + -34.8002564 + ], + [ + -58.4026441, + -34.7995347 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4026441, + -34.7995347 + ], + [ + -58.4032748, + -34.7987421 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4032748, + -34.7987421 + ], + [ + -58.40353, + -34.7986715 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.40353, + -34.7986715 + ], + [ + -58.4045643, + -34.7970077 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4045643, + -34.7970077 + ], + [ + -58.4051217, + -34.7960535 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4069531, + -34.7970396 + ], + [ + -58.4061007, + -34.797642 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4061007, + -34.797642 + ], + [ + -58.4054827, + -34.7986499 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4054827, + -34.7986499 + ], + [ + -58.4043951, + -34.7993638 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4043951, + -34.7993638 + ], + [ + -58.4037967, + -34.8001502 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4037967, + -34.8001502 + ], + [ + -58.4032304, + -34.80088 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4032304, + -34.80088 + ], + [ + -58.4024017, + -34.8019585 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4024017, + -34.8019585 + ], + [ + -58.4015377, + -34.8030708 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942128, + -34.8026319 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942128, + -34.8026319 + ], + [ + -58.3937803, + -34.8023836 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3937803, + -34.8023836 + ], + [ + -58.3937374, + -34.8023571 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3937374, + -34.8023571 + ], + [ + -58.3935525, + -34.8022475 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935525, + -34.8022475 + ], + [ + -58.3924396, + -34.8015867 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3924396, + -34.8015867 + ], + [ + -58.3913232, + -34.8009287 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3913232, + -34.8009287 + ], + [ + -58.390647, + -34.8005306 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3918268, + -34.8023406 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3918268, + -34.8023406 + ], + [ + -58.39097, + -34.8018398 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.39097, + -34.8018398 + ], + [ + -58.3908824, + -34.80173 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3908824, + -34.80173 + ], + [ + -58.3907391, + -34.8016607 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935525, + -34.8022475 + ], + [ + -58.3929547, + -34.8030144 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3919262, + -34.8043515 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3912106, + -34.805308 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3912106, + -34.805308 + ], + [ + -58.3903757, + -34.8063186 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3919262, + -34.8043515 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3929448, + -34.8049413 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3932414, + -34.8051253 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3930577, + -34.8030849 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3930577, + -34.8030849 + ], + [ + -58.3929448, + -34.8049413 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3929333, + -34.8056958 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3929333, + -34.8056958 + ], + [ + -58.3925604, + -34.8061236 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3925604, + -34.8061236 + ], + [ + -58.3917149, + -34.8070982 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3917149, + -34.8070982 + ], + [ + -58.3906765, + -34.8083226 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3906765, + -34.8083226 + ], + [ + -58.3906293, + -34.8083765 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3906293, + -34.8083765 + ], + [ + -58.3905693, + -34.8089708 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3931006, + -34.8064748 + ], + [ + -58.3925604, + -34.8061236 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3925604, + -34.8061236 + ], + [ + -58.3912106, + -34.805308 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3912106, + -34.805308 + ], + [ + -58.3900486, + -34.8046158 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3900486, + -34.8046158 + ], + [ + -58.3887181, + -34.8038776 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3962631, + -34.7999826 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3962631, + -34.7999826 + ], + [ + -58.3963617, + -34.8000402 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3977629, + -34.8008817 + ], + [ + -58.3980104, + -34.8010326 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4056871, + -34.803814 + ], + [ + -58.40459, + -34.8031917 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.40459, + -34.8031917 + ], + [ + -58.4035424, + -34.8026119 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4035424, + -34.8026119 + ], + [ + -58.4024017, + -34.8019585 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4024017, + -34.8019585 + ], + [ + -58.4012571, + -34.8013012 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4003196, + -34.8007694 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003196, + -34.8007694 + ], + [ + -58.4000651, + -34.800625 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4000651, + -34.800625 + ], + [ + -58.3994936, + -34.8003009 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005863, + -34.7973807 + ], + [ + -58.3999811, + -34.7981126 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3999811, + -34.7981126 + ], + [ + -58.3994224, + -34.7987983 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3994224, + -34.7987983 + ], + [ + -58.3991078, + -34.7991665 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3991078, + -34.7991665 + ], + [ + -58.3986112, + -34.7998147 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3980853, + -34.8004779 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3980853, + -34.8004779 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4000651, + -34.800625 + ], + [ + -58.4008757, + -34.7995904 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4008757, + -34.7995904 + ], + [ + -58.4014189, + -34.7988802 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4014189, + -34.7988802 + ], + [ + -58.4020013, + -34.7981301 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003196, + -34.8007694 + ], + [ + -58.399452, + -34.8018971 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3940899, + -34.8003361 + ], + [ + -58.3945242, + -34.8006035 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001253, + -34.8060931 + ], + [ + -58.400154, + -34.8060524 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.400154, + -34.8060524 + ], + [ + -58.400195, + -34.8060195 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.400195, + -34.8060195 + ], + [ + -58.4002453, + -34.805997 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4002453, + -34.805997 + ], + [ + -58.4003011, + -34.8059864 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003011, + -34.8059864 + ], + [ + -58.4003583, + -34.8059886 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003583, + -34.8059886 + ], + [ + -58.4004127, + -34.8060034 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4004127, + -34.8060034 + ], + [ + -58.4004602, + -34.8060297 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4005021, + -34.8060719 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005021, + -34.8060719 + ], + [ + -58.4005258, + -34.8061227 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005258, + -34.8061227 + ], + [ + -58.4005291, + -34.806177 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005291, + -34.806177 + ], + [ + -58.4005117, + -34.8062295 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005117, + -34.8062295 + ], + [ + -58.4004752, + -34.806275 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4013091, + -34.8049401 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4013091, + -34.8049401 + ], + [ + -58.4015784, + -34.8046036 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4024151, + -34.8035586 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3941494, + -34.8144285 + ], + [ + -58.3947151, + -34.8135927 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3947151, + -34.8135927 + ], + [ + -58.39521, + -34.8128913 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.39521, + -34.8128913 + ], + [ + -58.3961515, + -34.8116469 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3961515, + -34.8116469 + ], + [ + -58.3966281, + -34.8110239 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3966281, + -34.8110239 + ], + [ + -58.3970574, + -34.8104706 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3970574, + -34.8104706 + ], + [ + -58.3970575, + -34.8103726 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3970575, + -34.8103726 + ], + [ + -58.3978259, + -34.8093579 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3978259, + -34.8093579 + ], + [ + -58.3985907, + -34.8083664 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3985907, + -34.8083664 + ], + [ + -58.3993749, + -34.8073699 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3993749, + -34.8073699 + ], + [ + -58.400226, + -34.8063129 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4028696, + -34.8075087 + ], + [ + -58.4015378, + -34.8068225 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4015378, + -34.8068225 + ], + [ + -58.4004752, + -34.806275 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4005424, + -34.8040087 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005424, + -34.8040087 + ], + [ + -58.399244, + -34.8055954 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.399244, + -34.8055954 + ], + [ + -58.3983081, + -34.806706 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3983081, + -34.806706 + ], + [ + -58.3974713, + -34.8077069 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3966454, + -34.8086947 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4011968, + -34.8097556 + ], + [ + -58.4000438, + -34.809165 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4000438, + -34.809165 + ], + [ + -58.3999022, + -34.8090924 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3999022, + -34.8090924 + ], + [ + -58.3998633, + -34.8090725 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3998633, + -34.8090725 + ], + [ + -58.3985907, + -34.8083664 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3985907, + -34.8083664 + ], + [ + -58.3974713, + -34.8077069 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3964129, + -34.8070746 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941477, + -34.8057278 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4005424, + -34.8040087 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4005424, + -34.8040087 + ], + [ + -58.3994805, + -34.8033989 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3994805, + -34.8033989 + ], + [ + -58.398366, + -34.8027589 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.398366, + -34.8027589 + ], + [ + -58.3971802, + -34.8020789 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3971802, + -34.8020789 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3971802, + -34.8020789 + ], + [ + -58.3969642, + -34.8023356 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3969642, + -34.8023356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3969642, + -34.8023356 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3935984, + -34.8050019 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3972354, + -34.8060837 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3983081, + -34.806706 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3983081, + -34.806706 + ], + [ + -58.3993749, + -34.8073699 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941987, + -34.8071883 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3934596, + -34.8080828 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3934596, + -34.8080828 + ], + [ + -58.393423, + -34.8082664 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.398366, + -34.8027589 + ], + [ + -58.3976747, + -34.8036356 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3976747, + -34.8036356 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4013091, + -34.8049401 + ], + [ + -58.4025396, + -34.8055669 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4025396, + -34.8055669 + ], + [ + -58.4038371, + -34.8062277 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3891946, + -34.8056311 + ], + [ + -58.3900486, + -34.8046158 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3900486, + -34.8046158 + ], + [ + -58.3904782, + -34.804065 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3904782, + -34.804065 + ], + [ + -58.3907792, + -34.8036757 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3918268, + -34.8023406 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3918268, + -34.8023406 + ], + [ + -58.3924396, + -34.8015867 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3924396, + -34.8015867 + ], + [ + -58.3931521, + -34.8007371 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3931521, + -34.8007371 + ], + [ + -58.3935142, + -34.8002063 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935142, + -34.8002063 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3938917, + -34.8002224 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3930397, + -34.7987714 + ], + [ + -58.393525, + -34.7990038 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.393525, + -34.7990038 + ], + [ + -58.3935706, + -34.7990412 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935706, + -34.7990412 + ], + [ + -58.3936001, + -34.7990765 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3936001, + -34.7990765 + ], + [ + -58.3936215, + -34.7991227 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3936215, + -34.7991227 + ], + [ + -58.3936376, + -34.7991734 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3936376, + -34.7991734 + ], + [ + -58.3936457, + -34.7992395 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3936457, + -34.7992395 + ], + [ + -58.393643, + -34.7993122 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.393643, + -34.7993122 + ], + [ + -58.3936376, + -34.7994289 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3936376, + -34.7994289 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3986617, + -34.8014125 + ], + [ + -58.3990591, + -34.8008188 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3990591, + -34.8008188 + ], + [ + -58.3994936, + -34.8003009 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935636, + -34.8067511 + ], + [ + -58.3935559, + -34.8065149 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935559, + -34.8065149 + ], + [ + -58.3941477, + -34.8057278 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3941477, + -34.8057278 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3955515, + -34.8040306 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3930098, + -34.8116828 + ], + [ + -58.3930233, + -34.8114078 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3930233, + -34.8114078 + ], + [ + -58.3938559, + -34.8103296 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3938559, + -34.8103296 + ], + [ + -58.3948057, + -34.8091206 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3948057, + -34.8091206 + ], + [ + -58.3956137, + -34.8080702 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3964129, + -34.8070746 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3966681, + -34.8067671 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3972354, + -34.8060837 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3981728, + -34.8049644 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3987746, + -34.8042389 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3994805, + -34.8033989 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3945242, + -34.8006035 + ], + [ + -58.3939846, + -34.8012736 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3972482, + -34.8070924 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3986617, + -34.8014125 + ], + [ + -58.399452, + -34.8018971 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.399452, + -34.8018971 + ], + [ + -58.4003357, + -34.8023944 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003357, + -34.8023944 + ], + [ + -58.4003789, + -34.8024187 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4013631, + -34.8029699 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4015377, + -34.8030708 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4015377, + -34.8030708 + ], + [ + -58.4024151, + -34.8035586 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4024151, + -34.8035586 + ], + [ + -58.4026791, + -34.8037053 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4026791, + -34.8037053 + ], + [ + -58.4036115, + -34.8042237 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4036115, + -34.8042237 + ], + [ + -58.4037436, + -34.8042971 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4037436, + -34.8042971 + ], + [ + -58.4048825, + -34.804862 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3994936, + -34.8003009 + ], + [ + -58.3986112, + -34.7998147 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3972001, + -34.799019 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3972001, + -34.799019 + ], + [ + -58.3962705, + -34.798502 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3935793, + -34.800023 + ], + [ + -58.392493, + -34.7994585 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.392493, + -34.7994585 + ], + [ + -58.3917332, + -34.7990214 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3917332, + -34.7990214 + ], + [ + -58.3913394, + -34.7988011 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3913394, + -34.7988011 + ], + [ + -58.3912913, + -34.7986684 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942128, + -34.8026319 + ], + [ + -58.3952781, + -34.8013356 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3954312, + -34.8011493 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3954312, + -34.8011493 + ], + [ + -58.3963617, + -34.8000402 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3972001, + -34.799019 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3966454, + -34.8086947 + ], + [ + -58.3956137, + -34.8080702 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3941987, + -34.8071883 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3935636, + -34.8067511 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3945242, + -34.8006035 + ], + [ + -58.3954312, + -34.8011493 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3983132, + -34.8012066 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3983132, + -34.8012066 + ], + [ + -58.3986617, + -34.8014125 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3941728, + -34.7987608 + ], + [ + -58.3942795, + -34.7988258 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942795, + -34.7988258 + ], + [ + -58.3945157, + -34.7989646 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3945157, + -34.7989646 + ], + [ + -58.3954262, + -34.7994934 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001253, + -34.8060931 + ], + [ + -58.399244, + -34.8055954 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.399244, + -34.8055954 + ], + [ + -58.3981728, + -34.8049644 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3971168, + -34.8043422 + ], + [ + -58.396618, + -34.8040484 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3962705, + -34.798502 + ], + [ + -58.3954262, + -34.7994934 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3945242, + -34.8006035 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4004752, + -34.806275 + ], + [ + -58.4004221, + -34.8063094 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4004221, + -34.8063094 + ], + [ + -58.4003584, + -34.8063282 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4003584, + -34.8063282 + ], + [ + -58.4002907, + -34.8063294 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4002907, + -34.8063294 + ], + [ + -58.400226, + -34.8063129 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.400226, + -34.8063129 + ], + [ + -58.4001748, + -34.8062833 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001748, + -34.8062833 + ], + [ + -58.4001364, + -34.8062426 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001364, + -34.8062426 + ], + [ + -58.4001143, + -34.8061945 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001143, + -34.8061945 + ], + [ + -58.4001105, + -34.8061431 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.4001105, + -34.8061431 + ], + [ + -58.4001253, + -34.8060931 + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/in/cutedge.geojson b/packages/turf-polygonize/test/in/cutedge.geojson new file mode 100644 index 0000000000..6681670563 --- /dev/null +++ b/packages/turf-polygonize/test/in/cutedge.geojson @@ -0,0 +1,209 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9661968, + 40.7997572 + ], + [ + -73.967898, + 40.800463 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9689288, + 40.7984325 + ], + [ + -73.9675747, + 40.7978699 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9671143, + 40.7985006 + ], + [ + -73.9684641, + 40.7990578 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9689288, + 40.7984325 + ], + [ + -73.9684641, + 40.7990578 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.968007, + 40.8000481 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.968007, + 40.8000481 + ], + [ + -73.9679585, + 40.8002177 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9679585, + 40.8002177 + ], + [ + -73.967898, + 40.800463 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9675747, + 40.7978699 + ], + [ + -73.9671143, + 40.7985006 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9671143, + 40.7985006 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9666552, + 40.7991294 + ], + [ + -73.9661968, + 40.7997572 + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/in/dangle.geojson b/packages/turf-polygonize/test/in/dangle.geojson new file mode 100644 index 0000000000..5a469e423e --- /dev/null +++ b/packages/turf-polygonize/test/in/dangle.geojson @@ -0,0 +1,158 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9661968, + 40.7997572 + ], + [ + -73.967898, + 40.800463 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9681077, + 40.7997378 + ], + [ + -73.968007, + 40.8000481 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.968007, + 40.8000481 + ], + [ + -73.9679585, + 40.8002177 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9679585, + 40.8002177 + ], + [ + -73.967898, + 40.800463 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9675747, + 40.7978699 + ], + [ + -73.9671143, + 40.7985006 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9671143, + 40.7985006 + ], + [ + -73.9666552, + 40.7991294 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.9666552, + 40.7991294 + ], + [ + -73.9661968, + 40.7997572 + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/in/two-polygons.geojson b/packages/turf-polygonize/test/in/two-polygons.geojson new file mode 100644 index 0000000000..5602209fbb --- /dev/null +++ b/packages/turf-polygonize/test/in/two-polygons.geojson @@ -0,0 +1,195 @@ +{ + "type":"FeatureCollection", + "features":[ + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.3971168, + -34.8043422 + ], + [ + -58.396618, + -34.8040484 + ] + ] + } + }, + { + "type":"Feature", + "properties":{ + + }, + "geometry":{ + "type":"LineString", + "coordinates":[ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/complex.geojson b/packages/turf-polygonize/test/out/complex.geojson new file mode 100644 index 0000000000..be37d9cb85 --- /dev/null +++ b/packages/turf-polygonize/test/out/complex.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4003789,-34.8024187],[-58.4012571,-34.8013012],[-58.4003196,-34.8007694],[-58.399452,-34.8018971],[-58.4003357,-34.8023944],[-58.4003789,-34.8024187]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4012571,-34.8013012],[-58.4003789,-34.8024187],[-58.4013631,-34.8029699],[-58.4015377,-34.8030708],[-58.4024017,-34.8019585],[-58.4012571,-34.8013012]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.395087,-34.8031464],[-58.3959417,-34.8036499],[-58.3964727,-34.8029764],[-58.3969642,-34.8023356],[-58.3952781,-34.8013356],[-58.3942128,-34.8026319],[-58.395087,-34.8031464]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3935525,-34.8022475],[-58.3924396,-34.8015867],[-58.3918268,-34.8023406],[-58.3929547,-34.8030144],[-58.3935525,-34.8022475]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3929547,-34.8030144],[-58.3918268,-34.8023406],[-58.3907792,-34.8036757],[-58.3919262,-34.8043515],[-58.3929547,-34.8030144]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3929547,-34.8030144],[-58.3919262,-34.8043515],[-58.3929448,-34.8049413],[-58.3930577,-34.8030849],[-58.3929547,-34.8030144]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3919262,-34.8043515],[-58.3912106,-34.805308],[-58.3925604,-34.8061236],[-58.3929333,-34.8056958],[-58.3929448,-34.8049413],[-58.3919262,-34.8043515]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3912106,-34.805308],[-58.3919262,-34.8043515],[-58.3907792,-34.8036757],[-58.3904782,-34.804065],[-58.3900486,-34.8046158],[-58.3912106,-34.805308]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3954262,-34.7994934],[-58.3962631,-34.7999826],[-58.3963617,-34.8000402],[-58.3972001,-34.799019],[-58.3962705,-34.798502],[-58.3954262,-34.7994934]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3962631,-34.7999826],[-58.3954262,-34.7994934],[-58.3945242,-34.8006035],[-58.3954312,-34.8011493],[-58.3963617,-34.8000402],[-58.3962631,-34.7999826]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3963617,-34.8000402],[-58.3977629,-34.8008817],[-58.3980853,-34.8004779],[-58.3986112,-34.7998147],[-58.3972001,-34.799019],[-58.3963617,-34.8000402]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3977629,-34.8008817],[-58.3963617,-34.8000402],[-58.3954312,-34.8011493],[-58.3952781,-34.8013356],[-58.3969642,-34.8023356],[-58.3971802,-34.8020789],[-58.3980104,-34.8010326],[-58.3977629,-34.8008817]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3977629,-34.8008817],[-58.3980104,-34.8010326],[-58.3983132,-34.8012066],[-58.3986617,-34.8014125],[-58.3990591,-34.8008188],[-58.3994936,-34.8003009],[-58.3986112,-34.7998147],[-58.3980853,-34.8004779],[-58.3977629,-34.8008817]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4003196,-34.8007694],[-58.4000651,-34.800625],[-58.3994936,-34.8003009],[-58.3990591,-34.8008188],[-58.3986617,-34.8014125],[-58.399452,-34.8018971],[-58.4003196,-34.8007694]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4001253,-34.8060931],[-58.400154,-34.8060524],[-58.400195,-34.8060195],[-58.4002453,-34.805997],[-58.4003011,-34.8059864],[-58.4003583,-34.8059886],[-58.4004127,-34.8060034],[-58.4004602,-34.8060297],[-58.4013091,-34.8049401],[-58.4015784,-34.8046036],[-58.4005424,-34.8040087],[-58.399244,-34.8055954],[-58.4001253,-34.8060931]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.400154,-34.8060524],[-58.4001253,-34.8060931],[-58.4001105,-34.8061431],[-58.4001143,-34.8061945],[-58.4001364,-34.8062426],[-58.4001748,-34.8062833],[-58.400226,-34.8063129],[-58.4002907,-34.8063294],[-58.4003584,-34.8063282],[-58.4004221,-34.8063094],[-58.4004752,-34.806275],[-58.4005117,-34.8062295],[-58.4005291,-34.806177],[-58.4005258,-34.8061227],[-58.4005021,-34.8060719],[-58.4004602,-34.8060297],[-58.4004127,-34.8060034],[-58.4003583,-34.8059886],[-58.4003011,-34.8059864],[-58.4002453,-34.805997],[-58.400195,-34.8060195],[-58.400154,-34.8060524]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4015784,-34.8046036],[-58.4024151,-34.8035586],[-58.4015377,-34.8030708],[-58.4013631,-34.8029699],[-58.4005424,-34.8040087],[-58.4015784,-34.8046036]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3985907,-34.8083664],[-58.3993749,-34.8073699],[-58.3983081,-34.806706],[-58.3974713,-34.8077069],[-58.3985907,-34.8083664]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3993749,-34.8073699],[-58.400226,-34.8063129],[-58.4001748,-34.8062833],[-58.4001364,-34.8062426],[-58.4001143,-34.8061945],[-58.4001105,-34.8061431],[-58.4001253,-34.8060931],[-58.399244,-34.8055954],[-58.3983081,-34.806706],[-58.3993749,-34.8073699]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4005424,-34.8040087],[-58.4013631,-34.8029699],[-58.4003789,-34.8024187],[-58.4003357,-34.8023944],[-58.399452,-34.8018971],[-58.3986617,-34.8014125],[-58.3983132,-34.8012066],[-58.3980104,-34.8010326],[-58.3971802,-34.8020789],[-58.398366,-34.8027589],[-58.3994805,-34.8033989],[-58.4005424,-34.8040087]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.399244,-34.8055954],[-58.4005424,-34.8040087],[-58.3994805,-34.8033989],[-58.3987746,-34.8042389],[-58.3981728,-34.8049644],[-58.399244,-34.8055954]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3983081,-34.806706],[-58.399244,-34.8055954],[-58.3981728,-34.8049644],[-58.3972354,-34.8060837],[-58.3983081,-34.806706]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3974713,-34.8077069],[-58.3983081,-34.806706],[-58.3972354,-34.8060837],[-58.3966681,-34.8067671],[-58.3964129,-34.8070746],[-58.3974713,-34.8077069]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3966454,-34.8086947],[-58.3974713,-34.8077069],[-58.3964129,-34.8070746],[-58.3956137,-34.8080702],[-58.3966454,-34.8086947]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964129,-34.8070746],[-58.3949373,-34.8061929],[-58.3941987,-34.8071883],[-58.3956137,-34.8080702],[-58.3964129,-34.8070746]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3949373,-34.8061929],[-58.3964129,-34.8070746],[-58.3966681,-34.8067671],[-58.3972354,-34.8060837],[-58.3957427,-34.8051655],[-58.3949373,-34.8061929]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3949373,-34.8061929],[-58.3941477,-34.8057278],[-58.3935559,-34.8065149],[-58.3935636,-34.8067511],[-58.3941987,-34.8071883],[-58.3949373,-34.8061929]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3941477,-34.8057278],[-58.3949373,-34.8061929],[-58.3957427,-34.8051655],[-58.3949969,-34.8047067],[-58.3941477,-34.8057278]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3994805,-34.8033989],[-58.398366,-34.8027589],[-58.3976747,-34.8036356],[-58.3987746,-34.8042389],[-58.3994805,-34.8033989]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.398366,-34.8027589],[-58.3971802,-34.8020789],[-58.3969642,-34.8023356],[-58.3964727,-34.8029764],[-58.3976747,-34.8036356],[-58.398366,-34.8027589]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3957427,-34.8051655],[-58.3972354,-34.8060837],[-58.3981728,-34.8049644],[-58.3971168,-34.8043422],[-58.396618,-34.8040484],[-58.3957427,-34.8051655]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3976747,-34.8036356],[-58.3971168,-34.8043422],[-58.3981728,-34.8049644],[-58.3987746,-34.8042389],[-58.3976747,-34.8036356]]]}}]} \ No newline at end of file diff --git a/packages/turf-polygonize/test/out/cutedge.geojson b/packages/turf-polygonize/test/out/cutedge.geojson new file mode 100644 index 0000000000..3db8b5b09c --- /dev/null +++ b/packages/turf-polygonize/test/out/cutedge.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-73.9675747,40.7978699],[-73.9689288,40.7984325],[-73.9684641,40.7990578],[-73.9671143,40.7985006],[-73.9675747,40.7978699]]]}}]} \ No newline at end of file diff --git a/packages/turf-polygonize/test/out/dangle.geojson b/packages/turf-polygonize/test/out/dangle.geojson new file mode 100644 index 0000000000..3aa0a428ce --- /dev/null +++ b/packages/turf-polygonize/test/out/dangle.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[]} \ No newline at end of file diff --git a/packages/turf-polygonize/test/out/two-polygons.geojson b/packages/turf-polygonize/test/out/two-polygons.geojson new file mode 100644 index 0000000000..ff92101f3b --- /dev/null +++ b/packages/turf-polygonize/test/out/two-polygons.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]}}]} \ No newline at end of file From 3d6e2f68482e7cc7259e8baf3f9c0c05dfea90d4 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 22:47:25 -0300 Subject: [PATCH 14/36] Fixed src/util.js lint --- packages/turf-polygonize/src/util.js | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/turf-polygonize/src/util.js b/packages/turf-polygonize/src/util.js index 359f035dbf..467e754fd8 100644 --- a/packages/turf-polygonize/src/util.js +++ b/packages/turf-polygonize/src/util.js @@ -1,5 +1,5 @@ const inside = require('@turf/inside'), - { point } = require('@turf/helpers'); + {point} = require('@turf/helpers'); /** Returns the direction of the point q relative to the vector p1 -> p2. * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() @@ -9,17 +9,17 @@ const inside = require('@turf/inside'), * @param {Number[]} p2 - the final point of the vector * @param {Number[]} q - the point to compute the direction to * - * @return 1 if q is ccw (left) from p1->p2, + * @returns {Number} - 1 if q is ccw (left) from p1->p2, * -1 if q is cw (right) from p1->p2, * 0 if q is colinear with p1->p2 */ function orientationIndex(p1, p2, q) { - const dx1 = p2[0] - p1[0], - dy1 = p2[1] - p1[1], - dx2 = q[0] - p2[0], - dy2 = q[1] - p2[1]; + const dx1 = p2[0] - p1[0], + dy1 = p2[1] - p1[1], + dx2 = q[0] - p2[0], + dy2 = q[1] - p2[1]; - return Math.sign(dx1 * dy2 - dx2 * dy1); + return Math.sign(dx1 * dy2 - dx2 * dy1); } /** Checks if two envelopes are equal. @@ -27,18 +27,18 @@ function orientationIndex(p1, p2, q) { * * @param {Feature} env1 - Envelope * @param {Feature} env2 - Envelope - * @return {Boolean} - True if the envelopes are equal + * @returns {Boolean} - True if the envelopes are equal */ function envelopeIsEqual(env1, env2) { - const envX1 = env1.geometry.coordinates.map(c => c[0]), - envY1 = env1.geometry.coordinates.map(c => c[1]), - envX2 = env2.geometry.coordinates.map(c => c[0]), - envY2 = env2.geometry.coordinates.map(c => c[1]); + const envX1 = env1.geometry.coordinates.map(c => c[0]), + envY1 = env1.geometry.coordinates.map(c => c[1]), + envX2 = env2.geometry.coordinates.map(c => c[0]), + envY2 = env2.geometry.coordinates.map(c => c[1]); - return Math.max(null, envX1) == Math.max(null, envX2) && - Math.max(null, envY1) == Math.max(null, envY2) && - Math.min(null, envX1) == Math.min(null, envX2) && - Math.min(null, envY1) == Math.min(null, envY2); + return Math.max(null, envX1) === Math.max(null, envX2) && + Math.max(null, envY1) === Math.max(null, envY2) && + Math.min(null, envX1) === Math.min(null, envX2) && + Math.min(null, envY1) === Math.min(null, envY2); } /** Check if a envelope is contained in other one. @@ -48,25 +48,25 @@ function envelopeIsEqual(env1, env2) { * * @param {Feature} self - Envelope * @param {Feature} env - Envelope - * @return {Boolean} - True if env is contained in self + * @returns {Boolean} - True if env is contained in self */ function envelopeContains(self, env) { - return env.geometry.coordinates[0].every(c => inside(point(c), self)); + return env.geometry.coordinates[0].every(c => inside(point(c), self)); } /** Checks if two coordinates are equal. * - * @param {Number[]} - * @param {Number[]} - * @return {Boolean} - True if coordinates are equal + * @param {Number[]} coord1 - First coordinate + * @param {Number[]} coord2 - Second coordinate + * @returns {Boolean} - True if coordinates are equal */ function coordinatesEqual(coord1, coord2) { - return coord1[0] == coord2[0] && coord1[1] == coord2[1]; + return coord1[0] === coord2[0] && coord1[1] === coord2[1]; } module.exports = { - orientationIndex, - envelopeIsEqual, - envelopeContains, - coordinatesEqual, + orientationIndex, + envelopeIsEqual, + envelopeContains, + coordinatesEqual }; From 6caa419941030ae91ca78bf20dc1bbebcce8098b Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 22:54:00 -0300 Subject: [PATCH 15/36] Lint: fixed Node and Node.test --- packages/turf-polygonize/index.js | 2 +- packages/turf-polygonize/src/Edge.js | 120 ++-- packages/turf-polygonize/src/EdgeRing.js | 190 +++---- packages/turf-polygonize/src/EdgeRing.test.js | 28 +- packages/turf-polygonize/src/Graph.js | 536 +++++++++--------- packages/turf-polygonize/src/Graph.test.js | 132 ++--- packages/turf-polygonize/src/Node.js | 100 ++-- packages/turf-polygonize/src/Node.test.js | 36 +- 8 files changed, 572 insertions(+), 572 deletions(-) diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 0b97266198..629ec3f802 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -15,7 +15,7 @@ const Graph = require('./src/Graph'), * of a polygon. * * @param {FeatureCollection} geoJson - Lines in order to polygonize - * @return {FeatureCollection} + * @returns {FeatureCollection} */ module.exports = function polygonize(geoJson) { const graph = Graph.fromGeoJson(geoJson); diff --git a/packages/turf-polygonize/src/Edge.js b/packages/turf-polygonize/src/Edge.js index e0a7f3a995..68630e3d3a 100644 --- a/packages/turf-polygonize/src/Edge.js +++ b/packages/turf-polygonize/src/Edge.js @@ -1,76 +1,76 @@ -const { lineString } = require('@turf/helpers'), - { orientationIndex } = require('./util'); +const {lineString} = require('@turf/helpers'), + {orientationIndex} = require('./util'); /** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge */ class Edge { - /** Creates or get the symetric Edge. - * - * @return {Edge} - */ - getSymetric() { - if (! this.symetric) { - this.symetric = new Edge(this.to, this.from); - this.symetric.symetric = this; - } + /** Creates or get the symetric Edge. + * + * @returns {Edge} + */ + getSymetric() { + if (! this.symetric) { + this.symetric = new Edge(this.to, this.from); + this.symetric.symetric = this; + } - return this.symetric; - } + return this.symetric; + } - /** - * @param {Node} from - start node of the Edge - * @param {Node} to - end node of the edge - */ - constructor(from, to) { - this.from = from; //< start - this.to = to; //< End + /** + * @param {Node} from - start node of the Edge + * @param {Node} to - end node of the edge + */ + constructor(from, to) { + this.from = from; //< start + this.to = to; //< End - this.next = undefined; //< The edge to be computed after - this.label = undefined; //< Used in order to detect Cut Edges (Bridges) - this.symetric = undefined; //< The symetric edge of this - this.ring = undefined; //< EdgeRing in which the Edge is + this.next = undefined; //< The edge to be computed after + this.label = undefined; //< Used in order to detect Cut Edges (Bridges) + this.symetric = undefined; //< The symetric edge of this + this.ring = undefined; //< EdgeRing in which the Edge is - this.from.addOuterEdge(this); - this.to.addInnerEdge(this); - } + this.from.addOuterEdge(this); + this.to.addInnerEdge(this); + } - /** Removes edge from from and to nodes. - */ - deleteEdge() { - this.from.removeOuterEdge(this); - this.to.removeInnerEdge(this); - } + /** Removes edge from from and to nodes. + */ + deleteEdge() { + this.from.removeOuterEdge(this); + this.to.removeInnerEdge(this); + } - /** Compares Edge equallity. - * An edge is equal to another, if the from and to nodes are the same. - * @return {Boolean} - */ - isEqual(edge) { - return this.from.id == edge.from.id && this.to.id == edge.to.id; - } + /** Compares Edge equallity. + * An edge is equal to another, if the from and to nodes are the same. + * @returns {Boolean} + */ + isEqual(edge) { + return this.from.id == edge.from.id && this.to.id == edge.to.id; + } - toString() { - return `Edge { ${this.from.id} -> ${this.to.id} }`; - } + toString() { + return `Edge { ${this.from.id} -> ${this.to.id} }`; + } - /** Returns a LineString representation of the Edge - * @return {Feature} - */ - toLineString() { - return lineString([this.from.coordinates, this.to.coordinates]); - } + /** Returns a LineString representation of the Edge + * @returns {Feature} + */ + toLineString() { + return lineString([this.from.coordinates, this.to.coordinates]); + } - /** Comparator of two edges. - * Implementation of geos::planargraph::DirectedEdge::compareTo. - * - * @param {Edge} other - * @return -1 if this Edge has a greater angle with the positive x-axis than b, - * 0 if the Edges are colinear, - * 1 otherwise - */ - compareTo(edge) { - return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); - } + /** Comparator of two edges. + * Implementation of geos::planargraph::DirectedEdge::compareTo. + * + * @param {Edge} other + * @returns {Number} -1 if this Edge has a greater angle with the positive x-axis than b, + * 0 if the Edges are colinear, + * 1 otherwise + */ + compareTo(edge) { + return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); + } } module.exports = Edge; diff --git a/packages/turf-polygonize/src/EdgeRing.js b/packages/turf-polygonize/src/EdgeRing.js index 4041856b3d..8cb21965e5 100644 --- a/packages/turf-polygonize/src/EdgeRing.js +++ b/packages/turf-polygonize/src/EdgeRing.js @@ -1,7 +1,7 @@ -const { orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual } = require('./util'), - { multiPoint, polygon } = require('@turf/helpers'), - envelope = require('@turf/envelope'), - inside = require('@turf/inside'); +const {orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual} = require('./util'), + {multiPoint, polygon} = require('@turf/helpers'), + envelope = require('@turf/envelope'), + inside = require('@turf/inside'); /** Ring of edges which form a polygon. * The ring may be either an outer shell or a hole. @@ -9,110 +9,110 @@ const { orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual } * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing */ class EdgeRing extends Array { - /** Check if the ring is valid in geomtry terms. - * A ring must have either 0 or 4 or more points. The first and the last must be - * equal (in 2D) - * geos::geom::LinearRing::validateConstruction - * - * @return {Boolean} - */ - isValid() { - // TODO: stub - return true; - } + /** Check if the ring is valid in geomtry terms. + * A ring must have either 0 or 4 or more points. The first and the last must be + * equal (in 2D) + * geos::geom::LinearRing::validateConstruction + * + * @returns {Boolean} + */ + isValid() { + // TODO: stub + return true; + } - /** Tests whether this ring is a hole. - * A ring is a hole if it is oriented counter-clockwise. - * Similar implementation of geos::algorithm::CGAlgorithms::isCCW - * @return {Boolean} - true: if it is a hole - */ - isHole() { - // XXX: Assuming Ring is valid - // Find highest point - const hiIndex = this.reduce((high, edge, i) => { - if (edge.from.coordinates[1] > this[high].from.coordinates[1]) - high = i; - return high; - }, 0), - iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, - iNext = (hiIndex + 1) % this.length, - disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); + /** Tests whether this ring is a hole. + * A ring is a hole if it is oriented counter-clockwise. + * Similar implementation of geos::algorithm::CGAlgorithms::isCCW + * @returns {Boolean} - true: if it is a hole + */ + isHole() { + // XXX: Assuming Ring is valid + // Find highest point + const hiIndex = this.reduce((high, edge, i) => { + if (edge.from.coordinates[1] > this[high].from.coordinates[1]) + high = i; + return high; + }, 0), + iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, + iNext = (hiIndex + 1) % this.length, + disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); - if (disc == 0) - return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; - return disc > 0; - } + if (disc == 0) + return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; + return disc > 0; + } - /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). - * @return {Feature} - */ - toMultiPoint() { - return multiPoint(this.map(edge => edge.from.coordinates)); - } + /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). + * @returns {Feature} + */ + toMultiPoint() { + return multiPoint(this.map(edge => edge.from.coordinates)); + } - /** Creates a Polygon representing the EdgeRing. - * XXX: the polygon could be cached - * @return {Feature} - */ - toPolygon() { - const coordinates = this.map(edge => edge.from.coordinates); - coordinates.push(this[0].from.coordinates); - return polygon([coordinates]); - } + /** Creates a Polygon representing the EdgeRing. + * XXX: the polygon could be cached + * @returns {Feature} + */ + toPolygon() { + const coordinates = this.map(edge => edge.from.coordinates); + coordinates.push(this[0].from.coordinates); + return polygon([coordinates]); + } - /** Calculates the envelope of the EdgeRing. - * XXX: the envelope could be cached - * @return {Feature} - envelope - */ - getEnvelope() { - return envelope(this.toMultiPoint()); - } + /** Calculates the envelope of the EdgeRing. + * XXX: the envelope could be cached + * @returns {Feature} - envelope + */ + getEnvelope() { + return envelope(this.toMultiPoint()); + } - /** - * geos::operation::polygonize::EdgeRing::findEdgeRingContaining - * - * @param {EdgeRing} edgeRing - * @param {EdgeRing[]} shellList - * - * @return {EdgeRing} - */ - static findEdgeRingContaining(testEdgeRing, shellList) { - const testEnvelope = testEdgeRing.getEnvelope(); + /** + * geos::operation::polygonize::EdgeRing::findEdgeRingContaining + * + * @param {EdgeRing} edgeRing + * @param {EdgeRing[]} shellList + * + * @returns {EdgeRing} + */ + static findEdgeRingContaining(testEdgeRing, shellList) { + const testEnvelope = testEdgeRing.getEnvelope(); - let minEnvelope, - minShell; - shellList.forEach(shell => { - const tryEnvelope = shell.getEnvelope(); + let minEnvelope, + minShell; + shellList.forEach(shell => { + const tryEnvelope = shell.getEnvelope(); - if (minShell) - minEnvelope = minShell.getEnvelope(); + if (minShell) + minEnvelope = minShell.getEnvelope(); - // the hole envelope cannot equal the shell envelope - if (envelopeIsEqual(tryEnvelope, testEnvelope)) - return; + // the hole envelope cannot equal the shell envelope + if (envelopeIsEqual(tryEnvelope, testEnvelope)) + return; - if (envelopeContains(tryEnvelope, testEnvelope)) { - const testPoint = testEdgeRing.map(edge => edge.from.coordinates) - .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); + if (envelopeContains(tryEnvelope, testEnvelope)) { + const testPoint = testEdgeRing.map(edge => edge.from.coordinates) + .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); - if (testPoint && shell.inside(point(testPoint))) { - if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) - minShell = shell; - } - } - }); + if (testPoint && shell.inside(point(testPoint))) { + if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) + minShell = shell; + } + } + }); - return minShell; - } + return minShell; + } - /** Checks if the point is inside the edgeRing - * - * @param {Feature} point - * @return {Boolean} - */ - inside(point) { - return inside(point, this.toPolygon()); - } + /** Checks if the point is inside the edgeRing + * + * @param {Feature} point + * @returns {Boolean} + */ + inside(point) { + return inside(point, this.toPolygon()); + } } module.exports = EdgeRing; diff --git a/packages/turf-polygonize/src/EdgeRing.test.js b/packages/turf-polygonize/src/EdgeRing.test.js index c912ffdc24..2d223aceb8 100644 --- a/packages/turf-polygonize/src/EdgeRing.test.js +++ b/packages/turf-polygonize/src/EdgeRing.test.js @@ -1,22 +1,22 @@ const test = require('tape'), - EdgeRing = require('./EdgeRing'), - Edge = require('./Edge'), - Node = require('./Node'); + EdgeRing = require('./EdgeRing'), + Edge = require('./Edge'), + Node = require('./Node'); test('EdgeRing.isHole', t => { - let edgeRing = new EdgeRing(); - edgeRing.push(new Edge(new Node([0, 0]), new Node([1, 0]))); - edgeRing.push(new Edge(edgeRing[0].to, new Node([0, 1]))); - edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); + let edgeRing = new EdgeRing(); + edgeRing.push(new Edge(new Node([0, 0]), new Node([1, 0]))); + edgeRing.push(new Edge(edgeRing[0].to, new Node([0, 1]))); + edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); - t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); + t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); - edgeRing = new EdgeRing(); - edgeRing.push(new Edge(new Node([0, 0]), new Node([0, 1]))); - edgeRing.push(new Edge(edgeRing[0].to, new Node([1, 0]))); - edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); + edgeRing = new EdgeRing(); + edgeRing.push(new Edge(new Node([0, 0]), new Node([0, 1]))); + edgeRing.push(new Edge(edgeRing[0].to, new Node([1, 0]))); + edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); - t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); + t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); - t.end(); + t.end(); }); diff --git a/packages/turf-polygonize/src/Graph.js b/packages/turf-polygonize/src/Graph.js index 796f950797..7d816064a3 100644 --- a/packages/turf-polygonize/src/Graph.js +++ b/packages/turf-polygonize/src/Graph.js @@ -1,6 +1,6 @@ const Node = require('./Node'), - Edge = require('./Edge'), - EdgeRing = require('./EdgeRing'); + Edge = require('./Edge'), + EdgeRing = require('./EdgeRing'); /** Represents a planar graph of edges and nodes that can be used to compute a * polygonization. @@ -12,277 +12,277 @@ const Node = require('./Node'), * This graph is directed (both directions are created) */ class Graph { - /** Creates a graph from a GeoJSON. - * @param {FeatureCollection} geoJson - it must comply with the restrictions detailed in the index - * @return {Graph} - */ - static fromGeoJson(geoJson) { - const graph = new Graph(); - geoJson.features.forEach(feature => { - const start = graph.getNode(feature.geometry.coordinates[0]), - end = graph.getNode(feature.geometry.coordinates[1]); - - graph.addEdge(start, end); - }); - - return graph; - } - - /** Creates or get a Node. - * - * @param {Number[]} coordinates - * @return {Node} - */ - getNode(coordinates) { - const id = Node.buildId(coordinates); - let node = this.nodes[id]; - if (!node) - node = this.nodes[id] = new Node(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 - */ - addEdge(from, to) { - const edge = new Edge(from, to), - symetricEdge = edge.getSymetric(); - - this.edges.push(edge); - this.edges.push(symetricEdge); - } - - constructor() { - this.edges = []; //< {Edge[]} dirEdges - - // The key is the `id` of the Node (ie: coordinates.join(',')) - this.nodes = {}; - } - - /** Removes Dangle Nodes (nodes with grade 1). - */ - deleteDangles() { - Object.values(this.nodes) - .forEach(node => 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 - */ - _removeIfDangle(node) { - // As edges are directed and symetrical, we count only innerEdges - if (node.innerEdges.length <= 1) { - const outerNodes = node.outerEdges.map(e => e.to); - this.removeNode(node); - outerNodes.forEach(n => this._removeIfDangle(n)); + /** Creates a graph from a GeoJSON. + * @param {FeatureCollection} geoJson - it must comply with the restrictions detailed in the index + * @returns {Graph} + */ + static fromGeoJson(geoJson) { + const graph = new Graph(); + geoJson.features.forEach(feature => { + const start = graph.getNode(feature.geometry.coordinates[0]), + end = graph.getNode(feature.geometry.coordinates[1]); + + graph.addEdge(start, end); + }); + + return graph; } - } - - /** 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. - */ - deleteCutEdges() { - this._computeNextCWEdges(); - this._findLabeledEdgeRings(); - - // Cut-edges (bridges) are edges where both edges have the same label - this.edges.forEach(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 - */ - _computeNextCWEdges(node) { - if (typeof(node) == "undefined") - return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); - - node.outerEdges.forEach((edge, i) => { - node.outerEdges[(i === 0 ? node.outerEdges.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 - * @param {Number} label - */ - _computeNextCCWEdges(node, label) { - const edges = node.outerEdges; - let firstOutDE, - prevInDE; - - for (let i = edges.length - 1; i >= 0; --i) { - let de = edges[i], - sym = de.symetric, - outDE, - inDE; - - 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; + + /** Creates or get a Node. + * + * @param {Number[]} coordinates + * @returns {Node} + */ + getNode(coordinates) { + const id = Node.buildId(coordinates); + let node = this.nodes[id]; + if (!node) + node = this.nodes[id] = new Node(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 + */ + addEdge(from, to) { + const edge = new Edge(from, to), + symetricEdge = edge.getSymetric(); + + this.edges.push(edge); + this.edges.push(symetricEdge); + } + + constructor() { + this.edges = []; //< {Edge[]} dirEdges + + // The key is the `id` of the Node (ie: coordinates.join(',')) + this.nodes = {}; + } + + /** Removes Dangle Nodes (nodes with grade 1). + */ + deleteDangles() { + Object.values(this.nodes) + .forEach(node => 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 + */ + _removeIfDangle(node) { + // As edges are directed and symetrical, we count only innerEdges + if (node.innerEdges.length <= 1) { + const outerNodes = node.outerEdges.map(e => e.to); + this.removeNode(node); + outerNodes.forEach(n => 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. + */ + deleteCutEdges() { + this._computeNextCWEdges(); + this._findLabeledEdgeRings(); + + // Cut-edges (bridges) are edges where both edges have the same label + this.edges.forEach(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 + */ + _computeNextCWEdges(node) { + if (typeof(node) == "undefined") + return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + + node.outerEdges.forEach((edge, i) => { + node.outerEdges[(i === 0 ? node.outerEdges.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 + * @param {Number} label + */ + _computeNextCCWEdges(node, label) { + const edges = node.outerEdges; + let firstOutDE, + prevInDE; + + for (let i = edges.length - 1; i >= 0; --i) { + let de = edges[i], + sym = de.symetric, + outDE, + inDE; + + 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 (!firstOutDE) - firstOutDE = outDE; - } + if (prevInDE) + prevInDE.next = firstOutDE; } - 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. - * - * @return {Edge[]} edges that start rings - */ - _findLabeledEdgeRings() { - const edgeRingStarts = []; - let label = 0; - this.edges.forEach(edge => { - if (edge.label >= 0) - return; - - edgeRingStarts.push(edge); - - let 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. - * - * @return {EdgeRing[]} - */ - getEdgeRings() { - this._computeNextCWEdges(); - - // Clear labels - this.edges.forEach(edge => edge.label = undefined); - - this._findLabeledEdgeRings().forEach(edge => { - // convertMaximalToMinimalEdgeRings - this._findIntersectionNodes(edge).forEach(node => { - this._computeNextCCWEdges(node, edge.label) - }); - }); - - const edgeRingList = []; - - // find all edgerings - this.edges.forEach(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 - * @return {Node[]} - intersection nodes - */ - _findIntersectionNodes(startEdge) { - const intersectionNodes = []; - let edge = startEdge; - do { - // getDegree - let degree = 0; - edge.from.outerEdges.forEach(e => { - if (e.label == startEdge.label) - ++degree; - }); - - if (degree > 1) - intersectionNodes.push(edge.from); - - edge = edge.next; - } 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 - * @return {EdgeRing} - */ - _findEdgeRing(startEdge) { - let edge = startEdge; - const edgeRing = new EdgeRing(); - - 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 - */ - removeNode(node) { - node.outerEdges.forEach(edge => this.removeEdge(edge)); - node.innerEdges.forEach(edge => this.removeEdge(edge)); - delete this.nodes[node.id]; - } - - /** Remove edge from the graph and deletes the edge. - * - * @param {Edge} edge - */ - removeEdge(edge) { - this.edges = this.edges.filter(e => !e.isEqual(edge)); - edge.deleteEdge(); - } + + /** 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 + */ + _findLabeledEdgeRings() { + const edgeRingStarts = []; + let label = 0; + this.edges.forEach(edge => { + if (edge.label >= 0) + return; + + edgeRingStarts.push(edge); + + let 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[]} + */ + getEdgeRings() { + this._computeNextCWEdges(); + + // Clear labels + this.edges.forEach(edge => edge.label = undefined); + + this._findLabeledEdgeRings().forEach(edge => { + // convertMaximalToMinimalEdgeRings + this._findIntersectionNodes(edge).forEach(node => { + this._computeNextCCWEdges(node, edge.label) + }); + }); + + const edgeRingList = []; + + // find all edgerings + this.edges.forEach(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 + * @returns {Node[]} - intersection nodes + */ + _findIntersectionNodes(startEdge) { + const intersectionNodes = []; + let edge = startEdge; + do { + // getDegree + let degree = 0; + edge.from.outerEdges.forEach(e => { + if (e.label == startEdge.label) + ++degree; + }); + + if (degree > 1) + intersectionNodes.push(edge.from); + + edge = edge.next; + } 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} + */ + _findEdgeRing(startEdge) { + let edge = startEdge; + const edgeRing = new EdgeRing(); + + 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 + */ + removeNode(node) { + node.outerEdges.forEach(edge => this.removeEdge(edge)); + node.innerEdges.forEach(edge => this.removeEdge(edge)); + delete this.nodes[node.id]; + } + + /** Remove edge from the graph and deletes the edge. + * + * @param {Edge} edge + */ + removeEdge(edge) { + this.edges = this.edges.filter(e => !e.isEqual(edge)); + edge.deleteEdge(); + } } module.exports = Graph; diff --git a/packages/turf-polygonize/src/Graph.test.js b/packages/turf-polygonize/src/Graph.test.js index 71c484be26..06b071c274 100644 --- a/packages/turf-polygonize/src/Graph.test.js +++ b/packages/turf-polygonize/src/Graph.test.js @@ -1,86 +1,86 @@ const test = require('tape'), - Graph = require('./Graph'), - Node = require('./Node'), - { featureCollection, lineString } = require('@turf/helpers'); + Graph = require('./Graph'), + Node = require('./Node'), + {featureCollection, lineString} = require('@turf/helpers'); test('Graph :: fromGeoJson', t => { - const geoJson = featureCollection([ - lineString([[0, 1], [0, 0]]), - lineString([[1, 1], [0, 0]]), - lineString([[1, 0], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); + const geoJson = featureCollection([ + lineString([[0, 1], [0, 0]]), + lineString([[1, 1], [0, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); - t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); + t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); - // Edges are symetric - t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); + // Edges are symetric + t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); - t.end(); + t.end(); }); test('Graph :: deleteDangles', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [0, 2]]), - lineString([[0, 1], [1, 1]]), - lineString([[1, 1], [1, 0]]), - lineString([[1, 0], [0, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [0, 2]]), + lineString([[0, 1], [1, 1]]), + lineString([[1, 1], [1, 0]]), + lineString([[1, 0], [0, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); - graph.deleteDangles(); + graph.deleteDangles(); - t.equal(Object.keys(graph.nodes).length, 4); + t.equal(Object.keys(graph.nodes).length, 4); - t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); + t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); - t.end(); + t.end(); }); test('Graph :: deleteCutEdges', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - - t.equal(Object.keys(graph.nodes).length, 6); - t.equal(graph.edges.length, 12); - - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); - - t.end(); + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + + t.equal(Object.keys(graph.nodes).length, 6); + t.equal(graph.edges.length, 12); + + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); + t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); + + t.end(); }); test('Graph :: getEdgeRings', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - const edgeRings = graph.getEdgeRings(); - - t.equal(edgeRings.length, 4); - - edgeRings.forEach(edgeRing => { - t.equal(edgeRing.length, 3); - }); - - t.end(); + const geoJson = featureCollection([ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]), + ]), + graph = Graph.fromGeoJson(geoJson); + + graph.deleteCutEdges(); + const edgeRings = graph.getEdgeRings(); + + t.equal(edgeRings.length, 4); + + edgeRings.forEach(edgeRing => { + t.equal(edgeRing.length, 3); + }); + + t.end(); }); diff --git a/packages/turf-polygonize/src/Node.js b/packages/turf-polygonize/src/Node.js index f42ec1fe57..0b75ee7603 100644 --- a/packages/turf-polygonize/src/Node.js +++ b/packages/turf-polygonize/src/Node.js @@ -1,66 +1,66 @@ -const { orientationIndex } = require('./util'); +const {orientationIndex} = require('./util'); class Node { - static buildId(coordinates) { - return coordinates.join(','); - } + static buildId(coordinates) { + return coordinates.join(','); + } - constructor(coordinates) { - this.id = Node.buildId(coordinates); - this.coordinates = coordinates; //< Number[] - this.innerEdges = []; //< Edge[] + constructor(coordinates) { + this.id = Node.buildId(coordinates); + this.coordinates = coordinates; //< Number[] + this.innerEdges = []; //< Edge[] - // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does - this.outerEdges = []; //< Edge[] - } + // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does + this.outerEdges = []; //< Edge[] + } - removeInnerEdge(edge) { - this.innerEdges = this.innerEdges.filter(e => e.from.id != edge.from.id); - } + removeInnerEdge(edge) { + this.innerEdges = this.innerEdges.filter(e => e.from.id !== edge.from.id); + } - removeOuterEdge(edge) { - this.outerEdges = this.outerEdges.filter(e => e.to.id != edge.to.id); - } + removeOuterEdge(edge) { + this.outerEdges = this.outerEdges.filter(e => e.to.id !== edge.to.id); + } - /** Outer edges are stored CCW order. - * XXX: on each add we are ordering, this could be optimized - * @param {Edge} edge - */ - addOuterEdge(edge) { - this.outerEdges.push(edge); - //this.outerEdges.sort((a, b) => a.compareTo(b)); - // Using this comparator in order to be deterministic - this.outerEdges.sort((a, b) => { - const aNode = a.to, - bNode = b.to; + /** Outer edges are stored CCW order. + * XXX: on each add we are ordering, this could be optimized + * @param {Edge} edge - Edge to add as an outerEdge. + */ + addOuterEdge(edge) { + this.outerEdges.push(edge); + //this.outerEdges.sort((a, b) => a.compareTo(b)); + // Using this comparator in order to be deterministic + this.outerEdges.sort((a, b) => { + const aNode = a.to, + bNode = b.to; - if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) - return 1; - if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) - return -1; + if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) + return 1; + if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) + return -1; - if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { - if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) - return aNode.coordinates[1] - bNode.coordinates[1]; - return bNode.coordinates[1] - aNode.coordinates[1]; - } + if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { + if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) + return aNode.coordinates[1] - bNode.coordinates[1]; + return bNode.coordinates[1] - aNode.coordinates[1]; + } - const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); - if (det < 0) - return 1; - if (det > 0) - return -1; + const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); + if (det < 0) + return 1; + if (det > 0) + return -1; - const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), - d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); + const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), + d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); - return d1 - d2; - }); - } + return d1 - d2; + }); + } - addInnerEdge(edge) { - this.innerEdges.push(edge); - } + addInnerEdge(edge) { + this.innerEdges.push(edge); + } } module.exports = Node; diff --git a/packages/turf-polygonize/src/Node.test.js b/packages/turf-polygonize/src/Node.test.js index 69e1c06b38..7c4f2839cf 100644 --- a/packages/turf-polygonize/src/Node.test.js +++ b/packages/turf-polygonize/src/Node.test.js @@ -1,25 +1,25 @@ const test = require('tape'), - Node = require('./Node'), - Edge = require('./Edge'); + Node = require('./Node'), + Edge = require('./Edge'); test('Node.outerEdges CCW order', t => { - const center = new Node([0, 0]), - addNode = c => new Edge(center, new Node(c)); + const center = new Node([0, 0]), + addNode = c => new Edge(center, new Node(c)); - addNode([0, 1]); - addNode([1, 1]); - addNode([1, 0]); - addNode([1, -1]); - addNode([0, -1]); - addNode([-1, -1]); - addNode([-1, 0]); - addNode([-1, 1]); + addNode([0, 1]); + addNode([1, 1]); + addNode([1, 0]); + addNode([1, -1]); + addNode([0, -1]); + addNode([-1, -1]); + addNode([-1, 0]); + addNode([-1, 1]); - t.deepEqual( - center.outerEdges.map(e => e.to.coordinates), - [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], - 'Outernodes have to be in CCW order' - ); + t.deepEqual( + center.outerEdges.map(e => e.to.coordinates), + [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], + 'Outernodes have to be in CCW order' + ); - t.end(); + t.end(); }); From 2c9eddc2034c41962ce47139e70336c0f1ed4cef Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 22:57:22 -0300 Subject: [PATCH 16/36] Lint: fixed src/Graph.test.js --- packages/turf-polygonize/src/Graph.test.js | 74 ++++++++++++---------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/packages/turf-polygonize/src/Graph.test.js b/packages/turf-polygonize/src/Graph.test.js index 06b071c274..a4e18f0bbc 100644 --- a/packages/turf-polygonize/src/Graph.test.js +++ b/packages/turf-polygonize/src/Graph.test.js @@ -4,11 +4,13 @@ const test = require('tape'), {featureCollection, lineString} = require('@turf/helpers'); test('Graph :: fromGeoJson', t => { - const geoJson = featureCollection([ - lineString([[0, 1], [0, 0]]), - lineString([[1, 1], [0, 0]]), - lineString([[1, 0], [0, 0]]), - ]), + const geoJson = featureCollection( + [ + lineString([[0, 1], [0, 0]]), + lineString([[1, 1], [0, 0]]), + lineString([[1, 0], [0, 0]]) + ] + ), graph = Graph.fromGeoJson(geoJson); t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); @@ -20,34 +22,38 @@ test('Graph :: fromGeoJson', t => { }); test('Graph :: deleteDangles', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [0, 2]]), - lineString([[0, 1], [1, 1]]), - lineString([[1, 1], [1, 0]]), - lineString([[1, 0], [0, 0]]), - ]), + const geoJson = featureCollection( + [ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [0, 2]]), + lineString([[0, 1], [1, 1]]), + lineString([[1, 1], [1, 0]]), + lineString([[1, 0], [0, 0]]) + ] + ), graph = Graph.fromGeoJson(geoJson); graph.deleteDangles(); t.equal(Object.keys(graph.nodes).length, 4); - t.notOk(graph.nodes[Node.buildId([0,2])], "Dangle node has to be removed"); + t.notOk(graph.nodes[Node.buildId([0, 2])], 'Dangle node has to be removed'); t.end(); }); test('Graph :: deleteCutEdges', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), + const geoJson = featureCollection( + [ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]) + ] + ), graph = Graph.fromGeoJson(geoJson); graph.deleteCutEdges(); @@ -55,22 +61,24 @@ test('Graph :: deleteCutEdges', t => { t.equal(Object.keys(graph.nodes).length, 6); t.equal(graph.edges.length, 12); - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([1, 1]) && e.from.id == Node.buildId([2, 1]))); - t.notOk(graph.edges.find(e => e.to.id == Node.buildId([2, 1]) && e.from.id == Node.buildId([1, 1]))); + t.notOk(graph.edges.find(e => e.to.id === Node.buildId([1, 1]) && e.from.id === Node.buildId([2, 1]))); + t.notOk(graph.edges.find(e => e.to.id === Node.buildId([2, 1]) && e.from.id === Node.buildId([1, 1]))); t.end(); }); test('Graph :: getEdgeRings', t => { - const geoJson = featureCollection([ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]), - ]), + const geoJson = featureCollection( + [ + lineString([[0, 0], [0, 1]]), + lineString([[0, 1], [1, 1]]), + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [2, 1]]), + lineString([[2, 1], [3, 1]]), + lineString([[3, 1], [3, 0]]), + lineString([[2, 1], [3, 0]]) + ] + ), graph = Graph.fromGeoJson(geoJson); graph.deleteCutEdges(); From af3c0beb903c7d4e1bff2e7dd1e912341b1a86e2 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 23:05:59 -0300 Subject: [PATCH 17/36] Lind: Fixed src/Graph.js --- packages/turf-polygonize/src/Graph.js | 55 ++++++++++++++------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/turf-polygonize/src/Graph.js b/packages/turf-polygonize/src/Graph.js index 7d816064a3..b42fe4d968 100644 --- a/packages/turf-polygonize/src/Graph.js +++ b/packages/turf-polygonize/src/Graph.js @@ -14,7 +14,7 @@ const Node = require('./Node'), class Graph { /** Creates a graph from a GeoJSON. * @param {FeatureCollection} geoJson - it must comply with the restrictions detailed in the index - * @returns {Graph} + * @returns {Graph} - The newly created graph */ static fromGeoJson(geoJson) { const graph = new Graph(); @@ -30,8 +30,8 @@ class Graph { /** Creates or get a Node. * - * @param {Number[]} coordinates - * @returns {Node} + * @param {Number[]} coordinates - Coordinates of the node + * @returns {Node} - The created or stored node */ getNode(coordinates) { const id = Node.buildId(coordinates); @@ -73,7 +73,7 @@ class Graph { /** 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 + * @param {Node} node - Node to check if it's a dangle */ _removeIfDangle(node) { // As edges are directed and symetrical, we count only innerEdges @@ -96,7 +96,7 @@ class Graph { // Cut-edges (bridges) are edges where both edges have the same label this.edges.forEach(edge => { - if (edge.label == edge.symetric.label) { + if (edge.label === edge.symetric.label) { this.removeEdge(edge.symetric); this.removeEdge(edge); } @@ -110,12 +110,13 @@ class Graph { * @param {Node} [node] - If no node is passed, the function calls itself for every node in the Graph */ _computeNextCWEdges(node) { - if (typeof(node) == "undefined") - return Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); - - node.outerEdges.forEach((edge, i) => { - node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1].symetric.next = edge; - }); + if (typeof node === 'undefined') { + Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); + } else { + node.outerEdges.forEach((edge, i) => { + node.outerEdges[(i === 0 ? node.outerEdges.length : i) - 1].symetric.next = edge; + }); + } } /** Computes the next edge pointers going CCW around the given node, for the given edgering label. @@ -124,8 +125,8 @@ class Graph { * XXX: method literally transcribed from `geos::operation::polygonize::PolygonizeGraph::computeNextCCWEdges`, * could be written in a more javascript way. * - * @param {Node} node - * @param {Number} label + * @param {Node} node - Node + * @param {Number} label - Ring's label */ _computeNextCCWEdges(node, label) { const edges = node.outerEdges; @@ -138,10 +139,10 @@ class Graph { outDE, inDE; - if (de.label == label) + if (de.label === label) outDE = de; - if (sym.label == label) + if (sym.label === label) inDE = sym; if (!outDE || !inDE) // This edge is not in edgering @@ -152,7 +153,7 @@ class Graph { if (outDE) { if (prevInDE) { - prevInDE.next = outDE + prevInDE.next = outDE; prevInDE = undefined; } @@ -194,18 +195,20 @@ class Graph { /** Computes the EdgeRings formed by the edges in this graph. * - * @returns {EdgeRing[]} + * @returns {EdgeRing[]} - A list of all the EdgeRings in the graph. */ getEdgeRings() { this._computeNextCWEdges(); // Clear labels - this.edges.forEach(edge => edge.label = undefined); + this.edges.forEach(edge => { + edge.label = undefined; + }); this._findLabeledEdgeRings().forEach(edge => { // convertMaximalToMinimalEdgeRings this._findIntersectionNodes(edge).forEach(node => { - this._computeNextCCWEdges(node, edge.label) + this._computeNextCCWEdges(node, edge.label); }); }); @@ -223,7 +226,7 @@ class Graph { /** Find all nodes in a Maxima EdgeRing which are self-intersection nodes. * - * @param {Node} startEdge + * @param {Node} startEdge - Start Edge of the Ring * @returns {Node[]} - intersection nodes */ _findIntersectionNodes(startEdge) { @@ -233,7 +236,7 @@ class Graph { // getDegree let degree = 0; edge.from.outerEdges.forEach(e => { - if (e.label == startEdge.label) + if (e.label === startEdge.label) ++degree; }); @@ -241,7 +244,7 @@ class Graph { intersectionNodes.push(edge.from); edge = edge.next; - } while(!startEdge.isEqual(edge)); + } while (!startEdge.isEqual(edge)); return intersectionNodes; } @@ -249,7 +252,7 @@ class Graph { /** Get the edge-ring which starts from the provided Edge. * * @param {Edge} startEdge - starting edge of the edge ring - * @returns {EdgeRing} + * @returns {EdgeRing} - EdgeRing which start Edge is the provided one. */ _findEdgeRing(startEdge) { let edge = startEdge; @@ -259,7 +262,7 @@ class Graph { edgeRing.push(edge); edge.ring = edgeRing; edge = edge.next; - } while(!startEdge.isEqual(edge)); + } while (!startEdge.isEqual(edge)); return edgeRing; } @@ -267,7 +270,7 @@ class Graph { /** Removes a node from the Graph. * * It also removes edges asociated to that node - * @param {Node} node + * @param {Node} node - Node to be removed */ removeNode(node) { node.outerEdges.forEach(edge => this.removeEdge(edge)); @@ -277,7 +280,7 @@ class Graph { /** Remove edge from the graph and deletes the edge. * - * @param {Edge} edge + * @param {Edge} edge - Edge to be removed */ removeEdge(edge) { this.edges = this.edges.filter(e => !e.isEqual(edge)); From ee3196964a53df52bf2d69907d6f8c6b142deaa6 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 23:13:45 -0300 Subject: [PATCH 18/36] Lint: Fixed src/EdgeRing --- packages/turf-polygonize/src/EdgeRing.js | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/turf-polygonize/src/EdgeRing.js b/packages/turf-polygonize/src/EdgeRing.js index 8cb21965e5..19bcadd83f 100644 --- a/packages/turf-polygonize/src/EdgeRing.js +++ b/packages/turf-polygonize/src/EdgeRing.js @@ -1,5 +1,5 @@ const {orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual} = require('./util'), - {multiPoint, polygon} = require('@turf/helpers'), + {multiPoint, polygon, point} = require('@turf/helpers'), envelope = require('@turf/envelope'), inside = require('@turf/inside'); @@ -14,7 +14,7 @@ class EdgeRing extends Array { * equal (in 2D) * geos::geom::LinearRing::validateConstruction * - * @returns {Boolean} + * @returns {Boolean} - Validity of the EdgeRing */ isValid() { // TODO: stub @@ -30,21 +30,21 @@ class EdgeRing extends Array { // XXX: Assuming Ring is valid // Find highest point const hiIndex = this.reduce((high, edge, i) => { - if (edge.from.coordinates[1] > this[high].from.coordinates[1]) - high = i; - return high; - }, 0), - iPrev = (hiIndex == 0 ? this.length : hiIndex) -1, + if (edge.from.coordinates[1] > this[high].from.coordinates[1]) + high = i; + return high; + }, 0), + iPrev = (hiIndex === 0 ? this.length : hiIndex) - 1, iNext = (hiIndex + 1) % this.length, disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); - if (disc == 0) + if (disc === 0) return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; return disc > 0; } /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). - * @returns {Feature} + * @returns {Feature} - Multipoint representation of the EdgeRing */ toMultiPoint() { return multiPoint(this.map(edge => edge.from.coordinates)); @@ -52,7 +52,7 @@ class EdgeRing extends Array { /** Creates a Polygon representing the EdgeRing. * XXX: the polygon could be cached - * @returns {Feature} + * @returns {Feature} - Polygon representation of the Edge Ring */ toPolygon() { const coordinates = this.map(edge => edge.from.coordinates); @@ -69,12 +69,12 @@ class EdgeRing extends Array { } /** - * geos::operation::polygonize::EdgeRing::findEdgeRingContaining + * `geos::operation::polygonize::EdgeRing::findEdgeRingContaining` * - * @param {EdgeRing} edgeRing - * @param {EdgeRing[]} shellList + * @param {EdgeRing} testEdgeRing - EdgeRing to look in the list + * @param {EdgeRing[]} shellList - List of EdgeRing in which to search * - * @returns {EdgeRing} + * @returns {EdgeRing} - EdgeRing which contains the testEdgeRing */ static findEdgeRingContaining(testEdgeRing, shellList) { const testEnvelope = testEdgeRing.getEnvelope(); @@ -107,8 +107,8 @@ class EdgeRing extends Array { /** Checks if the point is inside the edgeRing * - * @param {Feature} point - * @returns {Boolean} + * @param {Feature} point - Point to check if it is inside the edgeRing + * @returns {Boolean} - True if it is inside, False otherwise */ inside(point) { return inside(point, this.toPolygon()); From 8769bda099462b768988b57dd9ec32fe3be47fbb Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 23:17:25 -0300 Subject: [PATCH 19/36] Lint: fixed src/Edge --- packages/turf-polygonize/src/Edge.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/turf-polygonize/src/Edge.js b/packages/turf-polygonize/src/Edge.js index 68630e3d3a..2ea3033045 100644 --- a/packages/turf-polygonize/src/Edge.js +++ b/packages/turf-polygonize/src/Edge.js @@ -6,10 +6,10 @@ const {lineString} = require('@turf/helpers'), class Edge { /** Creates or get the symetric Edge. * - * @returns {Edge} + * @returns {Edge} - Symetric Edge. */ getSymetric() { - if (! this.symetric) { + if (!this.symetric) { this.symetric = new Edge(this.to, this.from); this.symetric.symetric = this; } @@ -43,10 +43,12 @@ class Edge { /** Compares Edge equallity. * An edge is equal to another, if the from and to nodes are the same. - * @returns {Boolean} + * + * @param {Edge} edge - Another Edge + * @returns {Boolean} - True if Edges are equal, False otherwise */ isEqual(edge) { - return this.from.id == edge.from.id && this.to.id == edge.to.id; + return this.from.id === edge.from.id && this.to.id === edge.to.id; } toString() { @@ -54,7 +56,8 @@ class Edge { } /** Returns a LineString representation of the Edge - * @returns {Feature} + * + * @returns {Feature} - LineString representation of the Edge */ toLineString() { return lineString([this.from.coordinates, this.to.coordinates]); @@ -63,7 +66,7 @@ class Edge { /** Comparator of two edges. * Implementation of geos::planargraph::DirectedEdge::compareTo. * - * @param {Edge} other + * @param {Edge} edge - Another edge to compare with this one * @returns {Number} -1 if this Edge has a greater angle with the positive x-axis than b, * 0 if the Edges are colinear, * 1 otherwise From 92ffec8dc3023aa3031d19fc073ecb513a01c649 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 23:19:11 -0300 Subject: [PATCH 20/36] Lint: Fixed index.js --- packages/turf-polygonize/index.js | 54 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 629ec3f802..a7e2729423 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,6 +1,6 @@ const Graph = require('./src/Graph'), - EdgeRing = require('./src/EdgeRing'), - { featureCollection } = require('@turf/helpers'); + EdgeRing = require('./src/EdgeRing'), + {featureCollection} = require('@turf/helpers'); /** Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * @@ -12,39 +12,39 @@ const Graph = require('./src/Graph'), * * - Dangles: edges which have one or both ends which are not incident on another edge endpoint. * - Cut Edges (bridges): edges that are connected at both ends but which do not form part - * of a polygon. + * of a polygon. * * @param {FeatureCollection} geoJson - Lines in order to polygonize - * @returns {FeatureCollection} + * @returns {FeatureCollection} - Polygons created */ module.exports = function polygonize(geoJson) { - const graph = Graph.fromGeoJson(geoJson); + const graph = Graph.fromGeoJson(geoJson); - // 1. Remove dangle node - graph.deleteDangles(); + // 1. Remove dangle node + graph.deleteDangles(); - // 2. Remove cut-edges (bridge edges) - graph.deleteCutEdges(); + // 2. Remove cut-edges (bridge edges) + graph.deleteCutEdges(); - // 3. Get all holes and shells - const holes = [], - shells = []; + // 3. Get all holes and shells + const holes = [], + shells = []; - graph.getEdgeRings() - .filter(edgeRing => edgeRing.isValid()) - .forEach(edgeRing => { - if (edgeRing.isHole()) - holes.push(edgeRing); - else - shells.push(edgeRing); - }); + graph.getEdgeRings() + .filter(edgeRing => edgeRing.isValid()) + .forEach(edgeRing => { + if (edgeRing.isHole()) + holes.push(edgeRing); + else + shells.push(edgeRing); + }); - // 4. Assign Holes to Shells - holes.forEach(hole => { - if (EdgeRing.findEdgeRingContaining(hole, shells)) - shells.push(hole); - }); + // 4. Assign Holes to Shells + holes.forEach(hole => { + if (EdgeRing.findEdgeRingContaining(hole, shells)) + shells.push(hole); + }); - // 5. EdgeRings to Polygons - return featureCollection(shells.map(shell => shell.toPolygon())); + // 5. EdgeRings to Polygons + return featureCollection(shells.map(shell => shell.toPolygon())); }; From 3ef8f798d35b366d9983146ece01a244530596e5 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sat, 27 May 2017 23:19:36 -0300 Subject: [PATCH 21/36] Updated Readme --- packages/turf-polygonize/README.md | 70 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/packages/turf-polygonize/README.md b/packages/turf-polygonize/README.md index 911766ff1b..c1e43ef96d 100644 --- a/packages/turf-polygonize/README.md +++ b/packages/turf-polygonize/README.md @@ -12,13 +12,13 @@ The implementation correctly handles: - Dangles: edges which have one or both ends which are not incident on another edge endpoint. - Cut Edges (bridges): edges that are connected at both ends but which do not form part - of a polygon. + of a polygon. **Parameters** - `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** Lines in order to polygonize -Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** +Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygons created # Graph @@ -37,9 +37,9 @@ Creates or get a Node. **Parameters** -- `coordinates` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** +- `coordinates` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Coordinates of the node -Returns **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** +Returns **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** The created or stored node ## addEdge @@ -62,7 +62,7 @@ It calls itself recursively, removing a dangling node might cause another dangli **Parameters** -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node to check if it's a dangle ## deleteCutEdges @@ -92,8 +92,8 @@ could be written in a more javascript way. **Parameters** -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** -- `label` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node +- `label` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Ring's label ## \_findLabeledEdgeRings @@ -106,7 +106,7 @@ Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere Computes the EdgeRings formed by the edges in this graph. -Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** +Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** A list of all the EdgeRings in the graph. ## \_findIntersectionNodes @@ -114,7 +114,7 @@ Find all nodes in a Maxima EdgeRing which are self-intersection nodes. **Parameters** -- `startEdge` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** +- `startEdge` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Start Edge of the Ring Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)>** intersection nodes @@ -126,7 +126,7 @@ Get the edge-ring which starts from the provided Edge. - `startEdge` **[Edge](#edge)** starting edge of the edge ring -Returns **[EdgeRing](#edgering)** +Returns **[EdgeRing](#edgering)** EdgeRing which start Edge is the provided one. ## removeNode @@ -136,7 +136,7 @@ It also removes edges asociated to that node **Parameters** -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** +- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node to be removed ## removeEdge @@ -144,7 +144,7 @@ Remove edge from the graph and deletes the edge. **Parameters** -- `edge` **[Edge](#edge)** +- `edge` **[Edge](#edge)** Edge to be removed ## fromGeoJson @@ -154,7 +154,7 @@ Creates a graph from a GeoJSON. - `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** it must comply with the restrictions detailed in the index -Returns **[Graph](#graph)** +Returns **[Graph](#graph)** The newly created graph # addOuterEdge @@ -163,7 +163,7 @@ XXX: on each add we are ordering, this could be optimized **Parameters** -- `edge` **[Edge](#edge)** +- `edge` **[Edge](#edge)** Edge to add as an outerEdge. # Edge @@ -173,7 +173,7 @@ This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirected Creates or get the symetric Edge. -Returns **[Edge](#edge)** +Returns **[Edge](#edge)** Symetric Edge. ## constructor @@ -193,15 +193,15 @@ An edge is equal to another, if the from and to nodes are the same. **Parameters** -- `edge` +- `edge` **[Edge](#edge)** Another Edge -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if Edges are equal, False otherwise ## toLineString Returns a LineString representation of the Edge -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** LineString representation of the Edge ## compareTo @@ -210,12 +210,11 @@ Implementation of geos::planargraph::DirectedEdge::compareTo. **Parameters** -- `other` **[Edge](#edge)** -- `edge` +- `edge` **[Edge](#edge)** Another edge to compare with this one -Returns **Any** \-1 if this Edge has a greater angle with the positive x-axis than b, - 0 if the Edges are colinear, - 1 otherwise +Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** \-1 if this Edge has a greater angle with the positive x-axis than b, + 0 if the Edges are colinear, + 1 otherwise # EdgeRing @@ -233,7 +232,7 @@ A ring must have either 0 or 4 or more points. The first and the last must be equal (in 2D) geos::geom::LinearRing::validateConstruction -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Validity of the EdgeRing ## isHole @@ -247,14 +246,14 @@ Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe Creates a MultiPoint representing the EdgeRing (discarts edges directions). -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[MultiPoint](http://geojson.org/geojson-spec.html#multipoint)>** +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[MultiPoint](http://geojson.org/geojson-spec.html#multipoint)>** Multipoint representation of the EdgeRing ## toPolygon Creates a Polygon representing the EdgeRing. XXX: the polygon could be cached -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** +Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygon representation of the Edge Ring ## getEnvelope @@ -269,21 +268,20 @@ Checks if the point is inside the edgeRing **Parameters** -- `point` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Point](http://geojson.org/geojson-spec.html#point)>** +- `point` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Point](http://geojson.org/geojson-spec.html#point)>** Point to check if it is inside the edgeRing -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if it is inside, False otherwise ## findEdgeRingContaining -geos::operation::polygonize::EdgeRing::findEdgeRingContaining +`geos::operation::polygonize::EdgeRing::findEdgeRingContaining` **Parameters** -- `edgeRing` **[EdgeRing](#edgering)** -- `testEdgeRing` -- `shellList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** +- `testEdgeRing` **[EdgeRing](#edgering)** EdgeRing to look in the list +- `shellList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** List of EdgeRing in which to search -Returns **[EdgeRing](#edgering)** +Returns **[EdgeRing](#edgering)** EdgeRing which contains the testEdgeRing # orientationIndex @@ -297,7 +295,7 @@ Implementation of geos::algorithm::CGAlgorithm::orientationIndex() - `p2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the final point of the vector - `q` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the point to compute the direction to -Returns **Any** 1 if q is ccw (left) from p1->p2, +Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 1 if q is ccw (left) from p1->p2, \-1 if q is cw (right) from p1->p2, 0 if q is colinear with p1->p2 @@ -333,8 +331,8 @@ Checks if two coordinates are equal. **Parameters** -- `coord1` -- `coord2` +- `coord1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** First coordinate +- `coord2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Second coordinate Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if coordinates are equal From 4557a0e75694260e6f6ea694826be0ed1e52759e Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 18:49:51 -0300 Subject: [PATCH 22/36] Moved polygonize to other package --- packages/turf-polygonize/index.js | 36 +-- packages/turf-polygonize/package.json | 7 +- packages/turf-polygonize/src/Edge.js | 79 ----- packages/turf-polygonize/src/EdgeRing.js | 118 ------- packages/turf-polygonize/src/EdgeRing.test.js | 22 -- packages/turf-polygonize/src/Graph.js | 291 ------------------ packages/turf-polygonize/src/Graph.test.js | 94 ------ packages/turf-polygonize/src/Node.js | 66 ---- packages/turf-polygonize/src/Node.test.js | 25 -- packages/turf-polygonize/src/util.js | 72 ----- 10 files changed, 5 insertions(+), 805 deletions(-) delete mode 100644 packages/turf-polygonize/src/Edge.js delete mode 100644 packages/turf-polygonize/src/EdgeRing.js delete mode 100644 packages/turf-polygonize/src/EdgeRing.test.js delete mode 100644 packages/turf-polygonize/src/Graph.js delete mode 100644 packages/turf-polygonize/src/Graph.test.js delete mode 100644 packages/turf-polygonize/src/Node.js delete mode 100644 packages/turf-polygonize/src/Node.test.js delete mode 100644 packages/turf-polygonize/src/util.js diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index a7e2729423..a9252e75c7 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,6 +1,4 @@ -const Graph = require('./src/Graph'), - EdgeRing = require('./src/EdgeRing'), - {featureCollection} = require('@turf/helpers'); +var polygonize = require('polygonize'); /** Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * @@ -17,34 +15,4 @@ const Graph = require('./src/Graph'), * @param {FeatureCollection} geoJson - Lines in order to polygonize * @returns {FeatureCollection} - Polygons created */ -module.exports = function polygonize(geoJson) { - const graph = Graph.fromGeoJson(geoJson); - - // 1. Remove dangle node - graph.deleteDangles(); - - // 2. Remove cut-edges (bridge edges) - graph.deleteCutEdges(); - - // 3. Get all holes and shells - const holes = [], - shells = []; - - graph.getEdgeRings() - .filter(edgeRing => edgeRing.isValid()) - .forEach(edgeRing => { - if (edgeRing.isHole()) - holes.push(edgeRing); - else - shells.push(edgeRing); - }); - - // 4. Assign Holes to Shells - holes.forEach(hole => { - if (EdgeRing.findEdgeRingContaining(hole, shells)) - shells.push(hole); - }); - - // 5. EdgeRings to Polygons - return featureCollection(shells.map(shell => shell.toPolygon())); -}; +module.exports = polygonize; diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index 2bb50e437d..38ab9ee02d 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -4,7 +4,7 @@ "description": "turf polygonize module", "main": "index.js", "scripts": { - "test": "tape src/*.test.js test.js" + "test": "tape test.js" }, "repository": { "type": "git", @@ -26,11 +26,10 @@ }, "homepage": "https://github.com/Turfjs/turf#readme", "devDependencies": { + "@turf/helpers": "^4.3.0", "tape": "^4.6.3" }, "dependencies": { - "@turf/envelope": "^4.3.0", - "@turf/helpers": "^4.3.0", - "@turf/inside": "^4.3.0" + "polygonize": "^1.0.0" } } diff --git a/packages/turf-polygonize/src/Edge.js b/packages/turf-polygonize/src/Edge.js deleted file mode 100644 index 2ea3033045..0000000000 --- a/packages/turf-polygonize/src/Edge.js +++ /dev/null @@ -1,79 +0,0 @@ -const {lineString} = require('@turf/helpers'), - {orientationIndex} = require('./util'); - -/** This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge - */ -class Edge { - /** Creates or get the symetric Edge. - * - * @returns {Edge} - Symetric Edge. - */ - getSymetric() { - if (!this.symetric) { - this.symetric = new Edge(this.to, this.from); - this.symetric.symetric = this; - } - - return this.symetric; - } - - /** - * @param {Node} from - start node of the Edge - * @param {Node} to - end node of the edge - */ - constructor(from, to) { - this.from = from; //< start - this.to = to; //< End - - this.next = undefined; //< The edge to be computed after - this.label = undefined; //< Used in order to detect Cut Edges (Bridges) - this.symetric = undefined; //< The symetric edge of this - this.ring = undefined; //< EdgeRing in which the Edge is - - this.from.addOuterEdge(this); - this.to.addInnerEdge(this); - } - - /** Removes edge from from and to nodes. - */ - deleteEdge() { - this.from.removeOuterEdge(this); - this.to.removeInnerEdge(this); - } - - /** Compares Edge equallity. - * An edge is equal to another, if the from and to nodes are the same. - * - * @param {Edge} edge - Another Edge - * @returns {Boolean} - True if Edges are equal, False otherwise - */ - isEqual(edge) { - return this.from.id === edge.from.id && this.to.id === edge.to.id; - } - - toString() { - return `Edge { ${this.from.id} -> ${this.to.id} }`; - } - - /** Returns a LineString representation of the Edge - * - * @returns {Feature} - LineString representation of the Edge - */ - toLineString() { - return lineString([this.from.coordinates, this.to.coordinates]); - } - - /** Comparator of two edges. - * Implementation of geos::planargraph::DirectedEdge::compareTo. - * - * @param {Edge} edge - Another edge to compare with this one - * @returns {Number} -1 if this Edge has a greater angle with the positive x-axis than b, - * 0 if the Edges are colinear, - * 1 otherwise - */ - compareTo(edge) { - return orientationIndex(edge.from.coordinates, edge.to.coordinates, this.to.coordinates); - } -} - -module.exports = Edge; diff --git a/packages/turf-polygonize/src/EdgeRing.js b/packages/turf-polygonize/src/EdgeRing.js deleted file mode 100644 index 19bcadd83f..0000000000 --- a/packages/turf-polygonize/src/EdgeRing.js +++ /dev/null @@ -1,118 +0,0 @@ -const {orientationIndex, envelopeIsEqual, envelopeContains, coordinatesEqual} = require('./util'), - {multiPoint, polygon, point} = require('@turf/helpers'), - envelope = require('@turf/envelope'), - inside = require('@turf/inside'); - -/** Ring of edges which form a polygon. - * The ring may be either an outer shell or a hole. - * - * This class is inspired in GEOS's geos::operation::polygonize::EdgeRing - */ -class EdgeRing extends Array { - /** Check if the ring is valid in geomtry terms. - * A ring must have either 0 or 4 or more points. The first and the last must be - * equal (in 2D) - * geos::geom::LinearRing::validateConstruction - * - * @returns {Boolean} - Validity of the EdgeRing - */ - isValid() { - // TODO: stub - return true; - } - - /** Tests whether this ring is a hole. - * A ring is a hole if it is oriented counter-clockwise. - * Similar implementation of geos::algorithm::CGAlgorithms::isCCW - * @returns {Boolean} - true: if it is a hole - */ - isHole() { - // XXX: Assuming Ring is valid - // Find highest point - const hiIndex = this.reduce((high, edge, i) => { - if (edge.from.coordinates[1] > this[high].from.coordinates[1]) - high = i; - return high; - }, 0), - iPrev = (hiIndex === 0 ? this.length : hiIndex) - 1, - iNext = (hiIndex + 1) % this.length, - disc = orientationIndex(this[iPrev].from.coordinates, this[hiIndex].from.coordinates, this[iNext].from.coordinates); - - if (disc === 0) - return this[iPrev].from.coordinates[0] > this[iNext].from.coordinates[0]; - return disc > 0; - } - - /** Creates a MultiPoint representing the EdgeRing (discarts edges directions). - * @returns {Feature} - Multipoint representation of the EdgeRing - */ - toMultiPoint() { - return multiPoint(this.map(edge => edge.from.coordinates)); - } - - /** Creates a Polygon representing the EdgeRing. - * XXX: the polygon could be cached - * @returns {Feature} - Polygon representation of the Edge Ring - */ - toPolygon() { - const coordinates = this.map(edge => edge.from.coordinates); - coordinates.push(this[0].from.coordinates); - return polygon([coordinates]); - } - - /** Calculates the envelope of the EdgeRing. - * XXX: the envelope could be cached - * @returns {Feature} - envelope - */ - getEnvelope() { - return envelope(this.toMultiPoint()); - } - - /** - * `geos::operation::polygonize::EdgeRing::findEdgeRingContaining` - * - * @param {EdgeRing} testEdgeRing - EdgeRing to look in the list - * @param {EdgeRing[]} shellList - List of EdgeRing in which to search - * - * @returns {EdgeRing} - EdgeRing which contains the testEdgeRing - */ - static findEdgeRingContaining(testEdgeRing, shellList) { - const testEnvelope = testEdgeRing.getEnvelope(); - - let minEnvelope, - minShell; - shellList.forEach(shell => { - const tryEnvelope = shell.getEnvelope(); - - if (minShell) - minEnvelope = minShell.getEnvelope(); - - // the hole envelope cannot equal the shell envelope - if (envelopeIsEqual(tryEnvelope, testEnvelope)) - return; - - if (envelopeContains(tryEnvelope, testEnvelope)) { - const testPoint = testEdgeRing.map(edge => edge.from.coordinates) - .find(pt => !shell.some(edge => coordinatesEqual(pt, edge.from.coordinates))); - - if (testPoint && shell.inside(point(testPoint))) { - if (!minShell || envelopeContains(minEnvelope, tryEnvelope)) - minShell = shell; - } - } - }); - - return minShell; - } - - /** Checks if the point is inside the edgeRing - * - * @param {Feature} point - Point to check if it is inside the edgeRing - * @returns {Boolean} - True if it is inside, False otherwise - */ - inside(point) { - return inside(point, this.toPolygon()); - } -} - -module.exports = EdgeRing; diff --git a/packages/turf-polygonize/src/EdgeRing.test.js b/packages/turf-polygonize/src/EdgeRing.test.js deleted file mode 100644 index 2d223aceb8..0000000000 --- a/packages/turf-polygonize/src/EdgeRing.test.js +++ /dev/null @@ -1,22 +0,0 @@ -const test = require('tape'), - EdgeRing = require('./EdgeRing'), - Edge = require('./Edge'), - Node = require('./Node'); - -test('EdgeRing.isHole', t => { - let edgeRing = new EdgeRing(); - edgeRing.push(new Edge(new Node([0, 0]), new Node([1, 0]))); - edgeRing.push(new Edge(edgeRing[0].to, new Node([0, 1]))); - edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); - - t.ok(edgeRing.isHole(), 'A EdgeRing with elements in CCW order has to be a Hole'); - - edgeRing = new EdgeRing(); - edgeRing.push(new Edge(new Node([0, 0]), new Node([0, 1]))); - edgeRing.push(new Edge(edgeRing[0].to, new Node([1, 0]))); - edgeRing.push(new Edge(edgeRing[1].to, edgeRing[0].from)); - - t.notOk(edgeRing.isHole(), 'A EdgeRing with elements in CW order does not have to be a Hole'); - - t.end(); -}); diff --git a/packages/turf-polygonize/src/Graph.js b/packages/turf-polygonize/src/Graph.js deleted file mode 100644 index b42fe4d968..0000000000 --- a/packages/turf-polygonize/src/Graph.js +++ /dev/null @@ -1,291 +0,0 @@ -const Node = require('./Node'), - Edge = require('./Edge'), - EdgeRing = require('./EdgeRing'); - -/** 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) - */ -class Graph { - /** Creates a graph from a GeoJSON. - * @param {FeatureCollection} geoJson - it must comply with the restrictions detailed in the index - * @returns {Graph} - The newly created graph - */ - static fromGeoJson(geoJson) { - const graph = new Graph(); - geoJson.features.forEach(feature => { - const start = graph.getNode(feature.geometry.coordinates[0]), - end = graph.getNode(feature.geometry.coordinates[1]); - - graph.addEdge(start, end); - }); - - return graph; - } - - /** Creates or get a Node. - * - * @param {Number[]} coordinates - Coordinates of the node - * @returns {Node} - The created or stored node - */ - getNode(coordinates) { - const id = Node.buildId(coordinates); - let node = this.nodes[id]; - if (!node) - node = this.nodes[id] = new Node(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 - */ - addEdge(from, to) { - const edge = new Edge(from, to), - symetricEdge = edge.getSymetric(); - - this.edges.push(edge); - this.edges.push(symetricEdge); - } - - constructor() { - this.edges = []; //< {Edge[]} dirEdges - - // The key is the `id` of the Node (ie: coordinates.join(',')) - this.nodes = {}; - } - - /** Removes Dangle Nodes (nodes with grade 1). - */ - deleteDangles() { - Object.values(this.nodes) - .forEach(node => 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 - */ - _removeIfDangle(node) { - // As edges are directed and symetrical, we count only innerEdges - if (node.innerEdges.length <= 1) { - const outerNodes = node.outerEdges.map(e => e.to); - this.removeNode(node); - outerNodes.forEach(n => 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. - */ - deleteCutEdges() { - this._computeNextCWEdges(); - this._findLabeledEdgeRings(); - - // Cut-edges (bridges) are edges where both edges have the same label - this.edges.forEach(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 - */ - _computeNextCWEdges(node) { - if (typeof node === 'undefined') { - Object.values(this.nodes).forEach(node => this._computeNextCWEdges(node)); - } else { - node.outerEdges.forEach((edge, i) => { - node.outerEdges[(i === 0 ? node.outerEdges.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 - */ - _computeNextCCWEdges(node, label) { - const edges = node.outerEdges; - let firstOutDE, - prevInDE; - - for (let i = edges.length - 1; i >= 0; --i) { - let de = edges[i], - sym = de.symetric, - outDE, - inDE; - - 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 - */ - _findLabeledEdgeRings() { - const edgeRingStarts = []; - let label = 0; - this.edges.forEach(edge => { - if (edge.label >= 0) - return; - - edgeRingStarts.push(edge); - - let 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. - */ - getEdgeRings() { - this._computeNextCWEdges(); - - // Clear labels - this.edges.forEach(edge => { - edge.label = undefined; - }); - - this._findLabeledEdgeRings().forEach(edge => { - // convertMaximalToMinimalEdgeRings - this._findIntersectionNodes(edge).forEach(node => { - this._computeNextCCWEdges(node, edge.label); - }); - }); - - const edgeRingList = []; - - // find all edgerings - this.edges.forEach(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 - */ - _findIntersectionNodes(startEdge) { - const intersectionNodes = []; - let edge = startEdge; - do { - // getDegree - let degree = 0; - edge.from.outerEdges.forEach(e => { - if (e.label === startEdge.label) - ++degree; - }); - - if (degree > 1) - intersectionNodes.push(edge.from); - - edge = edge.next; - } 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. - */ - _findEdgeRing(startEdge) { - let edge = startEdge; - const edgeRing = new EdgeRing(); - - 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 - */ - removeNode(node) { - node.outerEdges.forEach(edge => this.removeEdge(edge)); - node.innerEdges.forEach(edge => this.removeEdge(edge)); - delete this.nodes[node.id]; - } - - /** Remove edge from the graph and deletes the edge. - * - * @param {Edge} edge - Edge to be removed - */ - removeEdge(edge) { - this.edges = this.edges.filter(e => !e.isEqual(edge)); - edge.deleteEdge(); - } -} - -module.exports = Graph; diff --git a/packages/turf-polygonize/src/Graph.test.js b/packages/turf-polygonize/src/Graph.test.js deleted file mode 100644 index a4e18f0bbc..0000000000 --- a/packages/turf-polygonize/src/Graph.test.js +++ /dev/null @@ -1,94 +0,0 @@ -const test = require('tape'), - Graph = require('./Graph'), - Node = require('./Node'), - {featureCollection, lineString} = require('@turf/helpers'); - -test('Graph :: fromGeoJson', t => { - const geoJson = featureCollection( - [ - lineString([[0, 1], [0, 0]]), - lineString([[1, 1], [0, 0]]), - lineString([[1, 0], [0, 0]]) - ] - ), - graph = Graph.fromGeoJson(geoJson); - - t.equal(Object.keys(graph.nodes).length, 4, 'The graph has to have the correct number of nodes'); - - // Edges are symetric - t.equal(graph.edges.length, 6, 'The graph has to have the correct number of edges'); - - t.end(); -}); - -test('Graph :: deleteDangles', t => { - const geoJson = featureCollection( - [ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [0, 2]]), - lineString([[0, 1], [1, 1]]), - lineString([[1, 1], [1, 0]]), - lineString([[1, 0], [0, 0]]) - ] - ), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteDangles(); - - t.equal(Object.keys(graph.nodes).length, 4); - - t.notOk(graph.nodes[Node.buildId([0, 2])], 'Dangle node has to be removed'); - - t.end(); -}); - -test('Graph :: deleteCutEdges', t => { - const geoJson = featureCollection( - [ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]) - ] - ), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - - t.equal(Object.keys(graph.nodes).length, 6); - t.equal(graph.edges.length, 12); - - t.notOk(graph.edges.find(e => e.to.id === Node.buildId([1, 1]) && e.from.id === Node.buildId([2, 1]))); - t.notOk(graph.edges.find(e => e.to.id === Node.buildId([2, 1]) && e.from.id === Node.buildId([1, 1]))); - - t.end(); -}); - -test('Graph :: getEdgeRings', t => { - const geoJson = featureCollection( - [ - lineString([[0, 0], [0, 1]]), - lineString([[0, 1], [1, 1]]), - lineString([[0, 0], [1, 1]]), - lineString([[1, 1], [2, 1]]), - lineString([[2, 1], [3, 1]]), - lineString([[3, 1], [3, 0]]), - lineString([[2, 1], [3, 0]]) - ] - ), - graph = Graph.fromGeoJson(geoJson); - - graph.deleteCutEdges(); - const edgeRings = graph.getEdgeRings(); - - t.equal(edgeRings.length, 4); - - edgeRings.forEach(edgeRing => { - t.equal(edgeRing.length, 3); - }); - - t.end(); -}); diff --git a/packages/turf-polygonize/src/Node.js b/packages/turf-polygonize/src/Node.js deleted file mode 100644 index 0b75ee7603..0000000000 --- a/packages/turf-polygonize/src/Node.js +++ /dev/null @@ -1,66 +0,0 @@ -const {orientationIndex} = require('./util'); - -class Node { - static buildId(coordinates) { - return coordinates.join(','); - } - - constructor(coordinates) { - this.id = Node.buildId(coordinates); - this.coordinates = coordinates; //< Number[] - this.innerEdges = []; //< Edge[] - - // We wil store to (out) edges in an CCW order as geos::planargraph::DirectedEdgeStar does - this.outerEdges = []; //< Edge[] - } - - removeInnerEdge(edge) { - this.innerEdges = this.innerEdges.filter(e => e.from.id !== edge.from.id); - } - - removeOuterEdge(edge) { - this.outerEdges = this.outerEdges.filter(e => e.to.id !== edge.to.id); - } - - /** Outer edges are stored CCW order. - * XXX: on each add we are ordering, this could be optimized - * @param {Edge} edge - Edge to add as an outerEdge. - */ - addOuterEdge(edge) { - this.outerEdges.push(edge); - //this.outerEdges.sort((a, b) => a.compareTo(b)); - // Using this comparator in order to be deterministic - this.outerEdges.sort((a, b) => { - const aNode = a.to, - bNode = b.to; - - if (aNode.coordinates[0] - this.coordinates[0] >= 0 && bNode.coordinates[0] - this.coordinates[0] < 0) - return 1; - if (aNode.coordinates[0] - this.coordinates[0] < 0 && bNode.coordinates[0] - this.coordinates[0] >= 0) - return -1; - - if (aNode.coordinates[0] - this.coordinates[0] === 0 && bNode.coordinates[0] - this.coordinates[0] === 0) { - if (aNode.coordinates[1] - this.coordinates[1] >= 0 || bNode.coordinates[1] - this.coordinates[1] >= 0) - return aNode.coordinates[1] - bNode.coordinates[1]; - return bNode.coordinates[1] - aNode.coordinates[1]; - } - - const det = orientationIndex(this.coordinates, aNode.coordinates, bNode.coordinates); - if (det < 0) - return 1; - if (det > 0) - return -1; - - const d1 = Math.pow(aNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(aNode.coordinates[1] - this.coordinates[1], 2), - d2 = Math.pow(bNode.coordinates[0] - this.coordinates[0], 2) + Math.pow(bNode.coordinates[1] - this.coordinates[1], 2); - - return d1 - d2; - }); - } - - addInnerEdge(edge) { - this.innerEdges.push(edge); - } -} - -module.exports = Node; diff --git a/packages/turf-polygonize/src/Node.test.js b/packages/turf-polygonize/src/Node.test.js deleted file mode 100644 index 7c4f2839cf..0000000000 --- a/packages/turf-polygonize/src/Node.test.js +++ /dev/null @@ -1,25 +0,0 @@ -const test = require('tape'), - Node = require('./Node'), - Edge = require('./Edge'); - -test('Node.outerEdges CCW order', t => { - const center = new Node([0, 0]), - addNode = c => new Edge(center, new Node(c)); - - addNode([0, 1]); - addNode([1, 1]); - addNode([1, 0]); - addNode([1, -1]); - addNode([0, -1]); - addNode([-1, -1]); - addNode([-1, 0]); - addNode([-1, 1]); - - t.deepEqual( - center.outerEdges.map(e => e.to.coordinates), - [[-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1]], - 'Outernodes have to be in CCW order' - ); - - t.end(); -}); diff --git a/packages/turf-polygonize/src/util.js b/packages/turf-polygonize/src/util.js deleted file mode 100644 index 467e754fd8..0000000000 --- a/packages/turf-polygonize/src/util.js +++ /dev/null @@ -1,72 +0,0 @@ -const inside = require('@turf/inside'), - {point} = require('@turf/helpers'); - -/** Returns the direction of the point q relative to the vector p1 -> p2. - * Implementation of geos::algorithm::CGAlgorithm::orientationIndex() - * (same as geos::algorithm::CGAlgorithm::computeOrientation()) - * - * @param {Number[]} p1 - the origin point of the vector - * @param {Number[]} p2 - the final point of the vector - * @param {Number[]} q - the point to compute the direction to - * - * @returns {Number} - 1 if q is ccw (left) from p1->p2, - * -1 if q is cw (right) from p1->p2, - * 0 if q is colinear with p1->p2 - */ -function orientationIndex(p1, p2, q) { - const dx1 = p2[0] - p1[0], - dy1 = p2[1] - p1[1], - dx2 = q[0] - p2[0], - dy2 = q[1] - p2[1]; - - return Math.sign(dx1 * dy2 - dx2 * dy1); -} - -/** Checks if two envelopes are equal. - * The function assumes that the arguments are envelopes, i.e.: Rectangular polygon - * - * @param {Feature} env1 - Envelope - * @param {Feature} env2 - Envelope - * @returns {Boolean} - True if the envelopes are equal - */ -function envelopeIsEqual(env1, env2) { - const envX1 = env1.geometry.coordinates.map(c => c[0]), - envY1 = env1.geometry.coordinates.map(c => c[1]), - envX2 = env2.geometry.coordinates.map(c => c[0]), - envY2 = env2.geometry.coordinates.map(c => c[1]); - - return Math.max(null, envX1) === Math.max(null, envX2) && - Math.max(null, envY1) === Math.max(null, envY2) && - Math.min(null, envX1) === Math.min(null, envX2) && - Math.min(null, envY1) === Math.min(null, envY2); -} - -/** Check if a envelope is contained in other one. - * The function assumes that the arguments are envelopes, i.e.: Convex polygon - * XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, - * this could be further improved. - * - * @param {Feature} self - Envelope - * @param {Feature} env - Envelope - * @returns {Boolean} - True if env is contained in self - */ -function envelopeContains(self, env) { - return env.geometry.coordinates[0].every(c => inside(point(c), self)); -} - -/** Checks if two coordinates are equal. - * - * @param {Number[]} coord1 - First coordinate - * @param {Number[]} coord2 - Second coordinate - * @returns {Boolean} - True if coordinates are equal - */ -function coordinatesEqual(coord1, coord2) { - return coord1[0] === coord2[0] && coord1[1] === coord2[1]; -} - -module.exports = { - orientationIndex, - envelopeIsEqual, - envelopeContains, - coordinatesEqual -}; From 30166cef8047d99c4a1cb79617975012a62cf875 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 18:53:08 -0300 Subject: [PATCH 23/36] Updated Readme --- packages/turf-polygonize/README.md | 316 ----------------------------- 1 file changed, 316 deletions(-) diff --git a/packages/turf-polygonize/README.md b/packages/turf-polygonize/README.md index c1e43ef96d..26eb5a384e 100644 --- a/packages/turf-polygonize/README.md +++ b/packages/turf-polygonize/README.md @@ -20,322 +20,6 @@ The implementation correctly handles: Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygons created -# Graph - -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) - -## getNode - -Creates or get a Node. - -**Parameters** - -- `coordinates` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Coordinates of the node - -Returns **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** The created or stored node - -## addEdge - -Adds an Edge and its symetricall. -Edges are added symetrically, i.e.: we also add its symetric - -**Parameters** - -- `from` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node which starts the Edge -- `to` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node which ends the Edge - -## deleteDangles - -Removes Dangle Nodes (nodes with grade 1). - -## \_removeIfDangle - -Check if node is dangle, if so, remove it. -It calls itself recursively, removing a dangling node might cause another dangling node - -**Parameters** - -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node to check if it's a dangle - -## deleteCutEdges - -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. - -## \_computeNextCWEdges - -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. - -**Parameters** - -- `node` **\[[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)]** If no node is passed, the function calls itself for every node in the Graph - -## \_computeNextCCWEdges - -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. - -**Parameters** - -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node -- `label` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** Ring's label - -## \_findLabeledEdgeRings - -Finds rings and labels edges according to which rings are. -The label is a number which is increased for each ring. - -Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Edge](#edge)>** edges that start rings - -## getEdgeRings - -Computes the EdgeRings formed by the edges in this graph. - -Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** A list of all the EdgeRings in the graph. - -## \_findIntersectionNodes - -Find all nodes in a Maxima EdgeRing which are self-intersection nodes. - -**Parameters** - -- `startEdge` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Start Edge of the Ring - -Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)>** intersection nodes - -## \_findEdgeRing - -Get the edge-ring which starts from the provided Edge. - -**Parameters** - -- `startEdge` **[Edge](#edge)** starting edge of the edge ring - -Returns **[EdgeRing](#edgering)** EdgeRing which start Edge is the provided one. - -## removeNode - -Removes a node from the Graph. - -It also removes edges asociated to that node - -**Parameters** - -- `node` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** Node to be removed - -## removeEdge - -Remove edge from the graph and deletes the edge. - -**Parameters** - -- `edge` **[Edge](#edge)** Edge to be removed - -## fromGeoJson - -Creates a graph from a GeoJSON. - -**Parameters** - -- `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** it must comply with the restrictions detailed in the index - -Returns **[Graph](#graph)** The newly created graph - -# addOuterEdge - -Outer edges are stored CCW order. -XXX: on each add we are ordering, this could be optimized - -**Parameters** - -- `edge` **[Edge](#edge)** Edge to add as an outerEdge. - -# Edge - -This class is inspired by GEOS's geos::operation::polygonize::PolygonizeDirectedEdge - -## getSymetric - -Creates or get the symetric Edge. - -Returns **[Edge](#edge)** Symetric Edge. - -## constructor - -**Parameters** - -- `from` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** start node of the Edge -- `to` **[Node](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling)** end node of the edge - -## deleteEdge - -Removes edge from from and to nodes. - -## isEqual - -Compares Edge equallity. -An edge is equal to another, if the from and to nodes are the same. - -**Parameters** - -- `edge` **[Edge](#edge)** Another Edge - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if Edges are equal, False otherwise - -## toLineString - -Returns a LineString representation of the Edge - -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** LineString representation of the Edge - -## compareTo - -Comparator of two edges. -Implementation of geos::planargraph::DirectedEdge::compareTo. - -**Parameters** - -- `edge` **[Edge](#edge)** Another edge to compare with this one - -Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** \-1 if this Edge has a greater angle with the positive x-axis than b, - 0 if the Edges are colinear, - 1 otherwise - -# EdgeRing - -**Extends Array** - -Ring of edges which form a polygon. -The ring may be either an outer shell or a hole. - -This class is inspired in GEOS's geos::operation::polygonize::EdgeRing - -## isValid - -Check if the ring is valid in geomtry terms. -A ring must have either 0 or 4 or more points. The first and the last must be -equal (in 2D) -geos::geom::LinearRing::validateConstruction - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Validity of the EdgeRing - -## isHole - -Tests whether this ring is a hole. -A ring is a hole if it is oriented counter-clockwise. -Similar implementation of geos::algorithm::CGAlgorithms::isCCW - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true: if it is a hole - -## toMultiPoint - -Creates a MultiPoint representing the EdgeRing (discarts edges directions). - -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[MultiPoint](http://geojson.org/geojson-spec.html#multipoint)>** Multipoint representation of the EdgeRing - -## toPolygon - -Creates a Polygon representing the EdgeRing. -XXX: the polygon could be cached - -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygon representation of the Edge Ring - -## getEnvelope - -Calculates the envelope of the EdgeRing. -XXX: the envelope could be cached - -Returns **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** envelope - -## inside - -Checks if the point is inside the edgeRing - -**Parameters** - -- `point` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Point](http://geojson.org/geojson-spec.html#point)>** Point to check if it is inside the edgeRing - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if it is inside, False otherwise - -## findEdgeRingContaining - -`geos::operation::polygonize::EdgeRing::findEdgeRingContaining` - -**Parameters** - -- `testEdgeRing` **[EdgeRing](#edgering)** EdgeRing to look in the list -- `shellList` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[EdgeRing](#edgering)>** List of EdgeRing in which to search - -Returns **[EdgeRing](#edgering)** EdgeRing which contains the testEdgeRing - -# orientationIndex - -Returns the direction of the point q relative to the vector p1 -> p2. -Implementation of geos::algorithm::CGAlgorithm::orientationIndex() -(same as geos::algorithm::CGAlgorithm::computeOrientation()) - -**Parameters** - -- `p1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the origin point of the vector -- `p2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the final point of the vector -- `q` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** the point to compute the direction to - -Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** 1 if q is ccw (left) from p1->p2, - \-1 if q is cw (right) from p1->p2, - 0 if q is colinear with p1->p2 - -# envelopeIsEqual - -Checks if two envelopes are equal. -The function assumes that the arguments are envelopes, i.e.: Rectangular polygon - -**Parameters** - -- `env1` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope -- `env2` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if the envelopes are equal - -# envelopeContains - -Check if a envelope is contained in other one. -The function assumes that the arguments are envelopes, i.e.: Convex polygon -XXX: Envelopes are rectangular, checking if a point is inside a rectangule is something easy, -this could be further improved. - -**Parameters** - -- `self` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope -- `env` **[Feature](http://geojson.org/geojson-spec.html#feature-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Envelope - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if env is contained in self - -# coordinatesEqual - -Checks if two coordinates are equal. - -**Parameters** - -- `coord1` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** First coordinate -- `coord2` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** Second coordinate - -Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** True if coordinates are equal - From 2cf4a30d9c03f42a658376e0c82b95fe31a7aedc Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 19:02:32 -0300 Subject: [PATCH 24/36] Added license and files in package.json --- packages/turf-polygonize/LICENSE | 20 ++++++++++++++++++++ packages/turf-polygonize/package.json | 3 +++ 2 files changed, 23 insertions(+) create mode 100644 packages/turf-polygonize/LICENSE diff --git a/packages/turf-polygonize/LICENSE b/packages/turf-polygonize/LICENSE new file mode 100644 index 0000000000..96ce51b76f --- /dev/null +++ b/packages/turf-polygonize/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 TurfJS + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index 38ab9ee02d..69a3bf35e2 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -3,6 +3,9 @@ "version": "4.3.0", "description": "turf polygonize module", "main": "index.js", + "files": [ + "index.js" + ], "scripts": { "test": "tape test.js" }, From 991bc802df7a421a9685d9ce18e9e7af774a892e Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 19:13:38 -0300 Subject: [PATCH 25/36] Added homepage to package.json --- packages/turf-polygonize/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index 69a3bf35e2..b5e10e7e89 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -27,7 +27,7 @@ "bugs": { "url": "https://github.com/Turfjs/turf/issues" }, - "homepage": "https://github.com/Turfjs/turf#readme", + "homepage": "https://github.com/Turfjs/turf", "devDependencies": { "@turf/helpers": "^4.3.0", "tape": "^4.6.3" From 96e37d559c7dd0060d8f94a32112b2e16f8cbe2f Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 19:15:52 -0300 Subject: [PATCH 26/36] Added @name to JSDoc of index --- packages/turf-polygonize/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index a9252e75c7..7f0b91aee0 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -12,6 +12,7 @@ var polygonize = require('polygonize'); * - Cut Edges (bridges): edges that are connected at both ends but which do not form part * of a polygon. * + * @name polygonize * @param {FeatureCollection} geoJson - Lines in order to polygonize * @returns {FeatureCollection} - Polygons created */ From 35c16eb0d92216470e151eb88e0b14006d131570 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Sun, 28 May 2017 19:32:05 -0300 Subject: [PATCH 27/36] Fixing tests --- packages/turf-polygonize/package.json | 4 +- packages/turf-polygonize/test.js | 57 +++++++++------------------ 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index b5e10e7e89..b818f78a3c 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -30,7 +30,9 @@ "homepage": "https://github.com/Turfjs/turf", "devDependencies": { "@turf/helpers": "^4.3.0", - "tape": "^4.6.3" + "load-json-file": "^2.0.0", + "tape": "^4.6.3", + "write-json-file": "^2.2.0" }, "dependencies": { "polygonize": "^1.0.0" diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 9e51e47198..629da3f434 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -2,50 +2,29 @@ const test = require('tape'), { featureCollection, lineString, polygon } = require('@turf/helpers'), polygonize = require('./'), fs = require('fs'), - path = require('path'); + path = require('path'), + load = require('load-json-file'), + write = require('write-json-file'); const directories = { in: path.join(__dirname, 'test', 'in') + path.sep, out: path.join(__dirname, 'test', 'out') + path.sep, }; -function getFullPath(filename, type='in') { - return path.join(directories[type], filename); -} +const fixtures = fs.readdirSync(directories.in).map(filename => { + return { + filename, + name: path.parse(filename).name, + geojson: load.sync(directories.in + filename) + }; +}); -function readJsonFile(filename, type='in') { - try { - return JSON.parse(fs.readFileSync(getFullPath(filename, type))); - } catch(e) { - if (e.code !== 'ENOENT') - throw e; - return undefined; - } -} - -function writeJsonFile(filename, data, type='out') { - fs.writeFileSync(getFullPath(filename, type), JSON.stringify(data)); -} +test('turf-polygonize', t => { + for (const {filename, name, geojson} of fixtures) { + const results = polygonize(geojson); -fs.readdirSync(directories.in) - .filter(filename => !filename.startsWith('.')) - .map(filename => { - return { - filename, - name: path.parse(filename).name, - input: readJsonFile(filename, 'in'), - output: readJsonFile(filename, 'out'), - } - }) - .forEach(({filename, name, input, output}) => { - test(`turf-polygonize :: ${name}`, t => { - const result = polygonize(input); - if (output) { - t.deepEqual(result, output); - } else { - t.skip(`${name} not found :: writing`); - writeJsonFile(filename, result); - } - t.end(); - }); - }); + if (process.env.REGEN) write.sync(directories.out + filename, results); + t.deepEquals(results, load.sync(directories.out + filename), name); + } + t.end(); +}); From 3e5585e9d25276ade2f16f1ca65c2e936772a34d Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Tue, 30 May 2017 15:24:30 -0300 Subject: [PATCH 28/36] Added mutation tests --- packages/turf-polygonize/test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 629da3f434..0a12bf6fcb 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -21,10 +21,12 @@ const fixtures = fs.readdirSync(directories.in).map(filename => { test('turf-polygonize', t => { for (const {filename, name, geojson} of fixtures) { - const results = polygonize(geojson); + const geojsonBefore = JSON.parse(JSON.stringify(geojson)), + results = polygonize(geojson); if (process.env.REGEN) write.sync(directories.out + filename, results); t.deepEquals(results, load.sync(directories.out + filename), name); + t.deepEquals(geojson, geojsonBefore, `${name} - input should NOT be mutated`); } t.end(); }); From cb6b30b7f28a78d440faf85f301d93329c3e3cb1 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 May 2017 09:16:51 -0400 Subject: [PATCH 29/36] Add missing required files - Typescript definition - Benchmark - Yarn.lock - Update test.js --- packages/turf-polygonize/bench.js | 46 ++++ packages/turf-polygonize/index.d.ts | 12 + packages/turf-polygonize/index.js | 17 +- packages/turf-polygonize/package.json | 11 +- packages/turf-polygonize/test.js | 63 +++-- packages/turf-polygonize/yarn.lock | 334 ++++++++++++++++++++++++++ 6 files changed, 450 insertions(+), 33 deletions(-) create mode 100644 packages/turf-polygonize/bench.js create mode 100644 packages/turf-polygonize/index.d.ts create mode 100644 packages/turf-polygonize/yarn.lock diff --git a/packages/turf-polygonize/bench.js b/packages/turf-polygonize/bench.js new file mode 100644 index 0000000000..f3b631c547 --- /dev/null +++ b/packages/turf-polygonize/bench.js @@ -0,0 +1,46 @@ +const fs = require('fs'); +const path = require('path'); +const load = require('load-json-file'); +const Benchmark = require('benchmark'); +const polygonize = require('./'); + +const directory = path.join(__dirname, 'test', 'in') + path.sep; +const fixtures = fs.readdirSync(directory).map(filename => { + return { + name: path.parse(filename).name, + geojson: load.sync(directory + filename) + }; +}); + + +/** + * Single Process Benchmark + * + * complex: 37.120ms + * cutedge: 0.858ms + * dangle: 0.289ms + * two-polygons: 0.784ms + */ +for (const {name, geojson} of fixtures) { + console.time(name); + polygonize(geojson); + console.timeEnd(name); +} + +/** + * Benchmark Results + * + * complex x 54.67 ops/sec ±9.63% (47 runs sampled) + * cutedge x 5,413 ops/sec ±2.20% (84 runs sampled) + * dangle x 9,175 ops/sec ±4.44% (83 runs sampled) + * two-polygons x 16,323 ops/sec ±1.39% (91 runs sampled) + */ +const suite = new Benchmark.Suite('turf-transform-polygonize'); +for (const {name, geojson} of fixtures) { + suite.add(name, () => polygonize(geojson)); +} + +suite + .on('cycle', e => console.log(String(e.target))) + .on('complete', () => {}) + .run(); diff --git a/packages/turf-polygonize/index.d.ts b/packages/turf-polygonize/index.d.ts new file mode 100644 index 0000000000..2087e03db9 --- /dev/null +++ b/packages/turf-polygonize/index.d.ts @@ -0,0 +1,12 @@ +/// + +type Lines = GeoJSON.FeatureCollection; +type Polygons = GeoJSON.FeatureCollection; + +/** + * http://turfjs.org/docs/#polygonize + */ +declare function polygonize(lines: Lines): Polygons; + +declare namespace polygonize { } +export = polygonize; diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 7f0b91aee0..066ee34104 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,10 +1,10 @@ var polygonize = require('polygonize'); -/** Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). - * +/** * Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly - * noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate - * points. + * noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate points. + * + * Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * * The implementation correctly handles: * @@ -13,7 +13,10 @@ var polygonize = require('polygonize'); * of a polygon. * * @name polygonize - * @param {FeatureCollection} geoJson - Lines in order to polygonize - * @returns {FeatureCollection} - Polygons created + * @param {FeatureCollection} lines Lines in order to polygonize + * @returns {FeatureCollection} Polygons created */ -module.exports = polygonize; +module.exports = function (lines) { + if (lines.type !== 'FeatureCollection') throw new Error('lines must be a FeatureCollection'); + return polygonize(lines); +}; diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index b818f78a3c..d76a1ac0e7 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -3,11 +3,14 @@ "version": "4.3.0", "description": "turf polygonize module", "main": "index.js", + "types": "index.d.ts", "files": [ - "index.js" + "index.js", + "index.d.ts" ], "scripts": { - "test": "tape test.js" + "test": "node test.js", + "bench": "node bench.js" }, "repository": { "type": "git", @@ -21,7 +24,8 @@ ], "author": "Turf Authors", "contributors": [ - "Nicolas Cisco <@nickcis>" + "Nicolas Cisco <@nickcis>", + "Denis Carriere <@DenisCarriere>" ], "license": "MIT", "bugs": { @@ -30,6 +34,7 @@ "homepage": "https://github.com/Turfjs/turf", "devDependencies": { "@turf/helpers": "^4.3.0", + "benchmark": "^2.1.4", "load-json-file": "^2.0.0", "tape": "^4.6.3", "write-json-file": "^2.2.0" diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 0a12bf6fcb..c29dc6acb1 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -1,32 +1,49 @@ -const test = require('tape'), - { featureCollection, lineString, polygon } = require('@turf/helpers'), - polygonize = require('./'), - fs = require('fs'), - path = require('path'), - load = require('load-json-file'), - write = require('write-json-file'); +const fs = require('fs'); +const test = require('tape'); +const path = require('path'); +const load = require('load-json-file'); +const write = require('write-json-file'); +const {featureCollection, lineString} = require('@turf/helpers'); +const polygonize = require('./'); const directories = { - in: path.join(__dirname, 'test', 'in') + path.sep, - out: path.join(__dirname, 'test', 'out') + path.sep, + in: path.join(__dirname, 'test', 'in') + path.sep, + out: path.join(__dirname, 'test', 'out') + path.sep }; const fixtures = fs.readdirSync(directories.in).map(filename => { - return { - filename, - name: path.parse(filename).name, - geojson: load.sync(directories.in + filename) - }; + return { + filename, + name: path.parse(filename).name, + geojson: load.sync(directories.in + filename) + }; }); test('turf-polygonize', t => { - for (const {filename, name, geojson} of fixtures) { - const geojsonBefore = JSON.parse(JSON.stringify(geojson)), - results = polygonize(geojson); - - if (process.env.REGEN) write.sync(directories.out + filename, results); - t.deepEquals(results, load.sync(directories.out + filename), name); - t.deepEquals(geojson, geojsonBefore, `${name} - input should NOT be mutated`); - } - t.end(); + for (const {filename, name, geojson} of fixtures) { + const results = polygonize(geojson); + + if (process.env.REGEN) write.sync(directories.out + filename, results); + t.deepEquals(results, load.sync(directories.out + filename), name); + } + t.end(); +}); + +test('turf-polygonize -- throws', t => { + const line = lineString([[0, 0], [1, 1]]); + + t.throws(() => polygonize(line)); +}); + +test('turf-polygonize -- input mutation', t => { + const lines = featureCollection([ + lineString([[0, 0], [1, 1]]), + lineString([[1, 1], [-1, -1]]), + lineString([[-1, -1], [0, 0]]) + ]); + const linesBefore = JSON.parse(JSON.stringify(lines)); + polygonize(lines); + + t.deepEquals(lines, linesBefore, 'input does not mutate'); + t.end(); }); diff --git a/packages/turf-polygonize/yarn.lock b/packages/turf-polygonize/yarn.lock new file mode 100644 index 0000000000..2247d7d871 --- /dev/null +++ b/packages/turf-polygonize/yarn.lock @@ -0,0 +1,334 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@turf/bbox-polygon@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/bbox-polygon/-/bbox-polygon-4.3.0.tgz#c75ca4159a35454b79dba1090c499f9f01cac95c" + dependencies: + "@turf/helpers" "^4.3.0" + +"@turf/bbox@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-4.3.0.tgz#644c4b745172d21aae70d1584b2a7866f1040865" + dependencies: + "@turf/meta" "^4.3.0" + +"@turf/envelope@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/envelope/-/envelope-4.3.0.tgz#912bb2d67bd8ad34addd084d138a683322e179d7" + dependencies: + "@turf/bbox" "^4.3.0" + "@turf/bbox-polygon" "^4.3.0" + +"@turf/helpers@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-4.3.0.tgz#7b2f733aa0eb3ea1f07d467ac02ede00cc6cde0d" + +"@turf/inside@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/inside/-/inside-4.3.0.tgz#ba06be5966a5ab31c1cabb9819241e0802422c45" + dependencies: + "@turf/invariant" "^4.3.0" + +"@turf/invariant@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-4.3.0.tgz#5bd1ce6ae51b1229dc0dc7d09d973fabae49af89" + +"@turf/meta@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.3.0.tgz#eb11dd2c2511524258123767fe0f5c3bd963e8d7" + +balanced-match@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +benchmark@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" + dependencies: + lodash "^4.17.4" + platform "^1.3.3" + +brace-expansion@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" + dependencies: + balanced-match "^0.4.1" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +defined@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.0" + is-callable "^1.1.3" + is-regex "^1.0.3" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + +for-each@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" + dependencies: + is-function "~1.0.0" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + +glob@~7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +has@^1.0.1, has@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-function@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-regex@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +lodash@^4.17.4: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +object-inspect@~1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.2.2.tgz#c82115e4fcc888aea14d64c22e4f17f6a70d5e5a" + +object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +platform@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" + +polygonize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/polygonize/-/polygonize-1.0.0.tgz#88342648c82da26f9132274f27a3c4b9fb33f33e" + dependencies: + "@turf/envelope" "^4.3.0" + "@turf/helpers" "^4.3.0" + "@turf/inside" "^4.3.0" + +resolve@~1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resumer@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + dependencies: + through "~2.3.4" + +slide@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + +sort-keys@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +string.prototype.trim@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.0" + function-bind "^1.0.2" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +tape@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.6.3.tgz#637e77581e9ab2ce17577e9bd4ce4f575806d8b6" + dependencies: + deep-equal "~1.0.1" + defined "~1.0.0" + for-each "~0.3.2" + function-bind "~1.1.0" + glob "~7.1.1" + has "~1.0.1" + inherits "~2.0.3" + minimist "~1.2.0" + object-inspect "~1.2.1" + resolve "~1.1.7" + resumer "~0.0.0" + string.prototype.trim "~1.1.2" + through "~2.3.8" + +through@~2.3.4, through@~2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +write-json-file@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.2.0.tgz#51862506bbb3b619eefab7859f1fd6c6d0530876" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^2.0.0" + sort-keys "^1.1.1" + write-file-atomic "^2.0.0" From b75446ec796403a0f2ee5b16cb72d11b16987152 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 May 2017 09:20:28 -0400 Subject: [PATCH 30/36] End tests --- packages/turf-polygonize/test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index c29dc6acb1..2c5de32d29 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -33,6 +33,7 @@ test('turf-polygonize -- throws', t => { const line = lineString([[0, 0], [1, 1]]); t.throws(() => polygonize(line)); + t.end(); }); test('turf-polygonize -- input mutation', t => { From cf6a7ad1d8cdc0776e694c836bd04c6171060253 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 May 2017 10:11:05 -0400 Subject: [PATCH 31/36] Added Support Feature/Geometry LineStrings - Colorized output - Fixed fixtures --- packages/turf-polygonize/README.md | 13 +- packages/turf-polygonize/index.js | 12 +- packages/turf-polygonize/package.json | 2 + packages/turf-polygonize/test.js | 26 +- .../turf-polygonize/test/in/cutedge.geojson | 256 +- .../turf-polygonize/test/in/dangle.geojson | 189 +- .../test/in/kinked-linestring.geojson | 45 + .../test/in/linestrings.geojson | 71 + .../turf-polygonize/test/out/complex.geojson | 6242 ++++++++++++++++- .../turf-polygonize/test/out/cutedge.geojson | 132 +- .../turf-polygonize/test/out/dangle.geojson | 80 +- .../test/out/kinked-linestring.geojson | 107 + .../test/out/linestrings.geojson | 129 + .../test/out/two-polygons.geojson | 310 +- packages/turf-polygonize/yarn.lock | 8 + 15 files changed, 7235 insertions(+), 387 deletions(-) create mode 100644 packages/turf-polygonize/test/in/kinked-linestring.geojson create mode 100644 packages/turf-polygonize/test/in/linestrings.geojson create mode 100644 packages/turf-polygonize/test/out/kinked-linestring.geojson create mode 100644 packages/turf-polygonize/test/out/linestrings.geojson diff --git a/packages/turf-polygonize/README.md b/packages/turf-polygonize/README.md index 26eb5a384e..1deafeb5c7 100644 --- a/packages/turf-polygonize/README.md +++ b/packages/turf-polygonize/README.md @@ -1,22 +1,19 @@ # @turf/polygonize -# index +# polygonize -Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). +Polygonizes [(Multi)LineString(s)](http://geojson.org/geojson-spec.html#linestring) into [Polygons](Polygons). -Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly -noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate -points. +Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). The implementation correctly handles: - Dangles: edges which have one or both ends which are not incident on another edge endpoint. -- Cut Edges (bridges): edges that are connected at both ends but which do not form part - of a polygon. +- Cut Edges (bridges): edges that are connected at both ends but which do not form part of a polygon. **Parameters** -- `geoJson` **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[LineString](http://geojson.org/geojson-spec.html#linestring)>** Lines in order to polygonize +- `lines` **([FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects) \| [Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<([LineString](http://geojson.org/geojson-spec.html#linestring) \| [MultiLineString](http://geojson.org/geojson-spec.html#multilinestring))>)** Lines in order to polygonize Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygons created diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 066ee34104..eeca8aa778 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,22 +1,20 @@ var polygonize = require('polygonize'); +var lineSegment = require('@turf/line-segment'); /** - * Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly - * noded, i.e., they must only meet at their endpoints. LineStrings must only have two coordinate points. + * Polygonizes {@link LineString|(Multi)LineString(s)} into {@link Polygons}. * * Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * * The implementation correctly handles: * * - Dangles: edges which have one or both ends which are not incident on another edge endpoint. - * - Cut Edges (bridges): edges that are connected at both ends but which do not form part - * of a polygon. + * - Cut Edges (bridges): edges that are connected at both ends but which do not form part of a polygon. * * @name polygonize - * @param {FeatureCollection} lines Lines in order to polygonize + * @param {FeatureCollection|Geometry|Feature} lines Lines in order to polygonize * @returns {FeatureCollection} Polygons created */ module.exports = function (lines) { - if (lines.type !== 'FeatureCollection') throw new Error('lines must be a FeatureCollection'); - return polygonize(lines); + return polygonize(lineSegment(lines)); }; diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index d76a1ac0e7..9b6699fd59 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -34,12 +34,14 @@ "homepage": "https://github.com/Turfjs/turf", "devDependencies": { "@turf/helpers": "^4.3.0", + "@turf/meta": "^4.3.0", "benchmark": "^2.1.4", "load-json-file": "^2.0.0", "tape": "^4.6.3", "write-json-file": "^2.2.0" }, "dependencies": { + "@turf/line-segment": "^4.3.0", "polygonize": "^1.0.0" } } diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index 2c5de32d29..b825c6be33 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -3,6 +3,7 @@ const test = require('tape'); const path = require('path'); const load = require('load-json-file'); const write = require('write-json-file'); +const {featureEach} = require('@turf/meta'); const {featureCollection, lineString} = require('@turf/helpers'); const polygonize = require('./'); @@ -21,7 +22,11 @@ const fixtures = fs.readdirSync(directories.in).map(filename => { test('turf-polygonize', t => { for (const {filename, name, geojson} of fixtures) { - const results = polygonize(geojson); + const polygonized = polygonize(geojson); + const results = featureCollection([]); + + featureEach(geojson, feature => results.features.push(colorize(feature))); + featureEach(polygonized, feature => results.features.push(colorize(feature, '#00F', 3))); if (process.env.REGEN) write.sync(directories.out + filename, results); t.deepEquals(results, load.sync(directories.out + filename), name); @@ -29,10 +34,17 @@ test('turf-polygonize', t => { t.end(); }); +test('turf-polygonize -- Geometry Support', t => { + const line = lineString([[0, 0], [1, 1], [5, 2], [0, 0]]); + + t.assert(polygonize(line.geometry), 'line geometry'); + t.end(); +}); + test('turf-polygonize -- throws', t => { - const line = lineString([[0, 0], [1, 1]]); + // const line = lineString([[0, 0], [1, 1]]); - t.throws(() => polygonize(line)); + // t.throws(() => polygonize(line)); t.end(); }); @@ -48,3 +60,11 @@ test('turf-polygonize -- input mutation', t => { t.deepEquals(lines, linesBefore, 'input does not mutate'); t.end(); }); + +function colorize(feature, color = '#F00', width = 6) { + feature.properties['fill'] = color; + feature.properties['fill-opacity'] = 0.3; + feature.properties['stroke'] = color; + feature.properties['stroke-width'] = width; + return feature; +} diff --git a/packages/turf-polygonize/test/in/cutedge.geojson b/packages/turf-polygonize/test/in/cutedge.geojson index 6681670563..ce0f881365 100644 --- a/packages/turf-polygonize/test/in/cutedge.geojson +++ b/packages/turf-polygonize/test/in/cutedge.geojson @@ -1,209 +1,49 @@ { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9661968, - 40.7997572 - ], - [ - -73.967898, - 40.800463 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9689288, - 40.7984325 - ], - [ - -73.9675747, - 40.7978699 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9671143, - 40.7985006 - ], - [ - -73.9684641, - 40.7990578 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9689288, - 40.7984325 - ], - [ - -73.9684641, - 40.7990578 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.968007, - 40.8000481 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.968007, - 40.8000481 - ], - [ - -73.9679585, - 40.8002177 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9679585, - 40.8002177 - ], - [ - -73.967898, - 40.800463 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9675747, - 40.7978699 - ], - [ - -73.9671143, - 40.7985006 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9671143, - 40.7985006 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9666552, - 40.7991294 - ], - [ - -73.9661968, - 40.7997572 - ] - ] - } - } - ] -} + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.96706879138947, + 40.798493169582244 + ], + [ + -73.96847426891327, + 40.79908606273021 + ], + [ + -73.96896779537201, + 40.798416011865356 + ], + [ + -73.96754086017609, + 40.797851539524295 + ], + [ + -73.96706879138947, + 40.798493169582244 + ], + [ + -73.96669328212738, + 40.79913073254739 + ], + [ + -73.96619439125061, + 40.79977641109269 + ], + [ + -73.96793782711028, + 40.80045456984621 + ], + [ + -73.96809875965117, + 40.79976016768429 + ], + [ + -73.96669328212738, + 40.79913073254739 + ] + ] + } +} \ No newline at end of file diff --git a/packages/turf-polygonize/test/in/dangle.geojson b/packages/turf-polygonize/test/in/dangle.geojson index 5a469e423e..0b6f86805d 100644 --- a/packages/turf-polygonize/test/in/dangle.geojson +++ b/packages/turf-polygonize/test/in/dangle.geojson @@ -1,158 +1,33 @@ { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9661968, - 40.7997572 - ], - [ - -73.967898, - 40.800463 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9681077, - 40.7997378 - ], - [ - -73.968007, - 40.8000481 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.968007, - 40.8000481 - ], - [ - -73.9679585, - 40.8002177 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9679585, - 40.8002177 - ], - [ - -73.967898, - 40.800463 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9675747, - 40.7978699 - ], - [ - -73.9671143, - 40.7985006 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9671143, - 40.7985006 - ], - [ - -73.9666552, - 40.7991294 - ] - ] - } - }, - { - "type": "Feature", - "properties": {}, - "geometry": { - "type": "LineString", - "coordinates": [ - [ - -73.9666552, - 40.7991294 - ], - [ - -73.9661968, - 40.7997572 - ] - ] - } - } - ] -} + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.96755695343018, + 40.797867783399724 + ], + [ + -73.96667718887329, + 40.7991429152196 + ], + [ + -73.96620512008666, + 40.799743924271894 + ], + [ + -73.96793246269226, + 40.800482995510926 + ], + [ + -73.96811485290527, + 40.79971143743523 + ], + [ + -73.96667718887329, + 40.7991429152196 + ] + ] + } +} \ No newline at end of file diff --git a/packages/turf-polygonize/test/in/kinked-linestring.geojson b/packages/turf-polygonize/test/in/kinked-linestring.geojson new file mode 100644 index 0000000000..544d762037 --- /dev/null +++ b/packages/turf-polygonize/test/in/kinked-linestring.geojson @@ -0,0 +1,45 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 131.9677734375, + -20.550508894195637 + ], + [ + 134.2529296875, + -16.59408141271846 + ], + [ + 123.3984375, + -30.48655084258847 + ], + [ + 130.1220703125, + -31.05293398570514 + ], + [ + 121.9482421875, + -22.471954507739213 + ], + [ + 132.275390625, + -25.324166525738384 + ], + [ + 129.0673828125, + -16.299051014581817 + ], + [ + 133.9453125, + -13.710035342476669 + ], + [ + 131.9677734375, + -20.550508894195637 + ] + ] + } +} \ No newline at end of file diff --git a/packages/turf-polygonize/test/in/linestrings.geojson b/packages/turf-polygonize/test/in/linestrings.geojson new file mode 100644 index 0000000000..bcd17e9e3a --- /dev/null +++ b/packages/turf-polygonize/test/in/linestrings.geojson @@ -0,0 +1,71 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 119.00390625, + -22.024545601240337 + ], + [ + 120.58593749999999, + -28.613459424004414 + ], + [ + 125.595703125, + -32.99023555965107 + ], + [ + 133.330078125, + -32.99023555965107 + ], + [ + 142.646484375, + -30.977609093348676 + ], + [ + 142.294921875, + -24.126701958681668 + ], + [ + 139.04296875, + -16.299051014581817 + ], + [ + 128.84765625, + -15.199386048559994 + ] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 142.646484375, + -30.977609093348676 + ], + [ + 132.451171875, + -27.449790329784214 + ], + [ + 128.671875, + -23.1605633090483 + ], + [ + 119.00390625, + -22.024545601240337 + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/packages/turf-polygonize/test/out/complex.geojson b/packages/turf-polygonize/test/out/complex.geojson index be37d9cb85..4edf0c23b5 100644 --- a/packages/turf-polygonize/test/out/complex.geojson +++ b/packages/turf-polygonize/test/out/complex.geojson @@ -1 +1,6241 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4003789,-34.8024187],[-58.4012571,-34.8013012],[-58.4003196,-34.8007694],[-58.399452,-34.8018971],[-58.4003357,-34.8023944],[-58.4003789,-34.8024187]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4012571,-34.8013012],[-58.4003789,-34.8024187],[-58.4013631,-34.8029699],[-58.4015377,-34.8030708],[-58.4024017,-34.8019585],[-58.4012571,-34.8013012]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.395087,-34.8031464],[-58.3959417,-34.8036499],[-58.3964727,-34.8029764],[-58.3969642,-34.8023356],[-58.3952781,-34.8013356],[-58.3942128,-34.8026319],[-58.395087,-34.8031464]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3935525,-34.8022475],[-58.3924396,-34.8015867],[-58.3918268,-34.8023406],[-58.3929547,-34.8030144],[-58.3935525,-34.8022475]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3929547,-34.8030144],[-58.3918268,-34.8023406],[-58.3907792,-34.8036757],[-58.3919262,-34.8043515],[-58.3929547,-34.8030144]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3929547,-34.8030144],[-58.3919262,-34.8043515],[-58.3929448,-34.8049413],[-58.3930577,-34.8030849],[-58.3929547,-34.8030144]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3919262,-34.8043515],[-58.3912106,-34.805308],[-58.3925604,-34.8061236],[-58.3929333,-34.8056958],[-58.3929448,-34.8049413],[-58.3919262,-34.8043515]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3912106,-34.805308],[-58.3919262,-34.8043515],[-58.3907792,-34.8036757],[-58.3904782,-34.804065],[-58.3900486,-34.8046158],[-58.3912106,-34.805308]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3954262,-34.7994934],[-58.3962631,-34.7999826],[-58.3963617,-34.8000402],[-58.3972001,-34.799019],[-58.3962705,-34.798502],[-58.3954262,-34.7994934]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3962631,-34.7999826],[-58.3954262,-34.7994934],[-58.3945242,-34.8006035],[-58.3954312,-34.8011493],[-58.3963617,-34.8000402],[-58.3962631,-34.7999826]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3963617,-34.8000402],[-58.3977629,-34.8008817],[-58.3980853,-34.8004779],[-58.3986112,-34.7998147],[-58.3972001,-34.799019],[-58.3963617,-34.8000402]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3977629,-34.8008817],[-58.3963617,-34.8000402],[-58.3954312,-34.8011493],[-58.3952781,-34.8013356],[-58.3969642,-34.8023356],[-58.3971802,-34.8020789],[-58.3980104,-34.8010326],[-58.3977629,-34.8008817]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3977629,-34.8008817],[-58.3980104,-34.8010326],[-58.3983132,-34.8012066],[-58.3986617,-34.8014125],[-58.3990591,-34.8008188],[-58.3994936,-34.8003009],[-58.3986112,-34.7998147],[-58.3980853,-34.8004779],[-58.3977629,-34.8008817]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4003196,-34.8007694],[-58.4000651,-34.800625],[-58.3994936,-34.8003009],[-58.3990591,-34.8008188],[-58.3986617,-34.8014125],[-58.399452,-34.8018971],[-58.4003196,-34.8007694]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4001253,-34.8060931],[-58.400154,-34.8060524],[-58.400195,-34.8060195],[-58.4002453,-34.805997],[-58.4003011,-34.8059864],[-58.4003583,-34.8059886],[-58.4004127,-34.8060034],[-58.4004602,-34.8060297],[-58.4013091,-34.8049401],[-58.4015784,-34.8046036],[-58.4005424,-34.8040087],[-58.399244,-34.8055954],[-58.4001253,-34.8060931]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.400154,-34.8060524],[-58.4001253,-34.8060931],[-58.4001105,-34.8061431],[-58.4001143,-34.8061945],[-58.4001364,-34.8062426],[-58.4001748,-34.8062833],[-58.400226,-34.8063129],[-58.4002907,-34.8063294],[-58.4003584,-34.8063282],[-58.4004221,-34.8063094],[-58.4004752,-34.806275],[-58.4005117,-34.8062295],[-58.4005291,-34.806177],[-58.4005258,-34.8061227],[-58.4005021,-34.8060719],[-58.4004602,-34.8060297],[-58.4004127,-34.8060034],[-58.4003583,-34.8059886],[-58.4003011,-34.8059864],[-58.4002453,-34.805997],[-58.400195,-34.8060195],[-58.400154,-34.8060524]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4015784,-34.8046036],[-58.4024151,-34.8035586],[-58.4015377,-34.8030708],[-58.4013631,-34.8029699],[-58.4005424,-34.8040087],[-58.4015784,-34.8046036]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3985907,-34.8083664],[-58.3993749,-34.8073699],[-58.3983081,-34.806706],[-58.3974713,-34.8077069],[-58.3985907,-34.8083664]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3993749,-34.8073699],[-58.400226,-34.8063129],[-58.4001748,-34.8062833],[-58.4001364,-34.8062426],[-58.4001143,-34.8061945],[-58.4001105,-34.8061431],[-58.4001253,-34.8060931],[-58.399244,-34.8055954],[-58.3983081,-34.806706],[-58.3993749,-34.8073699]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.4005424,-34.8040087],[-58.4013631,-34.8029699],[-58.4003789,-34.8024187],[-58.4003357,-34.8023944],[-58.399452,-34.8018971],[-58.3986617,-34.8014125],[-58.3983132,-34.8012066],[-58.3980104,-34.8010326],[-58.3971802,-34.8020789],[-58.398366,-34.8027589],[-58.3994805,-34.8033989],[-58.4005424,-34.8040087]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.399244,-34.8055954],[-58.4005424,-34.8040087],[-58.3994805,-34.8033989],[-58.3987746,-34.8042389],[-58.3981728,-34.8049644],[-58.399244,-34.8055954]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3983081,-34.806706],[-58.399244,-34.8055954],[-58.3981728,-34.8049644],[-58.3972354,-34.8060837],[-58.3983081,-34.806706]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3974713,-34.8077069],[-58.3983081,-34.806706],[-58.3972354,-34.8060837],[-58.3966681,-34.8067671],[-58.3964129,-34.8070746],[-58.3974713,-34.8077069]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3966454,-34.8086947],[-58.3974713,-34.8077069],[-58.3964129,-34.8070746],[-58.3956137,-34.8080702],[-58.3966454,-34.8086947]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964129,-34.8070746],[-58.3949373,-34.8061929],[-58.3941987,-34.8071883],[-58.3956137,-34.8080702],[-58.3964129,-34.8070746]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3949373,-34.8061929],[-58.3964129,-34.8070746],[-58.3966681,-34.8067671],[-58.3972354,-34.8060837],[-58.3957427,-34.8051655],[-58.3949373,-34.8061929]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3949373,-34.8061929],[-58.3941477,-34.8057278],[-58.3935559,-34.8065149],[-58.3935636,-34.8067511],[-58.3941987,-34.8071883],[-58.3949373,-34.8061929]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3941477,-34.8057278],[-58.3949373,-34.8061929],[-58.3957427,-34.8051655],[-58.3949969,-34.8047067],[-58.3941477,-34.8057278]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3994805,-34.8033989],[-58.398366,-34.8027589],[-58.3976747,-34.8036356],[-58.3987746,-34.8042389],[-58.3994805,-34.8033989]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.398366,-34.8027589],[-58.3971802,-34.8020789],[-58.3969642,-34.8023356],[-58.3964727,-34.8029764],[-58.3976747,-34.8036356],[-58.398366,-34.8027589]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3957427,-34.8051655],[-58.3972354,-34.8060837],[-58.3981728,-34.8049644],[-58.3971168,-34.8043422],[-58.396618,-34.8040484],[-58.3957427,-34.8051655]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3976747,-34.8036356],[-58.3971168,-34.8043422],[-58.3981728,-34.8049644],[-58.3987746,-34.8042389],[-58.3976747,-34.8036356]]]}}]} \ No newline at end of file +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4012571, + -34.8013012 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4020709, + -34.8002564 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4020709, + -34.8002564 + ], + [ + -58.4026441, + -34.7995347 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4026441, + -34.7995347 + ], + [ + -58.4032748, + -34.7987421 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4032748, + -34.7987421 + ], + [ + -58.40353, + -34.7986715 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.40353, + -34.7986715 + ], + [ + -58.4045643, + -34.7970077 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4045643, + -34.7970077 + ], + [ + -58.4051217, + -34.7960535 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4069531, + -34.7970396 + ], + [ + -58.4061007, + -34.797642 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4061007, + -34.797642 + ], + [ + -58.4054827, + -34.7986499 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4054827, + -34.7986499 + ], + [ + -58.4043951, + -34.7993638 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4043951, + -34.7993638 + ], + [ + -58.4037967, + -34.8001502 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4037967, + -34.8001502 + ], + [ + -58.4032304, + -34.80088 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4032304, + -34.80088 + ], + [ + -58.4024017, + -34.8019585 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4024017, + -34.8019585 + ], + [ + -58.4015377, + -34.8030708 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942128, + -34.8026319 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942128, + -34.8026319 + ], + [ + -58.3937803, + -34.8023836 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3937803, + -34.8023836 + ], + [ + -58.3937374, + -34.8023571 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3937374, + -34.8023571 + ], + [ + -58.3935525, + -34.8022475 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935525, + -34.8022475 + ], + [ + -58.3924396, + -34.8015867 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3924396, + -34.8015867 + ], + [ + -58.3913232, + -34.8009287 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3913232, + -34.8009287 + ], + [ + -58.390647, + -34.8005306 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3918268, + -34.8023406 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3918268, + -34.8023406 + ], + [ + -58.39097, + -34.8018398 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.39097, + -34.8018398 + ], + [ + -58.3908824, + -34.80173 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3908824, + -34.80173 + ], + [ + -58.3907391, + -34.8016607 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935525, + -34.8022475 + ], + [ + -58.3929547, + -34.8030144 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3919262, + -34.8043515 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3912106, + -34.805308 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3912106, + -34.805308 + ], + [ + -58.3903757, + -34.8063186 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3919262, + -34.8043515 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3929448, + -34.8049413 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3932414, + -34.8051253 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3930577, + -34.8030849 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3930577, + -34.8030849 + ], + [ + -58.3929448, + -34.8049413 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3929333, + -34.8056958 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3929333, + -34.8056958 + ], + [ + -58.3925604, + -34.8061236 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3925604, + -34.8061236 + ], + [ + -58.3917149, + -34.8070982 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3917149, + -34.8070982 + ], + [ + -58.3906765, + -34.8083226 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3906765, + -34.8083226 + ], + [ + -58.3906293, + -34.8083765 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3906293, + -34.8083765 + ], + [ + -58.3905693, + -34.8089708 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3931006, + -34.8064748 + ], + [ + -58.3925604, + -34.8061236 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3925604, + -34.8061236 + ], + [ + -58.3912106, + -34.805308 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3912106, + -34.805308 + ], + [ + -58.3900486, + -34.8046158 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3900486, + -34.8046158 + ], + [ + -58.3887181, + -34.8038776 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3962631, + -34.7999826 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3962631, + -34.7999826 + ], + [ + -58.3963617, + -34.8000402 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3977629, + -34.8008817 + ], + [ + -58.3980104, + -34.8010326 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4056871, + -34.803814 + ], + [ + -58.40459, + -34.8031917 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.40459, + -34.8031917 + ], + [ + -58.4035424, + -34.8026119 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4035424, + -34.8026119 + ], + [ + -58.4024017, + -34.8019585 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4024017, + -34.8019585 + ], + [ + -58.4012571, + -34.8013012 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4003196, + -34.8007694 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003196, + -34.8007694 + ], + [ + -58.4000651, + -34.800625 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4000651, + -34.800625 + ], + [ + -58.3994936, + -34.8003009 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005863, + -34.7973807 + ], + [ + -58.3999811, + -34.7981126 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3999811, + -34.7981126 + ], + [ + -58.3994224, + -34.7987983 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3994224, + -34.7987983 + ], + [ + -58.3991078, + -34.7991665 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3991078, + -34.7991665 + ], + [ + -58.3986112, + -34.7998147 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3980853, + -34.8004779 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3980853, + -34.8004779 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4000651, + -34.800625 + ], + [ + -58.4008757, + -34.7995904 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4008757, + -34.7995904 + ], + [ + -58.4014189, + -34.7988802 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4014189, + -34.7988802 + ], + [ + -58.4020013, + -34.7981301 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003196, + -34.8007694 + ], + [ + -58.399452, + -34.8018971 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3940899, + -34.8003361 + ], + [ + -58.3945242, + -34.8006035 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001253, + -34.8060931 + ], + [ + -58.400154, + -34.8060524 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.400154, + -34.8060524 + ], + [ + -58.400195, + -34.8060195 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.400195, + -34.8060195 + ], + [ + -58.4002453, + -34.805997 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4002453, + -34.805997 + ], + [ + -58.4003011, + -34.8059864 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003011, + -34.8059864 + ], + [ + -58.4003583, + -34.8059886 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003583, + -34.8059886 + ], + [ + -58.4004127, + -34.8060034 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4004127, + -34.8060034 + ], + [ + -58.4004602, + -34.8060297 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4005021, + -34.8060719 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005021, + -34.8060719 + ], + [ + -58.4005258, + -34.8061227 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005258, + -34.8061227 + ], + [ + -58.4005291, + -34.806177 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005291, + -34.806177 + ], + [ + -58.4005117, + -34.8062295 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005117, + -34.8062295 + ], + [ + -58.4004752, + -34.806275 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4013091, + -34.8049401 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4013091, + -34.8049401 + ], + [ + -58.4015784, + -34.8046036 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4024151, + -34.8035586 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3941494, + -34.8144285 + ], + [ + -58.3947151, + -34.8135927 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3947151, + -34.8135927 + ], + [ + -58.39521, + -34.8128913 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.39521, + -34.8128913 + ], + [ + -58.3961515, + -34.8116469 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3961515, + -34.8116469 + ], + [ + -58.3966281, + -34.8110239 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3966281, + -34.8110239 + ], + [ + -58.3970574, + -34.8104706 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3970574, + -34.8104706 + ], + [ + -58.3970575, + -34.8103726 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3970575, + -34.8103726 + ], + [ + -58.3978259, + -34.8093579 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3978259, + -34.8093579 + ], + [ + -58.3985907, + -34.8083664 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3985907, + -34.8083664 + ], + [ + -58.3993749, + -34.8073699 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3993749, + -34.8073699 + ], + [ + -58.400226, + -34.8063129 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4028696, + -34.8075087 + ], + [ + -58.4015378, + -34.8068225 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4015378, + -34.8068225 + ], + [ + -58.4004752, + -34.806275 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4005424, + -34.8040087 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005424, + -34.8040087 + ], + [ + -58.399244, + -34.8055954 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.399244, + -34.8055954 + ], + [ + -58.3983081, + -34.806706 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3983081, + -34.806706 + ], + [ + -58.3974713, + -34.8077069 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3966454, + -34.8086947 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4011968, + -34.8097556 + ], + [ + -58.4000438, + -34.809165 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4000438, + -34.809165 + ], + [ + -58.3999022, + -34.8090924 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3999022, + -34.8090924 + ], + [ + -58.3998633, + -34.8090725 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3998633, + -34.8090725 + ], + [ + -58.3985907, + -34.8083664 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3985907, + -34.8083664 + ], + [ + -58.3974713, + -34.8077069 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3964129, + -34.8070746 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941477, + -34.8057278 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4005424, + -34.8040087 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4005424, + -34.8040087 + ], + [ + -58.3994805, + -34.8033989 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3994805, + -34.8033989 + ], + [ + -58.398366, + -34.8027589 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.398366, + -34.8027589 + ], + [ + -58.3971802, + -34.8020789 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3971802, + -34.8020789 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3971802, + -34.8020789 + ], + [ + -58.3969642, + -34.8023356 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3969642, + -34.8023356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3969642, + -34.8023356 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3935984, + -34.8050019 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3972354, + -34.8060837 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3983081, + -34.806706 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3983081, + -34.806706 + ], + [ + -58.3993749, + -34.8073699 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941987, + -34.8071883 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3934596, + -34.8080828 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3934596, + -34.8080828 + ], + [ + -58.393423, + -34.8082664 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.398366, + -34.8027589 + ], + [ + -58.3976747, + -34.8036356 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3976747, + -34.8036356 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4013091, + -34.8049401 + ], + [ + -58.4025396, + -34.8055669 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4025396, + -34.8055669 + ], + [ + -58.4038371, + -34.8062277 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3891946, + -34.8056311 + ], + [ + -58.3900486, + -34.8046158 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3900486, + -34.8046158 + ], + [ + -58.3904782, + -34.804065 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3904782, + -34.804065 + ], + [ + -58.3907792, + -34.8036757 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3918268, + -34.8023406 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3918268, + -34.8023406 + ], + [ + -58.3924396, + -34.8015867 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3924396, + -34.8015867 + ], + [ + -58.3931521, + -34.8007371 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3931521, + -34.8007371 + ], + [ + -58.3935142, + -34.8002063 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935142, + -34.8002063 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3938917, + -34.8002224 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3930397, + -34.7987714 + ], + [ + -58.393525, + -34.7990038 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.393525, + -34.7990038 + ], + [ + -58.3935706, + -34.7990412 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935706, + -34.7990412 + ], + [ + -58.3936001, + -34.7990765 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3936001, + -34.7990765 + ], + [ + -58.3936215, + -34.7991227 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3936215, + -34.7991227 + ], + [ + -58.3936376, + -34.7991734 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3936376, + -34.7991734 + ], + [ + -58.3936457, + -34.7992395 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3936457, + -34.7992395 + ], + [ + -58.393643, + -34.7993122 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.393643, + -34.7993122 + ], + [ + -58.3936376, + -34.7994289 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3936376, + -34.7994289 + ], + [ + -58.3935793, + -34.800023 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3986617, + -34.8014125 + ], + [ + -58.3990591, + -34.8008188 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3990591, + -34.8008188 + ], + [ + -58.3994936, + -34.8003009 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935636, + -34.8067511 + ], + [ + -58.3935559, + -34.8065149 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935559, + -34.8065149 + ], + [ + -58.3941477, + -34.8057278 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3941477, + -34.8057278 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3955515, + -34.8040306 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3930098, + -34.8116828 + ], + [ + -58.3930233, + -34.8114078 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3930233, + -34.8114078 + ], + [ + -58.3938559, + -34.8103296 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3938559, + -34.8103296 + ], + [ + -58.3948057, + -34.8091206 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3948057, + -34.8091206 + ], + [ + -58.3956137, + -34.8080702 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3964129, + -34.8070746 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3966681, + -34.8067671 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3972354, + -34.8060837 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3981728, + -34.8049644 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3987746, + -34.8042389 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3994805, + -34.8033989 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3945242, + -34.8006035 + ], + [ + -58.3939846, + -34.8012736 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3972482, + -34.8070924 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3986617, + -34.8014125 + ], + [ + -58.399452, + -34.8018971 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.399452, + -34.8018971 + ], + [ + -58.4003357, + -34.8023944 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003357, + -34.8023944 + ], + [ + -58.4003789, + -34.8024187 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4013631, + -34.8029699 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4015377, + -34.8030708 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4015377, + -34.8030708 + ], + [ + -58.4024151, + -34.8035586 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4024151, + -34.8035586 + ], + [ + -58.4026791, + -34.8037053 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4026791, + -34.8037053 + ], + [ + -58.4036115, + -34.8042237 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4036115, + -34.8042237 + ], + [ + -58.4037436, + -34.8042971 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4037436, + -34.8042971 + ], + [ + -58.4048825, + -34.804862 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3994936, + -34.8003009 + ], + [ + -58.3986112, + -34.7998147 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3972001, + -34.799019 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3972001, + -34.799019 + ], + [ + -58.3962705, + -34.798502 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3935793, + -34.800023 + ], + [ + -58.392493, + -34.7994585 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.392493, + -34.7994585 + ], + [ + -58.3917332, + -34.7990214 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3917332, + -34.7990214 + ], + [ + -58.3913394, + -34.7988011 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3913394, + -34.7988011 + ], + [ + -58.3912913, + -34.7986684 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942128, + -34.8026319 + ], + [ + -58.3952781, + -34.8013356 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3954312, + -34.8011493 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3954312, + -34.8011493 + ], + [ + -58.3963617, + -34.8000402 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3972001, + -34.799019 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3966454, + -34.8086947 + ], + [ + -58.3956137, + -34.8080702 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3941987, + -34.8071883 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3935636, + -34.8067511 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3945242, + -34.8006035 + ], + [ + -58.3954312, + -34.8011493 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3983132, + -34.8012066 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3983132, + -34.8012066 + ], + [ + -58.3986617, + -34.8014125 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3941728, + -34.7987608 + ], + [ + -58.3942795, + -34.7988258 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942795, + -34.7988258 + ], + [ + -58.3945157, + -34.7989646 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3945157, + -34.7989646 + ], + [ + -58.3954262, + -34.7994934 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001253, + -34.8060931 + ], + [ + -58.399244, + -34.8055954 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.399244, + -34.8055954 + ], + [ + -58.3981728, + -34.8049644 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3971168, + -34.8043422 + ], + [ + -58.396618, + -34.8040484 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3962705, + -34.798502 + ], + [ + -58.3954262, + -34.7994934 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3945242, + -34.8006035 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4004752, + -34.806275 + ], + [ + -58.4004221, + -34.8063094 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4004221, + -34.8063094 + ], + [ + -58.4003584, + -34.8063282 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4003584, + -34.8063282 + ], + [ + -58.4002907, + -34.8063294 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4002907, + -34.8063294 + ], + [ + -58.400226, + -34.8063129 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.400226, + -34.8063129 + ], + [ + -58.4001748, + -34.8062833 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001748, + -34.8062833 + ], + [ + -58.4001364, + -34.8062426 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001364, + -34.8062426 + ], + [ + -58.4001143, + -34.8061945 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001143, + -34.8061945 + ], + [ + -58.4001105, + -34.8061431 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.4001105, + -34.8061431 + ], + [ + -58.4001253, + -34.8060931 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4003196, + -34.8007694 + ], + [ + -58.399452, + -34.8018971 + ], + [ + -58.4003357, + -34.8023944 + ], + [ + -58.4003789, + -34.8024187 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4012571, + -34.8013012 + ], + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4015377, + -34.8030708 + ], + [ + -58.4024017, + -34.8019585 + ], + [ + -58.4012571, + -34.8013012 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ], + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3959417, + -34.8036499 + ], + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3969642, + -34.8023356 + ], + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3942128, + -34.8026319 + ], + [ + -58.395087, + -34.8031464 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3935525, + -34.8022475 + ], + [ + -58.3924396, + -34.8015867 + ], + [ + -58.3918268, + -34.8023406 + ], + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3935525, + -34.8022475 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3918268, + -34.8023406 + ], + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3929547, + -34.8030144 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3929547, + -34.8030144 + ], + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3930577, + -34.8030849 + ], + [ + -58.3929547, + -34.8030144 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3912106, + -34.805308 + ], + [ + -58.3925604, + -34.8061236 + ], + [ + -58.3929333, + -34.8056958 + ], + [ + -58.3929448, + -34.8049413 + ], + [ + -58.3919262, + -34.8043515 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3912106, + -34.805308 + ], + [ + -58.3919262, + -34.8043515 + ], + [ + -58.3907792, + -34.8036757 + ], + [ + -58.3904782, + -34.804065 + ], + [ + -58.3900486, + -34.8046158 + ], + [ + -58.3912106, + -34.805308 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3962631, + -34.7999826 + ], + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3972001, + -34.799019 + ], + [ + -58.3962705, + -34.798502 + ], + [ + -58.3954262, + -34.7994934 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3962631, + -34.7999826 + ], + [ + -58.3954262, + -34.7994934 + ], + [ + -58.3945242, + -34.8006035 + ], + [ + -58.3954312, + -34.8011493 + ], + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3962631, + -34.7999826 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3977629, + -34.8008817 + ], + [ + -58.3980853, + -34.8004779 + ], + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3972001, + -34.799019 + ], + [ + -58.3963617, + -34.8000402 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3977629, + -34.8008817 + ], + [ + -58.3963617, + -34.8000402 + ], + [ + -58.3954312, + -34.8011493 + ], + [ + -58.3952781, + -34.8013356 + ], + [ + -58.3969642, + -34.8023356 + ], + [ + -58.3971802, + -34.8020789 + ], + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3977629, + -34.8008817 + ], + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3983132, + -34.8012066 + ], + [ + -58.3986617, + -34.8014125 + ], + [ + -58.3990591, + -34.8008188 + ], + [ + -58.3994936, + -34.8003009 + ], + [ + -58.3986112, + -34.7998147 + ], + [ + -58.3980853, + -34.8004779 + ], + [ + -58.3977629, + -34.8008817 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4003196, + -34.8007694 + ], + [ + -58.4000651, + -34.800625 + ], + [ + -58.3994936, + -34.8003009 + ], + [ + -58.3990591, + -34.8008188 + ], + [ + -58.3986617, + -34.8014125 + ], + [ + -58.399452, + -34.8018971 + ], + [ + -58.4003196, + -34.8007694 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4001253, + -34.8060931 + ], + [ + -58.400154, + -34.8060524 + ], + [ + -58.400195, + -34.8060195 + ], + [ + -58.4002453, + -34.805997 + ], + [ + -58.4003011, + -34.8059864 + ], + [ + -58.4003583, + -34.8059886 + ], + [ + -58.4004127, + -34.8060034 + ], + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4013091, + -34.8049401 + ], + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4005424, + -34.8040087 + ], + [ + -58.399244, + -34.8055954 + ], + [ + -58.4001253, + -34.8060931 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.400154, + -34.8060524 + ], + [ + -58.4001253, + -34.8060931 + ], + [ + -58.4001105, + -34.8061431 + ], + [ + -58.4001143, + -34.8061945 + ], + [ + -58.4001364, + -34.8062426 + ], + [ + -58.4001748, + -34.8062833 + ], + [ + -58.400226, + -34.8063129 + ], + [ + -58.4002907, + -34.8063294 + ], + [ + -58.4003584, + -34.8063282 + ], + [ + -58.4004221, + -34.8063094 + ], + [ + -58.4004752, + -34.806275 + ], + [ + -58.4005117, + -34.8062295 + ], + [ + -58.4005291, + -34.806177 + ], + [ + -58.4005258, + -34.8061227 + ], + [ + -58.4005021, + -34.8060719 + ], + [ + -58.4004602, + -34.8060297 + ], + [ + -58.4004127, + -34.8060034 + ], + [ + -58.4003583, + -34.8059886 + ], + [ + -58.4003011, + -34.8059864 + ], + [ + -58.4002453, + -34.805997 + ], + [ + -58.400195, + -34.8060195 + ], + [ + -58.400154, + -34.8060524 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4015784, + -34.8046036 + ], + [ + -58.4024151, + -34.8035586 + ], + [ + -58.4015377, + -34.8030708 + ], + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4005424, + -34.8040087 + ], + [ + -58.4015784, + -34.8046036 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3985907, + -34.8083664 + ], + [ + -58.3993749, + -34.8073699 + ], + [ + -58.3983081, + -34.806706 + ], + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3985907, + -34.8083664 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3993749, + -34.8073699 + ], + [ + -58.400226, + -34.8063129 + ], + [ + -58.4001748, + -34.8062833 + ], + [ + -58.4001364, + -34.8062426 + ], + [ + -58.4001143, + -34.8061945 + ], + [ + -58.4001105, + -34.8061431 + ], + [ + -58.4001253, + -34.8060931 + ], + [ + -58.399244, + -34.8055954 + ], + [ + -58.3983081, + -34.806706 + ], + [ + -58.3993749, + -34.8073699 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.4005424, + -34.8040087 + ], + [ + -58.4013631, + -34.8029699 + ], + [ + -58.4003789, + -34.8024187 + ], + [ + -58.4003357, + -34.8023944 + ], + [ + -58.399452, + -34.8018971 + ], + [ + -58.3986617, + -34.8014125 + ], + [ + -58.3983132, + -34.8012066 + ], + [ + -58.3980104, + -34.8010326 + ], + [ + -58.3971802, + -34.8020789 + ], + [ + -58.398366, + -34.8027589 + ], + [ + -58.3994805, + -34.8033989 + ], + [ + -58.4005424, + -34.8040087 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.399244, + -34.8055954 + ], + [ + -58.4005424, + -34.8040087 + ], + [ + -58.3994805, + -34.8033989 + ], + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3981728, + -34.8049644 + ], + [ + -58.399244, + -34.8055954 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3983081, + -34.806706 + ], + [ + -58.399244, + -34.8055954 + ], + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3983081, + -34.806706 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3983081, + -34.806706 + ], + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3974713, + -34.8077069 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3966454, + -34.8086947 + ], + [ + -58.3974713, + -34.8077069 + ], + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3966454, + -34.8086947 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3956137, + -34.8080702 + ], + [ + -58.3964129, + -34.8070746 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3964129, + -34.8070746 + ], + [ + -58.3966681, + -34.8067671 + ], + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3941477, + -34.8057278 + ], + [ + -58.3935559, + -34.8065149 + ], + [ + -58.3935636, + -34.8067511 + ], + [ + -58.3941987, + -34.8071883 + ], + [ + -58.3949373, + -34.8061929 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3941477, + -34.8057278 + ], + [ + -58.3949373, + -34.8061929 + ], + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3941477, + -34.8057278 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3994805, + -34.8033989 + ], + [ + -58.398366, + -34.8027589 + ], + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3994805, + -34.8033989 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.398366, + -34.8027589 + ], + [ + -58.3971802, + -34.8020789 + ], + [ + -58.3969642, + -34.8023356 + ], + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3976747, + -34.8036356 + ], + [ + -58.398366, + -34.8027589 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ], + [ + -58.396618, + -34.8040484 + ], + [ + -58.3971168, + -34.8043422 + ], + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3957427, + -34.8051655 + ], + [ + -58.3972354, + -34.8060837 + ], + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3971168, + -34.8043422 + ], + [ + -58.396618, + -34.8040484 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3971168, + -34.8043422 + ], + [ + -58.3981728, + -34.8049644 + ], + [ + -58.3987746, + -34.8042389 + ], + [ + -58.3976747, + -34.8036356 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/cutedge.geojson b/packages/turf-polygonize/test/out/cutedge.geojson index 3db8b5b09c..471b419927 100644 --- a/packages/turf-polygonize/test/out/cutedge.geojson +++ b/packages/turf-polygonize/test/out/cutedge.geojson @@ -1 +1,131 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-73.9675747,40.7978699],[-73.9689288,40.7984325],[-73.9684641,40.7990578],[-73.9671143,40.7985006],[-73.9675747,40.7978699]]]}}]} \ No newline at end of file +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.96706879138947, + 40.798493169582244 + ], + [ + -73.96847426891327, + 40.79908606273021 + ], + [ + -73.96896779537201, + 40.798416011865356 + ], + [ + -73.96754086017609, + 40.797851539524295 + ], + [ + -73.96706879138947, + 40.798493169582244 + ], + [ + -73.96669328212738, + 40.79913073254739 + ], + [ + -73.96619439125061, + 40.79977641109269 + ], + [ + -73.96793782711028, + 40.80045456984621 + ], + [ + -73.96809875965117, + 40.79976016768429 + ], + [ + -73.96669328212738, + 40.79913073254739 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.96847426891327, + 40.79908606273021 + ], + [ + -73.96706879138947, + 40.798493169582244 + ], + [ + -73.96754086017609, + 40.797851539524295 + ], + [ + -73.96896779537201, + 40.798416011865356 + ], + [ + -73.96847426891327, + 40.79908606273021 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.96619439125061, + 40.79977641109269 + ], + [ + -73.96669328212738, + 40.79913073254739 + ], + [ + -73.96809875965117, + 40.79976016768429 + ], + [ + -73.96793782711028, + 40.80045456984621 + ], + [ + -73.96619439125061, + 40.79977641109269 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/dangle.geojson b/packages/turf-polygonize/test/out/dangle.geojson index 3aa0a428ce..7e6d7fbe25 100644 --- a/packages/turf-polygonize/test/out/dangle.geojson +++ b/packages/turf-polygonize/test/out/dangle.geojson @@ -1 +1,79 @@ -{"type":"FeatureCollection","features":[]} \ No newline at end of file +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -73.96755695343018, + 40.797867783399724 + ], + [ + -73.96667718887329, + 40.7991429152196 + ], + [ + -73.96620512008666, + 40.799743924271894 + ], + [ + -73.96793246269226, + 40.800482995510926 + ], + [ + -73.96811485290527, + 40.79971143743523 + ], + [ + -73.96667718887329, + 40.7991429152196 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -73.96620512008666, + 40.799743924271894 + ], + [ + -73.96667718887329, + 40.7991429152196 + ], + [ + -73.96811485290527, + 40.79971143743523 + ], + [ + -73.96793246269226, + 40.800482995510926 + ], + [ + -73.96620512008666, + 40.799743924271894 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/kinked-linestring.geojson b/packages/turf-polygonize/test/out/kinked-linestring.geojson new file mode 100644 index 0000000000..a560385685 --- /dev/null +++ b/packages/turf-polygonize/test/out/kinked-linestring.geojson @@ -0,0 +1,107 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 131.9677734375, + -20.550508894195637 + ], + [ + 134.2529296875, + -16.59408141271846 + ], + [ + 123.3984375, + -30.48655084258847 + ], + [ + 130.1220703125, + -31.05293398570514 + ], + [ + 121.9482421875, + -22.471954507739213 + ], + [ + 132.275390625, + -25.324166525738384 + ], + [ + 129.0673828125, + -16.299051014581817 + ], + [ + 133.9453125, + -13.710035342476669 + ], + [ + 131.9677734375, + -20.550508894195637 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 131.9677734375, + -20.550508894195637 + ], + [ + 134.2529296875, + -16.59408141271846 + ], + [ + 123.3984375, + -30.48655084258847 + ], + [ + 130.1220703125, + -31.05293398570514 + ], + [ + 121.9482421875, + -22.471954507739213 + ], + [ + 132.275390625, + -25.324166525738384 + ], + [ + 129.0673828125, + -16.299051014581817 + ], + [ + 133.9453125, + -13.710035342476669 + ], + [ + 131.9677734375, + -20.550508894195637 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/linestrings.geojson b/packages/turf-polygonize/test/out/linestrings.geojson new file mode 100644 index 0000000000..4243da1c7d --- /dev/null +++ b/packages/turf-polygonize/test/out/linestrings.geojson @@ -0,0 +1,129 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 119.00390625, + -22.024545601240337 + ], + [ + 120.58593749999999, + -28.613459424004414 + ], + [ + 125.595703125, + -32.99023555965107 + ], + [ + 133.330078125, + -32.99023555965107 + ], + [ + 142.646484375, + -30.977609093348676 + ], + [ + 142.294921875, + -24.126701958681668 + ], + [ + 139.04296875, + -16.299051014581817 + ], + [ + 128.84765625, + -15.199386048559994 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + 142.646484375, + -30.977609093348676 + ], + [ + 132.451171875, + -27.449790329784214 + ], + [ + 128.671875, + -23.1605633090483 + ], + [ + 119.00390625, + -22.024545601240337 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 120.58593749999999, + -28.613459424004414 + ], + [ + 119.00390625, + -22.024545601240337 + ], + [ + 128.671875, + -23.1605633090483 + ], + [ + 132.451171875, + -27.449790329784214 + ], + [ + 142.646484375, + -30.977609093348676 + ], + [ + 133.330078125, + -32.99023555965107 + ], + [ + 125.595703125, + -32.99023555965107 + ], + [ + 120.58593749999999, + -28.613459424004414 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/test/out/two-polygons.geojson b/packages/turf-polygonize/test/out/two-polygons.geojson index ff92101f3b..acbe581ba3 100644 --- a/packages/turf-polygonize/test/out/two-polygons.geojson +++ b/packages/turf-polygonize/test/out/two-polygons.geojson @@ -1 +1,309 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3959417,-34.8036499],[-58.395087,-34.8031464],[-58.3942164,-34.8042266],[-58.3949969,-34.8047067],[-58.3957427,-34.8051655],[-58.396618,-34.8040484],[-58.3959417,-34.8036499]]]}},{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-58.3964727,-34.8029764],[-58.3959417,-34.8036499],[-58.396618,-34.8040484],[-58.3971168,-34.8043422],[-58.3976747,-34.8036356],[-58.3964727,-34.8029764]]]}}]} \ No newline at end of file +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3957427, + -34.8051655 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3971168, + -34.8043422 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.3971168, + -34.8043422 + ], + [ + -58.396618, + -34.8040484 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3959417, + -34.8036499 + ], + [ + -58.395087, + -34.8031464 + ], + [ + -58.3942164, + -34.8042266 + ], + [ + -58.3949969, + -34.8047067 + ], + [ + -58.3957427, + -34.8051655 + ], + [ + -58.396618, + -34.8040484 + ], + [ + -58.3959417, + -34.8036499 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -58.3964727, + -34.8029764 + ], + [ + -58.3959417, + -34.8036499 + ], + [ + -58.396618, + -34.8040484 + ], + [ + -58.3971168, + -34.8043422 + ], + [ + -58.3976747, + -34.8036356 + ], + [ + -58.3964727, + -34.8029764 + ] + ] + ] + } + } + ] +} diff --git a/packages/turf-polygonize/yarn.lock b/packages/turf-polygonize/yarn.lock index 2247d7d871..c8881aefee 100644 --- a/packages/turf-polygonize/yarn.lock +++ b/packages/turf-polygonize/yarn.lock @@ -35,6 +35,14 @@ version "4.3.0" resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-4.3.0.tgz#5bd1ce6ae51b1229dc0dc7d09d973fabae49af89" +"@turf/line-segment@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@turf/line-segment/-/line-segment-4.3.0.tgz#7835209227f3c0712ec549c6f51af72394c638ab" + dependencies: + "@turf/helpers" "^4.3.0" + "@turf/invariant" "^4.3.0" + "@turf/meta" "^4.3.0" + "@turf/meta@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.3.0.tgz#eb11dd2c2511524258123767fe0f5c3bd963e8d7" From fab043f01b7297eb5d8a7adfe66dbd91cf5442b5 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 May 2017 10:39:57 -0400 Subject: [PATCH 32/36] Update typescript definition --- packages/turf-polygonize/index.d.ts | 5 ++--- packages/turf-polygonize/index.js | 8 ++++---- packages/turf-polygonize/test.js | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/turf-polygonize/index.d.ts b/packages/turf-polygonize/index.d.ts index 2087e03db9..271e7bd987 100644 --- a/packages/turf-polygonize/index.d.ts +++ b/packages/turf-polygonize/index.d.ts @@ -1,12 +1,11 @@ /// -type Lines = GeoJSON.FeatureCollection; type Polygons = GeoJSON.FeatureCollection; +type Geoms = GeoJSON.LineString | GeoJSON.MultiLineString; /** * http://turfjs.org/docs/#polygonize */ -declare function polygonize(lines: Lines): Polygons; - +declare function polygonize(geojson: GeoJSON.Feature | GeoJSON.FeatureCollection | Geom): Polygons; declare namespace polygonize { } export = polygonize; diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index eeca8aa778..6e861fd6f1 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -12,9 +12,9 @@ var lineSegment = require('@turf/line-segment'); * - Cut Edges (bridges): edges that are connected at both ends but which do not form part of a polygon. * * @name polygonize - * @param {FeatureCollection|Geometry|Feature} lines Lines in order to polygonize - * @returns {FeatureCollection} Polygons created + * @param {FeatureCollection|Geometry|Feature} geojson (Multi)LineStrings in order to polygonize + * @returns {FeatureCollection} Polygons created from connected LineStrings */ -module.exports = function (lines) { - return polygonize(lineSegment(lines)); +module.exports = function (geojson) { + return polygonize(lineSegment(geojson)); }; diff --git a/packages/turf-polygonize/test.js b/packages/turf-polygonize/test.js index b825c6be33..19714ab8cc 100644 --- a/packages/turf-polygonize/test.js +++ b/packages/turf-polygonize/test.js @@ -23,8 +23,8 @@ const fixtures = fs.readdirSync(directories.in).map(filename => { test('turf-polygonize', t => { for (const {filename, name, geojson} of fixtures) { const polygonized = polygonize(geojson); - const results = featureCollection([]); + const results = featureCollection([]); featureEach(geojson, feature => results.features.push(colorize(feature))); featureEach(polygonized, feature => results.features.push(colorize(feature, '#00F', 3))); From 614723b6cca6d531bd5585c528a2ec4a788f5c8c Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 May 2017 10:42:03 -0400 Subject: [PATCH 33/36] Add MultiLineString Fixture --- .../test/in/multi-linestring.geojson | 45 +++++++++ .../test/out/multi-linestring.geojson | 99 +++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 packages/turf-polygonize/test/in/multi-linestring.geojson create mode 100644 packages/turf-polygonize/test/out/multi-linestring.geojson diff --git a/packages/turf-polygonize/test/in/multi-linestring.geojson b/packages/turf-polygonize/test/in/multi-linestring.geojson new file mode 100644 index 0000000000..80a2cc434a --- /dev/null +++ b/packages/turf-polygonize/test/in/multi-linestring.geojson @@ -0,0 +1,45 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [ + 126.3427734375, + -17.392579271057766 + ], + [ + 129.111328125, + -29.19053283229457 + ], + [ + 132.890625, + -31.01527898171125 + ], + [ + 136.7138671875, + -29.11377539511439 + ] + ], + [ + [ + 126.3427734375, + -17.392579271057766 + ], + [ + 132.978515625, + -15.368949896534705 + ], + [ + 135.966796875, + -24.126701958681668 + ], + [ + 136.7138671875, + -29.11377539511439 + ] + ] + ] + } +} \ No newline at end of file diff --git a/packages/turf-polygonize/test/out/multi-linestring.geojson b/packages/turf-polygonize/test/out/multi-linestring.geojson new file mode 100644 index 0000000000..6cd4d6925d --- /dev/null +++ b/packages/turf-polygonize/test/out/multi-linestring.geojson @@ -0,0 +1,99 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "fill": "#F00", + "fill-opacity": 0.3, + "stroke": "#F00", + "stroke-width": 6 + }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [ + 126.3427734375, + -17.392579271057766 + ], + [ + 129.111328125, + -29.19053283229457 + ], + [ + 132.890625, + -31.01527898171125 + ], + [ + 136.7138671875, + -29.11377539511439 + ] + ], + [ + [ + 126.3427734375, + -17.392579271057766 + ], + [ + 132.978515625, + -15.368949896534705 + ], + [ + 135.966796875, + -24.126701958681668 + ], + [ + 136.7138671875, + -29.11377539511439 + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "fill": "#00F", + "fill-opacity": 0.3, + "stroke": "#00F", + "stroke-width": 3 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 129.111328125, + -29.19053283229457 + ], + [ + 126.3427734375, + -17.392579271057766 + ], + [ + 132.978515625, + -15.368949896534705 + ], + [ + 135.966796875, + -24.126701958681668 + ], + [ + 136.7138671875, + -29.11377539511439 + ], + [ + 132.890625, + -31.01527898171125 + ], + [ + 129.111328125, + -29.19053283229457 + ] + ] + ] + } + } + ] +} From 54f63c761df746ed7cd8c43dc1846baeab6ffb8c Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Wed, 31 May 2017 15:21:06 -0300 Subject: [PATCH 34/36] Updated bench results --- packages/turf-polygonize/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 6e861fd6f1..4dc5a609ff 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -6,14 +6,18 @@ var lineSegment = require('@turf/line-segment'); * * Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). * + * Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly + * noded, i.e., they must only meet at their endpoints. + * * The implementation correctly handles: * * - Dangles: edges which have one or both ends which are not incident on another edge endpoint. * - Cut Edges (bridges): edges that are connected at both ends but which do not form part of a polygon. * * @name polygonize - * @param {FeatureCollection|Geometry|Feature} geojson (Multi)LineStrings in order to polygonize - * @returns {FeatureCollection} Polygons created from connected LineStrings + * @param {FeatureCollection|Geometry|Feature} geojson Lines in order to polygonize + * @returns {FeatureCollection} Polygons created + * @throws {Error} if geoJson is invalid. */ module.exports = function (geojson) { return polygonize(lineSegment(geojson)); From 51b88c09e7b7359272803c0f3e3852bbbbb90663 Mon Sep 17 00:00:00 2001 From: Nicolas Cisco Date: Wed, 31 May 2017 16:07:16 -0300 Subject: [PATCH 35/36] Upgraded poligonize version --- packages/turf-polygonize/README.md | 8 +++++++- packages/turf-polygonize/index.js | 5 +---- packages/turf-polygonize/package.json | 3 +-- packages/turf-polygonize/yarn.lock | 16 +++++----------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/turf-polygonize/README.md b/packages/turf-polygonize/README.md index 1deafeb5c7..25f0643991 100644 --- a/packages/turf-polygonize/README.md +++ b/packages/turf-polygonize/README.md @@ -6,6 +6,9 @@ Polygonizes [(Multi)LineString(s)](http://geojson.org/geojson-spec.html#linestri Implementation of GEOSPolygonize function (`geos::operation::polygonize::Polygonizer`). +Polygonizes a set of lines that represents edges in a planar graph. Edges must be correctly +noded, i.e., they must only meet at their endpoints. + The implementation correctly handles: - Dangles: edges which have one or both ends which are not incident on another edge endpoint. @@ -13,7 +16,10 @@ The implementation correctly handles: **Parameters** -- `lines` **([FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects) \| [Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<([LineString](http://geojson.org/geojson-spec.html#linestring) \| [MultiLineString](http://geojson.org/geojson-spec.html#multilinestring))>)** Lines in order to polygonize +- `geojson` **([FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects) \| [Geometry](http://geojson.org/geojson-spec.html#geometry) \| [Feature](http://geojson.org/geojson-spec.html#feature-objects)<([LineString](http://geojson.org/geojson-spec.html#linestring) \| [MultiLineString](http://geojson.org/geojson-spec.html#multilinestring))>)** Lines in order to polygonize + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** if geoJson is invalid. Returns **[FeatureCollection](http://geojson.org/geojson-spec.html#feature-collection-objects)<[Polygon](http://geojson.org/geojson-spec.html#polygon)>** Polygons created diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index 4dc5a609ff..ff303a6d8b 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -1,5 +1,4 @@ var polygonize = require('polygonize'); -var lineSegment = require('@turf/line-segment'); /** * Polygonizes {@link LineString|(Multi)LineString(s)} into {@link Polygons}. @@ -19,6 +18,4 @@ var lineSegment = require('@turf/line-segment'); * @returns {FeatureCollection} Polygons created * @throws {Error} if geoJson is invalid. */ -module.exports = function (geojson) { - return polygonize(lineSegment(geojson)); -}; +module.exports = polygonize; diff --git a/packages/turf-polygonize/package.json b/packages/turf-polygonize/package.json index 9b6699fd59..a5dc30f7b3 100644 --- a/packages/turf-polygonize/package.json +++ b/packages/turf-polygonize/package.json @@ -41,7 +41,6 @@ "write-json-file": "^2.2.0" }, "dependencies": { - "@turf/line-segment": "^4.3.0", - "polygonize": "^1.0.0" + "polygonize": "^1.0.1" } } diff --git a/packages/turf-polygonize/yarn.lock b/packages/turf-polygonize/yarn.lock index c8881aefee..6693ab397d 100644 --- a/packages/turf-polygonize/yarn.lock +++ b/packages/turf-polygonize/yarn.lock @@ -35,14 +35,6 @@ version "4.3.0" resolved "https://registry.yarnpkg.com/@turf/invariant/-/invariant-4.3.0.tgz#5bd1ce6ae51b1229dc0dc7d09d973fabae49af89" -"@turf/line-segment@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@turf/line-segment/-/line-segment-4.3.0.tgz#7835209227f3c0712ec549c6f51af72394c638ab" - dependencies: - "@turf/helpers" "^4.3.0" - "@turf/invariant" "^4.3.0" - "@turf/meta" "^4.3.0" - "@turf/meta@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-4.3.0.tgz#eb11dd2c2511524258123767fe0f5c3bd963e8d7" @@ -256,13 +248,15 @@ platform@^1.3.3: version "1.3.4" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" -polygonize@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/polygonize/-/polygonize-1.0.0.tgz#88342648c82da26f9132274f27a3c4b9fb33f33e" +polygonize@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/polygonize/-/polygonize-1.0.1.tgz#51fb7040914be0fbc43b0bd54d421d75fc2ae7a6" dependencies: "@turf/envelope" "^4.3.0" "@turf/helpers" "^4.3.0" "@turf/inside" "^4.3.0" + "@turf/invariant" "^4.3.0" + "@turf/meta" "^4.3.0" resolve@~1.1.7: version "1.1.7" From 22986576646dd47537d6787247bb7ac54ddf17c7 Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 1 Jun 2017 02:07:26 -0400 Subject: [PATCH 36/36] Keep module as a function --- packages/turf-polygonize/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/turf-polygonize/index.js b/packages/turf-polygonize/index.js index ff303a6d8b..e7dae45e4e 100644 --- a/packages/turf-polygonize/index.js +++ b/packages/turf-polygonize/index.js @@ -16,6 +16,8 @@ var polygonize = require('polygonize'); * @name polygonize * @param {FeatureCollection|Geometry|Feature} geojson Lines in order to polygonize * @returns {FeatureCollection} Polygons created - * @throws {Error} if geoJson is invalid. + * @throws {Error} if GeoJSON is invalid. */ -module.exports = polygonize; +module.exports = function (geojson) { + return polygonize(geojson); +};