Last active
September 3, 2024 09:34
-
-
Save paulofreitas/a6f742b63decf5874c53074865eb6dbf to your computer and use it in GitHub Desktop.
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 | |
class GeoJSON | |
{ | |
public function __construct(string $geojson) | |
{ | |
$this->_struct = json_decode($geojson); | |
} | |
public static function fromString(string $geojson) | |
{ | |
return new static($geojson); | |
} | |
public static function fromFile(string $geojson_file) | |
{ | |
return new static(file_get_contents($geojson_file)); | |
} | |
public function parse() | |
{ | |
if ($this->_struct->type == 'Feature') { | |
return new Feature($this->_struct); | |
} | |
if ($this->_struct->type == 'FeatureCollection') { | |
return new FeatureCollection($this->_struct); | |
} | |
throw Exception('Invalid GeoJSON'); | |
} | |
} | |
class FeatureCollection | |
{ | |
public function __construct($struct) | |
{ | |
$this->_struct = $struct; | |
$this->validate(); | |
} | |
public function validate() | |
{ | |
assert(isset($this->_struct->features), new Exception('Missing feature collection')); | |
} | |
public function isCollection() | |
{ | |
return true; | |
} | |
public function features() | |
{ | |
$features = []; | |
foreach ($this->_struct->features as $feature) { | |
$features[] = new Feature($feature); | |
} | |
return $features; | |
} | |
} | |
class Feature | |
{ | |
public function __construct($struct) | |
{ | |
$this->_struct = $struct; | |
$this->validate(); | |
} | |
public function validate() | |
{ | |
assert(isset($this->_struct->geometry), new Exception('Missing feature geometry')); | |
assert(isset($this->_struct->properties), new Exception('Missing feature properties')); | |
} | |
public function isCollection() | |
{ | |
return False; | |
} | |
public function geometry() | |
{ | |
return new Geometry($this->_struct->geometry); | |
} | |
public function properties() | |
{ | |
return $this->_struct->properties; | |
} | |
} | |
class Geometry | |
{ | |
public function __construct($struct) | |
{ | |
$this->_struct = $struct; | |
$this->validate(); | |
} | |
public function validate() | |
{ | |
assert(isset($this->_struct->type), new Exception('Missing geometry type')); | |
assert(in_array($this->_struct->type, ['Point', 'Polygon']), new Exception('Unsupported geometry type')); | |
assert(isset($this->_struct->coordinates), new Exception('Missing geometry coordinates')); | |
} | |
public function type() | |
{ | |
return $this->_struct->type; | |
} | |
public function coordinates() | |
{ | |
return $this->_struct->coordinates; | |
} | |
public function parse() | |
{ | |
if ($this->type() == 'Point') { | |
list($x, $y) = $this->_struct->coordinates; | |
return new Point($this->_struct->coordinates); | |
} | |
if ($this->type() == 'Polygon') { | |
return new Polygon($this->_struct->coordinates[0]); | |
} | |
throw new Exception('Unsupported geometry type'); | |
} | |
} | |
class Point | |
{ | |
private $_x; | |
private $_y; | |
public function __construct(float $x, float $y) | |
{ | |
$this->setLongitude($x); | |
$this->setLatitude($y); | |
} | |
public function latitude() | |
{ | |
return $this->_y; | |
} | |
public function longitude() | |
{ | |
return $this->_x; | |
} | |
public function setLatitude(float $y) | |
{ | |
assert($y >= -90 && $y <= 90, new Exception('Invalid latitude degree')); | |
$this->_y = $y; | |
} | |
public function setLongitude(float $x) | |
{ | |
assert($x >= -180 && $x <= 180, new Exception('Invalid longitude degree')); | |
$this->_x = $x; | |
} | |
} | |
class Polygon | |
{ | |
private $_points; | |
public function __construct(array $points = []) | |
{ | |
$this->addPoints($points); | |
} | |
public function addPoints(array $points) | |
{ | |
foreach ($points as $point) { | |
list($x, $y) = $point; | |
$this->addPoint(new Point($x, $y)); | |
} | |
} | |
public function addPoint(Point $point) | |
{ | |
$this->_points[] = $point; | |
} | |
public function contains(Point $point) | |
{ | |
$contains = false; | |
$n_points = count($this->_points); | |
for ($i = 0, $j = $n_points - 1; $i < $n_points; $j = $i++) { | |
$i_point = $this->_points[$i]; | |
$j_point = $this->_points[$j]; | |
if ((($i_point->longitude() > $point->longitude()) != ($j_point->longitude() > $point->longitude())) | |
&& ($point->latitude() < ($j_point->latitude() - $i_point->latitude()) * ($point->longitude() - $i_point->longitude()) / ($j_point->longitude() - $i_point->longitude()) + $i_point->latitude())) { | |
$contains = !$contains; | |
} | |
} | |
return $contains; | |
} | |
} | |
$raw_geojson = '{ | |
"type": "FeatureCollection", | |
"features": [ | |
{ | |
"type": "Feature", | |
"geometry": { | |
"type": "Polygon", | |
"coordinates": [ | |
[ | |
[ | |
-47.498998045921326, | |
-23.533586090311893 | |
], | |
[ | |
-47.49819874763489, | |
-23.532789333203414 | |
], | |
[ | |
-47.49783396720886, | |
-23.533109020277173 | |
], | |
[ | |
-47.49861717224121, | |
-23.533905775449583 | |
], | |
[ | |
-47.498998045921326, | |
-23.533586090311893 | |
] | |
] | |
] | |
}, | |
"properties": { | |
} | |
} | |
] | |
}'; | |
$feature = GeoJSON::fromString($raw_geojson)->parse(); | |
function polygon_contains($feature, $point) | |
{ | |
if (!$feature->isCollection()) { | |
return $feature instanceof Polygon && $geometry->contains($point); | |
} | |
foreach ($feature->features() as $_feature) { | |
$polygon = $_feature->geometry()->parse(); | |
if ($polygon instanceof Polygon && $polygon->contains($point)) { | |
return True; | |
} | |
} | |
return False; | |
} | |
var_dump(polygon_contains($feature, new Point(-47.0869358, -22.9075211))); // false | |
var_dump(polygon_contains($feature, new Point(-47.49847769737243, -23.533305750397986))); // true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment