Skip to content

Commit

Permalink
Proper support for GeoJSON Feature & FeatureCollection
Browse files Browse the repository at this point in the history
  • Loading branch information
BenMorel committed Mar 14, 2021
1 parent 9dbfdbe commit 69e9bb2
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 243 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
**New features**

- New method: `Geometry::transform()` transforms `Geometry` coordinates to a new SRID
- Proper support for `Feature` and `FeatureCollection` in `GeoJSONReader` and `GeoJSONWriter`

🐛 **Fixes**

Expand All @@ -16,7 +17,12 @@

💥 **BC breaks**

Note: these breaks will likely not affect you, unless you're writing your own geometry engine or your own WKB reader.
The following breaks only affect you if you use the GeoJSON reader/writer:

- `GeoJSONReader` now instantiates Features and FeatureCollections as `Feature` and `FeatureCollection` objects, instead of `Geometry` and `GeometryCollection` objects
- `GeoJSONWriter` will now write GeometryCollections as `GeometryCollection` type, instead of `FeatureCollection`

The following breaks will only affect you if you're writing your own geometry engine, or your own WKB reader:

- `AbstractWKBReader::readGeometryHeader()` signature was changed
- `WKBReader::read()` signature was changed
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,6 @@ $writer = new GeoJSONWriter();
echo $writer->write($point); // {"type":"Point","coordinates":[1,2]}
```

Note that `Feature`s are imported as `Geometry` objects, and `FeatureCollection`s are imported as `GeometryCollection`
objects. Non-spatial attributes are ignored.
The library supports reading and writing `Feature` and `FeatureCollection` objects, together with custom properties.

GeoJSON aims to support WGS84 only, and as such all Geometries are imported using [SRID 4326](https://epsg.io/4326).
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@
"preferred-install": "source"
},
"suggest": {
"ext-json": "To read and write GeoJSON files"
"ext-json": "To read and write GeoJSON"
}
}
6 changes: 4 additions & 2 deletions src/Exception/GeometryIOException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Brick\Geo\Exception;

use JsonException;

/**
* Exception thrown when an error occurs reading or writing WKT/WKB representations.
*/
Expand All @@ -24,9 +26,9 @@ public static function invalidEWKT() : GeometryIOException
return new self('Invalid EWKT.');
}

public static function invalidGeoJSON(string $context) : GeometryIOException
public static function invalidGeoJSON(string $context, ?JsonException $e = null) : GeometryIOException
{
$message = sprintf('Invalid GeoJSON: %s.', $context);
$message = sprintf('Invalid GeoJSON: %s', $context);

return new self($message);
}
Expand Down
104 changes: 104 additions & 0 deletions src/IO/GeoJSON/Feature.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace Brick\Geo\IO\GeoJSON;

use Brick\Geo\Geometry;
use stdClass;

/**
* A GeoJSON Feature. This class is immutable.
*/
final class Feature
{
/**
* The contained geometry, or null if this feature is not associated with a geometry.
*
* @var Geometry|null
*/
private ?Geometry $geometry;

/**
* An optional key-value map of feature properties. Must be convertible to JSON.
*/
private ?stdClass $properties;

/**
* @param Geometry|null $geometry
* @param stdClass|null $properties
*/
public function __construct(?Geometry $geometry = null, ?stdClass $properties = null)
{
$this->geometry = $geometry;
$this->properties = $properties;
}

public function getGeometry(): ?Geometry
{
return $this->geometry;
}

/**
* Returns a copy of this Feature with the given geometry.
*/
public function withGeometry(?Geometry $geometry): Feature
{
$that = clone $this;
$that->geometry = $geometry;

return $that;
}

public function getProperties(): ?stdClass
{
return $this->properties;
}

/**
* Returns a copy of this Feature with the given properties.
*
* @param stdClass|null $properties An optional key-value map of feature properties. Must be convertible to JSON.
*/
public function withProperties(?stdClass $properties): Feature
{
$that = clone $this;
$this->properties = $properties;

return $that;
}

/**
* @param string $name The property name.
* @param mixed $default The default value if the property is not found.
*
* @return mixed
*/
public function getProperty(string $name, $default = null)
{
if ($this->properties === null || ! property_exists($this->properties, $name)) {
return $default;
}

return $this->properties->{$name};
}

/**
* Returns a copy of this Feature with the given property set.
*
* @param string $name The property name.
* @param mixed $value The value. Must be convertible to JSON.
*/
public function withProperty(string $name, $value): Feature
{
$that = clone $this;

if ($that->properties === null) {
$that->properties = new stdClass();
}

$that->properties->{$name} = $value;

return $that;
}
}
61 changes: 61 additions & 0 deletions src/IO/GeoJSON/FeatureCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Brick\Geo\IO\GeoJSON;

use InvalidArgumentException;

/**
* A GeoJSON FeatureCollection. This class is immutable.
*/
final class FeatureCollection
{
/**
* The contained features.
*
* @var Feature[]
*/
private array $features = [];

/**
* @psalm-suppress DocblockTypeContradiction
*
* @param Feature[] $features The GeoJSON Features.
*/
public function __construct(array $features)
{
foreach ($features as $feature) {
if (! $feature instanceof Feature) {
throw new InvalidArgumentException(sprintf(
'Expected instance of %s, got %s.',
Feature::class,
is_object($feature) ? get_class($feature) : gettype($feature)
));
}
}

$this->features = array_values($features);
}

/**
* @return Feature[]
*/
public function getFeatures(): array
{
return $this->features;
}

/**
* Returns a copy of this FeatureCollection with the given Feature added.
* This instance is immutable and unaffected by this method call.
*/
public function withAddedFeature(Feature $feature): FeatureCollection
{
$that = clone $this;

$that->features[] = $feature;

return $that;
}
}
Loading

0 comments on commit 69e9bb2

Please sign in to comment.