GithubHelp home page GithubHelp logo

mclaeysb / simplepolygon Goto Github PK

View Code? Open in Web Editor NEW
77.0 5.0 11.0 194 KB

JS tool to break self-intersecting GeoJSON polygons down in their constituent non-self-intersecting parts

License: MIT License

JavaScript 100.00%

simplepolygon's Introduction

simplepolygon

Takes a complex (i.e. self-intersecting) GeoJSON polygon, and breaks it down into its composite simple, non-self-intersecting one-ring polygons.

The algorithm is based on a thesis submitted by Lavanya Subramaniam: Subramaniam, Lavanya. Partition of a Non-simple Polygon Into Simple Polygons. Diss. University of South Alabama, 2003, which can be found here and features a C/C++ implementation for polygons with one self-intersecting ring.

Here, the algorithm was implemented in JavaScript, and was extended to work with GeoJSON polygons, in the sense that it can handle outer and inner rings self- and cross-intersecting.

How to use it

Install Node.js, then simply use the node package manager 'npm' to

npm install simplepolygon

In your javascript, you can then use simplepolygon like so:

var simplepolygon = require('simplepolygon')

var poly = {
"type": "Feature",
 "geometry": {
   "type": "Polygon",
   "coordinates": [[[0,0],[2,0],[0,2],[2,2],[0,0]]]
 }
};
var result = simplepolygon(poly)

The input feature is a GeoJSON polygon which may be non-conform the Simple Features standard in the sense that it's inner and outer rings may cross-intersect or self-intersect, that the outer ring must not contain the optional inner rings and that the winding number must not be positive for the outer and negative for the inner rings.

The output is a FeatureCollection containing the simple, non-self-intersecting one-ring polygon features that the complex polygon is composed of. These simple polygons have properties such as their parent polygon, winding number and net winding number.

In the above example, the output will be a FeatureCollection of two polygons, one with coordinates [[[0,0],[2,0],[1,1],[0,0]]], parent -1, winding 1 and net winding 1, and one with coordinates [[[1,1],[0,2],[2,2],[1,1]]], parent -1, winding -1 and net winding -1.

Another example input and output is shown below.

How the algorithm works

This algorithm employs the notion of intersections and pseudo-vertices as outlined in the article.

Intersections are either vertices of an input ring (ring-vertex-intersection) or a self- or cross-intersection of those ring(s) (self-intersection).

Pseudo-vertices are a concepts that occur at an intersection: when two edges cross, one pseudo-vertex is formed by the first edge going up to and until the intersection vertex and the second edge going out from the intersection. A second pseudo vertex is formed reciprocally. Two such intersection-pseudo-vertices are depicted below.

Also, at each input ring vertices one pseudo-vertex (ring-pseudo-vertex) is created by one egde going in and the other going out.

This algorithm walks from intersections to intersection over (rings and) edges in their original direction, and while walking traces simple, non-self-intersecting one-ring polygons by storing the vertices along the way. This is possible since each intersection is first taught (using the pseudo-vertices) which is the next one given the incoming walking edge. When walking, the algorithm also stores where it has walked (since we must only walk over each (part of an) edge once), and keeps track of intersections that are new and from where another walk (and hence simple polygon) could be initiated. The resulting simple, one-ring polygons cover all input edges exactly once and don't self- or cross-intersect (but can touch at intersections). Hence, they form a set of nested rings.

Some notes on the algorithm:

  • We will talk about rings (arrays of [x,y]) and polygons (array of rings). The GeoJSON spec requires rings to be non-self- and non-cross-intersecting, but here the input rings can self- and cross-intersect (inter and intra ring). The output rings can't, since they are conform the spec. Therefore will talk about input rings or simply rings (non-conform), output rings (conform) and more generally simple, non-self or cross-intersecting rings (conform)
  • We say that a polygon self-intersects when it's rings either self-intersect of cross-intersect
  • Edges are oriented from their first to their second ring vertex. Hence, ring i edge j goes from vertex j to j+1. This direction or orientation of an edge is kept unchanged during the algorithm. We will only walk along this direction
  • We use the terms ring edge, ring vertex, self-intersection vertex, intersection (which includes ring-vertex-intersection and self-intersection) and pseudo-vertex (which includes ring-pseudo-vertex and intersection-pseudo-vertex)
  • At an intersection of two edges, two pseudo-vertices (intersection-pseudo-vertices) are one intersection (self-intersection) is present
  • At a ring vertex, one pseudo-vertex (ring-pseudo-vertex) and one intersection (ring-intersection) is present
  • A pseudo-vertex has an incoming and outgoing (crossing) edge
  • The following objects are stored and passed by the index in the list between brackets: intersections (isectList) and pseudo-vertices (pseudoVtxListByRingAndEdge)
  • The algorithm checks if the input has no non-unique vertices. This is mainly to prevent self-intersecting input polygons such as [[0,0],[2,0],[1,1],[0,2],[1,3],[2,2],[1,1],[0,0]], whose self-intersections would not be detected. As such, many polygons which are non-simple, by the OGC definition, for other reasons then self-intersection, will not be allowed. An exception includes polygons with spikes or cuts such as [[0, 0], [2, 0], [0, 2], [4, 2], [2, 2], [0, 0]], who are currently allowed and treated correctly, but make the output non-simple (by OGC definition). This could be prevented by checking for vertices on other edges.
  • The resulting component polygons are one-ring and simple (in the sense that their ring does not contain self-intersections) and two component simple polygons are either disjoint, touching in one or multiple vertices, or one fully encloses the other
  • This algorithm takes GeoJSON as input, be was developed for a euclidean (and not geodesic) setting. If used in a geodesic setting, the most important consideration to make is the computation of intersection points (which is practice is only an issue of the line segments are relatively long). Further we also note that winding numbers for area's larger than half of the globe are sometimes treated specially. All other concepts of this algorithm (convex angles, direction, ...) can be ported to a geodesic setting without problems.
  • Since v1.1.1, spatial indexes are used in the underlying computation of edge intersections and throughout the algorithm, to dramatically speed up the computations in case of large polygons

