Skip to content

Instantly share code, notes, and snippets.

@ArrayIterator
Created February 19, 2020 08:02
Show Gist options
  • Save ArrayIterator/95f78e0f42b0b47ee895dbeaaadc7d4f to your computer and use it in GitHub Desktop.
Save ArrayIterator/95f78e0f42b0b47ee895dbeaaadc7d4f to your computer and use it in GitHub Desktop.

Revisions

  1. ArrayIterator created this gist Feb 19, 2020.
    184 changes: 184 additions & 0 deletions PolygonFixer.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,184 @@
    <?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;
    }
    }