jazzband / geojson

Python bindings and utilities for GeoJSON
https://pypi.python.org/pypi/geojson/
BSD 3-Clause "New" or "Revised" License
908 stars 120 forks source link

Counter-clockwise polygons and dateline-crossing bounding boxen #37

Open sgillies opened 10 years ago

sgillies commented 10 years ago

@frewsxcv 2 draft-geojson PRs have possible consequences for python-geojson: https://github.com/geojson/draft-geojson/pull/42, https://github.com/geojson/draft-geojson/pull/42. The first recommends counter-clockwise winding for exterior rings of polygons (aka right-hand rule) and the second makes a recommendation that bounding boxes be [west, south, east, north] instead of [min(long), min(lat), max(long), max(lat)] to make dealing with the dateline more sane. Any thoughts or comments?

A function to wind/rewind polygons accordingly could be a good feature for python-geojson.

frewsxcv commented 10 years ago

Sounds great. I'm a bit busy these couple weeks, but I'll get to it when I get the chance. If anyone else reads this and wants to do it earlier, I'll gladly accept pull requests :)

Guts commented 7 years ago

I'm pretty interested in that feature.

I read that's possible with utils.map_coords. Is that right? Maybe I can help but I don't know how...

ChrisBarker-NOAA commented 6 years ago

I just came here looking for just this feature. It would be good for the .valid attribute to check for ccw.

And (maybe optionally) it could be fixed when creating the Feature

Here is some code for checking winding order:

https://gist.github.com/ChrisBarker-NOAA/6fc7de7557856c1351a27df2bb281df5

maybe I'll do a PR one of these days -- but how/where would you like it done?

Automatically?

Checking in .valid?

Only when asked? (i.e. a fix_winding() method)

ChrisBarker-NOAA commented 6 years ago

NOTE: I was using my python code in the gist just now, and for one case of a lot of large polygons -- it's pretty darn slow:

writing the geojson went from 624 ms to 2.4 s if I added the is_clockwise() check.

So probably better not to have it done automatically.

rayrrr commented 5 years ago

Hi @ChrisBarker-NOAA, new project lead here. Thanks for your input on this! It guided me in a good direction, and it turns out a more efficient algorithm is available.

It's all explained in the Curve Orientation article on Wikipedia, in the "practical considerations" section.

TL;DR: Regardless of a given linear ring's cardinality, only 3 (well-chosen) points are needed to determine its winding order: a point on the convex hull and its two immediate neighbors. Also, a point with the minimum longitude/latitude in the set is guaranteed to be on the convex hull, so these 3 points are trivial to choose.

In other words, the simpler formula in your "convex" function can actually work in all cases, just by choosing the correct three vertices to use from any well-formed polygon.

For now, here is a function implementing the above which I plan to add to the codebase soon, for you to try out. Note, the input parameter is a "linear ring" in GeoJSON parlance so that's what I'm calling it.

def is_clockwise(lring):
    """
    Determine winding order based on minimum lng/lat
    coordinate and its immediate neighbors.
    :param lring: a linear ring from a Polygon
    :type lring: _linear_ring
    :return: whether the linear ring has clockwise winding order
    :rtype: boolean
    """
    lring.pop()  # remove last-same-as-first coord pair
    x = lring.index(min(lring[:-1]))  # index of min coords (on convex hull)
    det = ((lring[x][0] - lring[x-1][0]) * (lring[x+1][1] - lring[x-1][1]) 
        - (lring[x+1][0] - lring[x-1][0]) * (lring[x][1] - lring[x-1][1]))
    return det < 0

I got about a 25% performance boost with this function on a trivial example compared to your gist; please let us know how it works on one of your larger use cases if you can.

Tafkas commented 4 years ago

Any updates on this?