Differences with the original article

This code differs from the algorithm and nomenclature of the article it is inspired on in the following way:

  • The code was written based on the article text, and not ported directly from the enclosed C/C++ code
  • This implementation expanded the algorithm to polygons containing inner and outer rings
  • No constructors are used, except PseudoVtx and Isect
  • Some variables are named differently: edges is called LineSegments in the article, ringAndEdgeOut is l, PseudoVtx is nVtx, Isect is intersection, nxtIsectAlongEdgeIn is index, ringAndEdge1 and ringAndEdge2 are origin1 and origin2, pseudoVtxListByRingAndEdge is polygonEdgeArray, isectList is intersectionList and isectQueue is intersectioQueue
  • pseudoVtxListByRingAndEdge contains the ring vertex at its end as the last item, and not the ring vertex at its start as the first item
  • winding is not implemented as a property of an intersection, but as its own queue

simplepolygon's People

Contributors

deniscarriere avatar ffflabs avatar mclaeysb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

simplepolygon's Issues

allIsectsAsIsectRbushTreeItem is not declared

@mclaeysb

In line

allIsectsAsIsectRbushTreeItem = [];

Variable allIsectsAsIsectRbushTreeItem isn't declared as var, let or const.

In some environments this amounts for the variable becoming global. Linters complain about it being undeclared, and QUnit doesn't pass tests if

QUnit.config.noglobals = true; config option is used.

Do you think you could declare this variable without side effects?

documentation outdated?

Hi,

This tool is really helpful to me, thanks a lot!

I have a question regarding an edge case mentioned in the README.md:

An exception includes polygons with spikes or cuts such as [[0,0],[2,0],[1,1],[2,2],[0,2],[1,1],[0,0]], who
are currently allowed and treated correctly, but make the output non-simple (by OGC definition).

This example is rejected in 1.1.7. Try to run this:

var feature, simplepolygon;

simplepolygon = require("simplepolygon");

feature = {
  type: "Feature",
  geometry: {
    type: "Polygon",
    coordinates: [[0, 0], [2, 0], [1, 1], [2, 2], [0, 2], [1, 1], [0, 0]]
  }
};

simplepolygon(feature);

Is this a bug? Or is the README outdated?

Clarify robustness / edge cases handling

@mclaeysb Thank you for the library!

I'm quite interested in the Subramaniam algorithm, but the paper is quite vague on details โ€” e.g. it's not clear what order the edges should be processed if there's more than 1 intersection in the same point, and doesn't cover degenerate cases like collinear edges.

Does the algorithm handle cases like this? Are there any known limitations to the input for it to be processed properly?

P.S. Feel free to close the issue if it's not actionable.

Self intersecting polygon -- some sub regions not getting detected

Dear Manuel, I am really excited about integrating simplepolygon into a locus-drawing js browser app I've been developing over the past few months. It draws loci of centers of families of triangles "mounted" on ellipses.

Browserifying simplepolygon worked like a charm.

However I am having a few issues with the results returned by simplepolygon. Consider first of all one 800-long sample polygon (it's actually a curve approximation), you can see it live here: https://bit.ly/2Zg7Mv1

x14 raw

As you can see, the above curve has 6 self intersections and 7 simple subregions. The coloring scheme below reveals the independent subregions returned by simplepolygon. unfortunately, only 3 are returned. Any idea why?

x14 filled

For your own testing I am attaching a zipped json w the 800 vertices:

vtx-X14.zip

One Step Closer

Just noticed some positive changes in v1.2.0.

Still not 100% there yet, but better than before, only splits 1 geometry.

Using this complex.geojson

image

Before to v1.2.0

image

Now v1.2.0

image

process.hrtime not available in browser

process.hrtime is not a valid function in the browser.

Have this ever been tested in the browser? Tests are passing fine in NodeJS.

https://github.com/mclaeysb/simplepolygon/search?utf8=%E2%9C%93&q=process.hrtime&type=

Might be handy to set up a simple /docs folder with an index.html and a browser friendly build to simply test this library in the browser. Here's a "minimalistic" approach I've been doing for some of my libraries https://github.com/DenisCarriere/slippy-tile/tree/master/docs.

I can send a PR to include a simple Rollup config that builds the bundle, let me know.

For this type of debugging/timings, you could look into including debug for your internal testing/debugging.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.