Created
February 19, 2020 08:02
-
-
Save ArrayIterator/95f78e0f42b0b47ee895dbeaaadc7d4f to your computer and use it in GitHub Desktop.
Fix Right Hand Rule Geo JSON clockwise
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace ArrayIterator; | |
/** | |
* Class PolygonFixer | |
* @package ArrayIterator | |
* Like a Geo Jeon right-hand-rule Fixer based on {@link https://mapster.me/right-hand-rule-geojson-fixer/} | |
* use http://geojsonlint.com/ to lint geo json | |
*/ | |
class PolygonFixer | |
{ | |
/** | |
* @param array $source | |
* @param bool $outer | |
* @return array | |
*/ | |
public function rewind(array $source, $outer = true) | |
{ | |
$type = is_array($source) && isset($source['type']) ? $source['type'] : null; | |
switch ($type) { | |
case 'FeatureCollection': | |
$source['features'] = array_map(function ($b) use ($outer) { | |
return $this->rewind($b, $outer); | |
}, $source['features']); | |
return $source; | |
case 'Feature': | |
$source['geometry'] = $this->rewind($source['geometry'], $outer); | |
return $source; | |
case 'Polygon': | |
case 'MultiPolygon': | |
return $this->correct($source, $outer); | |
default: | |
return $source; | |
} | |
} | |
/** | |
* @param array $source | |
* @param bool $dir | |
* @return array | |
*/ | |
protected function wind($source, bool $dir) | |
{ | |
if (!is_array($source)) { | |
return $source; | |
} | |
return $this->cw($source) === $dir ? $source : array_reverse($source); | |
} | |
/** | |
* @param array $source | |
* @param bool $outer | |
* @return array | |
*/ | |
protected function correctRings($source, bool $outer) | |
{ | |
if (!is_array($source)) { | |
return $source; | |
} | |
$outer = !!$outer; | |
if (!isset($source[0])) { | |
return $source; | |
} | |
$source[0] = $this->wind($source[0], !$outer); | |
for ($i = 1; $i < count($source); $i++) { | |
$source[$i] = $this->wind($source[$i], $outer); | |
} | |
return $source; | |
} | |
/** | |
* @param array $source | |
* @param bool $outer | |
* @return array | |
*/ | |
protected function correct($source, bool $outer) | |
{ | |
if (!is_array($source) || !isset($source['type'])) { | |
return $source; | |
} | |
if ($source['type'] === 'Polygon') { | |
$source['coordinates'] = $this->correctRings($source['coordinates'], $outer); | |
} else if ($source['type'] === 'MultiPolygon') { | |
$source['coordinates'] = array_map(function ($a) use ($outer) { | |
return $this->correctRings($a, $outer); | |
}, $source['coordinates']); | |
} | |
return $source; | |
} | |
/** | |
* @param array $source | |
* @return bool | |
*/ | |
protected function cw($source) : bool | |
{ | |
return $this->ringArea($source) >= 0; | |
} | |
/** | |
* @param array $source | |
* @return float|int|null | |
*/ | |
protected function geometry($source) | |
{ | |
if (!is_array($source) || !isset($source['type'])) { | |
return null; | |
} | |
if ($source['type'] === 'Polygon') { | |
return $this->polygonArea($source['coordinates']); | |
} elseif ($source['type'] === 'MultiPolygon') { | |
$area = 0; | |
for ($i = 0; $i < count($source['coordinates']); $i++) { | |
$area += $this->polygonArea($source['coordinates'][$i]); | |
} | |
return $area; | |
} else { | |
return null; | |
} | |
} | |
/** | |
* @param array $coords | |
* @return float|int | |
*/ | |
protected function polygonArea($coords) | |
{ | |
$area = 0; | |
if (!is_array($coords)) { | |
return $area; | |
} | |
if ($coords && count($coords) > 0) { | |
$area += abs($this->ringArea($coords[0])); | |
for ($i = 1; $i < count($coords); $i++) { | |
$area -= abs($this->ringArea($coords[$i])); | |
} | |
} | |
return $area; | |
} | |
/** | |
* @param array $coords | |
* @return float|int | |
*/ | |
protected function ringArea($coords) | |
{ | |
$area = 0; | |
if (!is_array($coords)) { | |
return $area; | |
} | |
if (count($coords) > 2) { | |
$p1 = null; | |
$p2 = null; | |
for ($i = 0; $i < count($coords) - 1; $i++) { | |
$p1 = $coords[$i]; | |
$p2 = $coords[$i + 1]; | |
$area += $this->rad($p2[0] - $p1[0]) * (2 + sin($this->rad($p1[1])) + sin($this->rad($p2[1]))); | |
} | |
$area = $area * 6378137 * 6378137 / 2; | |
} | |
return $area; | |
} | |
/** | |
* @param float $number | |
* @return float|int | |
*/ | |
protected function rad($number) | |
{ | |
if (!is_numeric($number)) { | |
return 0; | |
} | |
return $number * pi() / 180; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment