Skip to content

Instantly share code, notes, and snippets.

@ArrayIterator
Created February 19, 2020 08:02
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ArrayIterator/95f78e0f42b0b47ee895dbeaaadc7d4f to your computer and use it in GitHub Desktop.
Save ArrayIterator/95f78e0f42b0b47ee895dbeaaadc7d4f to your computer and use it in GitHub Desktop.
Fix Right Hand Rule Geo JSON clockwise
<?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