Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GeoJSON right-hand rule #16

Open
dchaffin opened this issue Apr 21, 2020 · 4 comments
Open

GeoJSON right-hand rule #16

dchaffin opened this issue Apr 21, 2020 · 4 comments

Comments

@dchaffin
Copy link

dchaffin commented Apr 21, 2020

I'm trying to use the union function to combine some shapes. The GeoJSONReader outputs each shape individually correct, but if I run union on them, the output doesn't follow the 'right-hand rule' (https://mapster.me/right-hand-rule-geojson-fixer/).

EDIT: I'm using MySQL for the GeometryEngine

Am I missing something or does this not support that yet? Thanks!

@BenMorel
Copy link
Member

Hi, could you please provide a sample code and expected/actual output?

@dchaffin
Copy link
Author

Thanks! Here is a quick example ...

$test01 = '{"type":"MultiPolygon","coordinates":[[[[107,7],[108,7],[108,8],[107,8],[107,7]]],[[[100,0],[101,0],[101,1],[100,1],[100,0]]]]}';
$test02 = '{"type":"MultiPolygon","coordinates":[[[[127,27],[128,27],[128,28],[127,28],[127,27]]],[[[120,20],[121,20],[121,21],[120,21],[120,20]]]]}';
$s1 = $reader->read($test01);
$s2 = $reader->read($test02);
$sOut1 = $s1->union($s2);
// This output doesn't appear to follow the right-hand rule
dpm($writer->write($sOut1));
    
// If I use this other library to "rewind", it works
$sOut2 = \Vicchi\GeoJson\Rewind::rewind((array)json_decode($writer->write($sOut1)), true);
$sOut2 = $reader->read(json_encode($sOut2));
dpm($writer->write($sOut2));

NOTE: The dpm() function is just a quick way to output on screen in Drupal.

The first example outputs:

{"type":"MultiPolygon","coordinates":[[[[100,0],[100,1],[101,1],[101,0],[100,0]]],[[[107,7],[107,8],[108,8],[108,7],[107,7]]],[[[120,20],[120,21],[121,21],[121,20],[120,20]]],[[[127,27],[127,28],[128,28],[128,27],[127,27]]]]}
The second outputs:

{"type":"MultiPolygon","coordinates":[[[[100,0],[101,0],[101,1],[100,1],[100,0]]],[[[107,7],[108,7],[108,8],[107,8],[107,7]]],[[[120,20],[121,20],[121,21],[120,21],[120,20]]],[[[127,27],[128,27],[128,28],[127,28],[127,27]]]]}

If you put these into the validator at: http://geojsonlint.com, the first output comes up with the message: "Polygons and MultiPolygons should follow the right-hand rule"

@BenMorel
Copy link
Member

Good point, I don't think this is enforced at the moment. I don't have this on top of my head, but I'm not even sure we enforce the clockwise/counterclockwise for polygons at all in this library.

I'm surprised though, that MySQL does not output a valid geometry following the right-hand rule, could you please check the raw output of ST_Union()?

@dchaffin
Copy link
Author

Sorry, I've never worked with ST_Union in MySQL like that ... took me a minute to figure out. But I think I did it right with the example info above. I ran:

SELECT ST_AsText(
    ST_UNION(
        ST_GeomFromText('MultiLineString((107 7,108 7,108 8,107 8,107 7),(100 0,101 0,101 1,100 1,100 0))'),
        ST_GeomFromText('MultiLineString((127 27,128 27,128 28,127 28,127 27),(120 20,121 20,121 21,120 21,120 20))')
    ))

And it generated:

MULTILINESTRING((100 0,100 1,101 1,101 0,100 0),(107 7,107 8,108 8,108 7,107 7),(120 20,120 21,121 21,121 20,120 20),(127 27,127 28,128 28,128 27,127 27))

That looks to me to match the first output above.

I had initially that other library above to "rewind" it, but that still left me with a "MultiPolygon" object that wasn't playing well with my Google Maps.

I ended up just taking the output of the union and calling array_reverse on the first item in each array and building my own object to then re-serialize with json_encode...

\Brick\Geo\Engine\GeometryEngineRegistry::set(new \Brick\Geo\Engine\PDOEngine($pdo));
$reader = new \Brick\Geo\IO\GeoJSONReader();

$shapeOutput = null;
foreach ($shapes as $shape){
  $s = $reader->read(json_encode($shape));
  if(is_null($shapeOutput)){
    $shapeOutput = $s;
  } else {
    $shapeOutput = $shapeOutput->union($s);
  }
}

$output = [
  'type' => 'FeatureCollection',
  'features' => [],
];

foreach ($shapeOutput->toArray() as $feature) {
  $feature[0] = array_reverse($feature[0]);
  $output['features'][] = [
    'type' => 'Feature',
    'properties' => (object)[ 'name' => 'TEST' ],
    'geometry' => (object)[
      'type' => 'Polygon',
      'coordinates' => $feature,
    ],
  ];
}

Each "shape" in that "shapes" array is a PHP object like:

Array
(
    [type] => FeatureCollection
    [features] => Array
        (
            [0] => Array
                (
                    [type] => Feature
                    [properties] => stdClass Object
                        (
                            [__CLASS__] => stdClass
                            [name] => zip-40004
                        )

                    [geometry] => stdClass Object
                        (
                            [__CLASS__] => stdClass
                            [type] => Polygon
                            [coordinates] => Array
                                (
                                    [0] => Array
                                        (
                                            [0] => Array(2)
                                            ........ repeat coordinates...

There's probably still some refactoring to do there, but it's working so far. In my case, I'm dealing with county and zip code shapes from the US Census which appear to consist of one or more polygons for the outside and then zero or more for any holes. Only the outermost (first one) seems to need reversed so far as I've seen.

I did find this information for MS SQL which may or may not be helpful...
https://bertwagner.com/2018/01/23/inverted-polygons-how-to-troubleshoot-sql-servers-left-hand-rule/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants