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

IO GeoJSON Support #14

Merged
merged 45 commits into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3acb675
Add Required Classes & Functions for GeoJSON POINT type.
Jan 19, 2019
bc07326
Add GeoJSON Polygon support
Jan 19, 2019
6994bd5
Use GeometryIOException::unsupportedGeoJSONType() helper function whe…
Jan 19, 2019
8279cbc
Refactor GeoJson Reader
Jan 20, 2019
e3c4417
Add GeoJSON Geometry Types: MULTIPOINT, MULTILINESTRING, MULTIPOLYGON
Jan 20, 2019
76a71c9
Update GeoJSON Reader doc-blocks to account for all @throws
Jan 20, 2019
878f2c1
Add FeatureCollection Tests
Jan 20, 2019
ac50e13
Add GeoJSON Writer Cupporting Point & MultiPoint
Jan 20, 2019
579d60c
Add LineString & MultiLineString to GeoJSONWriter
Jan 20, 2019
267471c
Add Polygon & Multi Polygon to GeoJSON Write
Jan 20, 2019
66c6ead
Add FeatureCollection to GeoJSON writer
Jan 20, 2019
45562d8
Add GeoJson Z-axis support
Jan 21, 2019
48bd2fb
Refactor Checking of GeoJSON FeatureCollection.Fetures array
Jan 21, 2019
0aeb6a4
Add GeoJSON genPoint ...$coods type as Float
Jan 21, 2019
7ee7faa
Change snake_case Param $geojson_array to camelCase $geojsonArray to …
Jan 21, 2019
f58518c
Return TypeHint GeoJSONWriter Private Functions
Jan 21, 2019
52cf6bf
Add GeoJSON json_decode checks in GeoJSONReader::read()
Jan 21, 2019
651a5a4
Merge GeoJSON Abstract Reader into Reader class
Jan 21, 2019
9cfd97f
Refactor GeoJSON Writer
Jan 21, 2019
a3eda38
Add string $context to GeometryIOException::invalidGeoJSON(...)
Jan 21, 2019
526ed7f
Remove GeoJSON spatial reference systems (SRIDs)
Jan 21, 2019
150043e
Fix error when writing GeoJSON it "Multi"-Class Geometry is passed.
Jan 21, 2019
b306873
Fix GeometryIOException::invalidGeoJSON(...) message part `GeoJson` t…
Jan 22, 2019
b69760d
Update IO GeoJson doc-blocks to fit standards
Jan 22, 2019
05017b4
Update GeoJSON SRID to 4326
Jan 22, 2019
54059b9
Remove Extra lines after ForEach statements in GeoJSON files
Jan 22, 2019
cd36118
Fix tests for GeoJSON SRID = 4326
Jan 22, 2019
6499d55
Remove GeoJSON isEmpty check when reading
Jan 22, 2019
41ad7b0
Simplify GeoJSON is3D return conditional
Jan 22, 2019
99140e1
Refactor GeometryCollection Verification in GeoJSONWriter
Jan 22, 2019
88b1fa1
Remove empty lines after GeoJSONReader cases
Jan 22, 2019
0ac1c49
Fix GeoJSONWriterTest function names to say "Write" not "Read"
Jan 22, 2019
1fe43e0
Simplify GeoJSONWriter::formatGeoJSONGeometry(...) function logic
Jan 22, 2019
a8f07f1
Remove unneeded GeoJSON GeometryCollection is_empty check
Jan 22, 2019
5c024b2
Type hint all GeoJSON read{Grometry} functions with ...$coords as array
Jan 22, 2019
3161c38
Update GeoJSONReader @throw blocks
Jan 23, 2019
151006b
Refactor GeoJSONWriter::FormatGeoJSONGeometry() to use a string array…
Jan 23, 2019
297af8d
Require CaseSensitive GeoJSON keys and types
Jan 23, 2019
6263978
Refactor IO GeoJSON array_key_exists to isset calls
Jan 23, 2019
86180cf
Refactor GeoJSONReader Gen* Functions to not Pack/Unpack aray parameters
Jan 23, 2019
79a4a7f
Fixes/Formatting for GeoJSONReader
Jan 24, 2019
d570ca9
Simplify GeoJSONReader @throws
Jan 24, 2019
e554f97
Add comment to GeoJSONWriter to note GeometryCollection Filtering
Jan 24, 2019
e591185
Remove isM from all tests
Jan 24, 2019
ab9b2bf
Remove Extra Lines
Jan 24, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/Exception/GeometryIOException.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public static function invalidEWKT() : GeometryIOException
return new self('Invalid EWKT.');
}

/**
* @return GeometryIOException
*/
public static function invalidGeoJSON() : GeometryIOException
{
return new self('Invalid GeoJson.');
}

/**
* @param int $wkbType
*
Expand All @@ -47,6 +55,18 @@ public static function unsupportedWKBType(int $wkbType) : GeometryIOException
return new self($message);
}

/**
* @param string $geojsonType
*
* @return GeometryIOException
*/
public static function unsupportedGeoJSONType(string $geojsonType) : GeometryIOException
{
$message = sprintf('Unsupported GeoJSON type: %s.', $geojsonType);

return new static($message);
}

/**
* @param string $geometryType
*
Expand Down
328 changes: 328 additions & 0 deletions src/IO/AbstractGeoJSONReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
<?php

declare(strict_types = 1);

namespace Brick\Geo\IO;

use Brick\Geo\CoordinateSystem;
use Brick\Geo\Exception\GeometryIOException;
use Brick\Geo\Geometry;
use Brick\Geo\GeometryCollection;
use Brick\Geo\LineString;
use Brick\Geo\MultiLineString;
use Brick\Geo\MultiPoint;
use Brick\Geo\MultiPolygon;
use Brick\Geo\Point;
use Brick\Geo\Polygon;

abstract class AbstractGeoJSONReader
{
/**
* @param array $geojson
* @param int $srid
*
* @return Geometry
*
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
* @throws GeometryIOException
* @throws \Brick\Geo\Exception\InvalidGeometryException
*/
public function readGeoJSON(array $geojson, int $srid) : Geometry
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
{
switch ($geojson['TYPE']) {
case 'FEATURE':
return $this->readFeature($geojson, $srid);

case 'FEATURECOLLECTION':
// Verify 'FEATURES' exists
if (! array_key_exists('FEATURES', $geojson)
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
|| ! is_array($geojson['FEATURES'])
) {
throw GeometryIOException::invalidGeoJSON();
}

$geometries = [];

foreach ($geojson['FEATURES'] as $feature) {
$geometries[] = $this->readFeature($feature, $srid);
}

return GeometryCollection::of(...$geometries);

case 'POINT':
case 'MULTIPOINT':
case 'LINESTRING':
case 'MULTILINESTRING':
case 'POLYGON':
case 'MULTIPOLYGON':

return $this->readGeometry($geojson, $srid);

default:
throw GeometryIOException::invalidGeoJSON();
}
}

/**
* @param array $feature
* @param int $srid
*
* @return Geometry
*
* @throws GeometryIOException
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
*/
protected function readFeature(array $feature, int $srid) : Geometry
{
// Verify Type 'FEATURE'
if (! array_key_exists('TYPE', $feature) || 'FEATURE' !== $feature['TYPE']) {
throw GeometryIOException::invalidGeoJSON();
}

// Verify Geometry exists and is array
if (! array_key_exists('GEOMETRY', $feature) || ! is_array($feature['GEOMETRY'])) {
throw GeometryIOException::invalidGeoJSON();
}

return $this->readGeometry($feature['GEOMETRY'], $srid);
}

/**
* @param array $geometry
* @param int $srid
*
* @return Geometry
*
* @throws GeometryIOException
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
*/
protected function readGeometry(array $geometry, int $srid) : Geometry
{
// Verify Geometry TYPE
if (! array_key_exists('TYPE', $geometry) || ! is_string($geometry['TYPE'])) {
throw GeometryIOException::invalidGeoJSON();
}

$geoType = $geometry['TYPE'];

// Verify Geometry COORDINATES
if (! array_key_exists('COORDINATES', $geometry) || ! array($geometry['COORDINATES'])) {
throw GeometryIOException::invalidGeoJSON();
}

$geoCoords = $geometry['COORDINATES'];

$hasZ = $this->hasZ($geoCoords);
$hasM = false;
$isEmpty = empty($geoCoords);

$cs = new CoordinateSystem($hasZ, $hasM, $srid);

switch ($geoType) {
case 'POINT':
if ($isEmpty) {
return new Point($cs);
}

return $this->genPoint($cs, ...$geoCoords);

case 'MULTIPOINT':
if ($isEmpty) {
return new MultiPoint($cs);
}

return $this->genMultiPoint($cs, ...$geoCoords);

case 'LINESTRING':
if ($isEmpty) {
return new LineString($cs);
}

return $this->genLineString($cs, ...$geoCoords);

case 'MULTILINESTRING':
if ($isEmpty) {
return new MultiLineString($cs);
}

return $this->genMultiLineString($cs, ...$geoCoords);

case 'POLYGON':
if ($isEmpty) {
return new Polygon($cs);
}

return $this->genPolygon($cs, ...$geoCoords);

case 'MULTIPOLYGON':
if ($isEmpty) {
return new MultiPolygon($cs);
}

return $this->genMultiPolygon($cs, ...$geoCoords);
}

throw GeometryIOException::unsupportedGeoJSONType($geoType);
}

/**
* [x, y]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return Point
*
* @throws \Brick\Geo\Exception\InvalidGeometryException
*/
private function genPoint(CoordinateSystem $cs, ...$coords) : Point
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
{
return new Point($cs, ...$coords);
}

/**
* [[x, y], ...]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return MultiPoint
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
*/
private function genMultiPoint(CoordinateSystem $cs, ...$coords) : MultiPoint
{
$points = [];

foreach ($coords as $pointCoords) {
$points[] = $this->genPoint($cs, ...$pointCoords);
}

return new MultiPoint($cs, ...$points);
}

/**
* [[x, y], ...]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return LineString
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
*/
private function genLineString(CoordinateSystem $cs, ...$coords) : LineString
{
$points = [];

foreach ($coords as $pointCoords) {
$points[] = $this->genPoint($cs, ...$pointCoords);
}

return new LineString($cs, ...$points);
}

/**
* [[[x, y], ...], ...]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return MultiLineString
*
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
*/
private function genMultiLineString(CoordinateSystem $cs, ...$coords) : MultiLineString
{
$lineStrings = [];

foreach ($coords as $lineStringCoords) {

$lineStrings[] = $this->genLineString($cs, ...$lineStringCoords);
}

return new MultiLineString($cs, ...$lineStrings);
}

/**
* [[[x, y], ...], ...]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return Polygon
*
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
*/
private function genPolygon(CoordinateSystem $cs, ...$coords) : Polygon
{
$lineStrings = [];

foreach ($coords as $lineStringCoords) {

$lineStrings[] = $this->genLineString($cs, ...$lineStringCoords);
}

return new Polygon($cs, ...$lineStrings);
}

/**
* [[[x, y], ...], ...]
*
* @param CoordinateSystem $cs
* @param array $coords
*
* @return MultiPolygon
*
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
*/
private function genMultiPolygon(CoordinateSystem $cs, ...$coords) : MultiPolygon
{
$polygons = [];

foreach ($coords as $polygonCoords) {

$polygons[] = $this->genPolygon($cs, ...$polygonCoords);
}

return new MultiPolygon($cs, ...$polygons);
}

/**
* @param $coords
* @return bool
*/
private function hasZ(array $coords)
{
if (empty($coords)) {
return false;
}

// At least one Geometry hasZ
if (! is_array($coords[0])) {
if (3 === count($coords)) {
return true;
}
return false;
}

foreach ($coords as $coord) {
if ($this->hasZ($coord)) {
return true;
}
}

return false;
}
}
33 changes: 33 additions & 0 deletions src/IO/GeoJSONReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types = 1);

namespace Brick\Geo\IO;

use Brick\Geo\Geometry;
use Brick\Geo\GeometryCollection;

/**
* Builds geometries out of GeoJSON Text strings.
*/
class GeoJSONReader extends AbstractGeoJSONReader
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
{
/**
* @param string $geojson The GeoJSON to read.
* @param int $srid The optional SRID of the geometry.
*
* @return GeometryCollection|Geometry
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
* @throws \Brick\Geo\Exception\CoordinateSystemException
* @throws \Brick\Geo\Exception\GeometryIOException
* @throws \Brick\Geo\Exception\InvalidGeometryException
* @throws \Brick\Geo\Exception\UnexpectedGeometryException
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
*/
public function read(string $geojson, int $srid = 0) : Geometry
{
$geojson_array = json_decode(strtoupper($geojson), true);
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved
michaelcurry marked this conversation as resolved.
Show resolved Hide resolved

$geometry = $this->readGeoJSON($geojson_array, $srid);

return $geometry;
}
}
Loading