Skip to content

Commit

Permalink
Merge pull request #102 from v1r0x/3d-geometries
Browse files Browse the repository at this point in the history
Add 3D coordinates
  • Loading branch information
njbarrett committed Aug 22, 2018
2 parents 7c2b8cf + 1a691ea commit bc9f24b
Show file tree
Hide file tree
Showing 18 changed files with 548 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/Geometries/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class Factory implements \GeoIO\Factory {
public function createPoint( $dimension, array $coordinates, $srid = null )
{
return new Point( $coordinates['y'], $coordinates['x'] );
return new Point( $coordinates['y'], $coordinates['x'], isset($coordinates['z']) ? $coordinates['z'] : null );
}

public function createLineString( $dimension, array $points, $srid = null )
Expand Down
12 changes: 12 additions & 0 deletions src/Geometries/Geometry.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,28 @@ public static function getWKTClass($value)

switch (strtoupper($type)) {
case 'POINT':
case 'POINTZ':
case 'POINT Z':
return Point::class;
case 'LINESTRING':
case 'LINESTRINGZ':
case 'LINESTRING Z':
return LineString::class;
case 'POLYGON':
case 'POLYGONZ':
case 'POLYGON Z':
return Polygon::class;
case 'MULTIPOINT':
case 'MULTIPOINTZ':
case 'MULTIPOINT Z':
return MultiPoint::class;
case 'MULTILINESTRING':
case 'MULTILINESTRINGZ':
case 'MULTILINESTRING Z':
return MultiLineString::class;
case 'MULTIPOLYGON':
case 'MULTIPOLYGONZ':
case 'MULTIPOLYGON Z':
return MultiPolygon::class;
case 'GEOMETRYCOLLECTION':
return GeometryCollection::class;
Expand Down
10 changes: 9 additions & 1 deletion src/Geometries/LineString.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

class LineString extends PointCollection implements GeometryInterface
{
public function is3d()
{
if(count($this->points) === 0) return false;
return $this->points[0]->is3d();
}

public function toWKT()
{
return sprintf('LINESTRING(%s)', $this->toPairList());
$wktType = 'LINESTRING';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, $this->toPairList());
}

public static function fromWKT($wkt)
Expand Down
10 changes: 9 additions & 1 deletion src/Geometries/MultiLineString.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ public function getLineStrings()
return $this->linestrings;
}

public function is3d()
{
if(count($this->linestrings) === 0) return false;
return $this->linestrings[0]->is3d();
}

public function toWKT()
{
return sprintf('MULTILINESTRING(%s)', (string)$this);
$wktType = 'MULTILINESTRING';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, (string)$this);
}

public static function fromString($wktArgument)
Expand Down
12 changes: 10 additions & 2 deletions src/Geometries/MultiPoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

class MultiPoint extends PointCollection implements GeometryInterface, \JsonSerializable
{
public function is3d()
{
if(count($this->points) === 0) return false;
return $this->points[0]->is3d();
}

public function toWKT()
{
return sprintf('MULTIPOINT(%s)', (string)$this);
$wktType = 'MULTIPOINT';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, (string)$this);
}

public static function fromWKT($wkt)
Expand All @@ -17,7 +25,7 @@ public static function fromWKT($wkt)
public static function fromString($wktArgument)
{
$matches = [];
preg_match_all('/\(\s*(\d+\s+\d+)\s*\)/', trim($wktArgument), $matches);
preg_match_all('/\(\s*(\d+\s+\d+(\s+\d+)?)\s*\)/', trim($wktArgument), $matches);

if (count($matches) < 2) {
return new static([]);
Expand Down
10 changes: 9 additions & 1 deletion src/Geometries/MultiPolygon.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ public function __construct(array $polygons)
$this->polygons = $polygons;
}

public function is3d()
{
if(count($this->polygons) === 0) return false;
return $this->polygons[0]->is3d();
}

public function toWKT()
{
return sprintf('MULTIPOLYGON(%s)', (string) $this);
$wktType = 'MULTIPOLYGON';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, (string) $this);
}

public function __toString()
Expand Down
46 changes: 38 additions & 8 deletions src/Geometries/Point.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ class Point extends Geometry
{
protected $lat;
protected $lng;
protected $alt;

public function __construct($lat, $lng)
public function __construct($lat, $lng, $alt = null)
{
$this->lat = (float)$lat;
$this->lng = (float)$lng;
$this->alt = isset($alt) ? (float)$alt : null;
}

public function getLat()
Expand All @@ -33,28 +35,54 @@ public function setLng($lng)
$this->lng = (float)$lng;
}

public function getAlt()
{
return $this->alt;
}

public function setAlt($alt)
{
$this->alt = (float)$alt;
}

public function is3d()
{
return isset($this->alt);
}

public function toPair()
{
return self::stringifyFloat($this->getLng()) . ' ' . self::stringifyFloat($this->getLat());
$pair = self::stringifyFloat($this->getLng()) . ' ' . self::stringifyFloat($this->getLat());
if($this->is3d()) {
$pair .= ' ' . self::stringifyFloat($this->getAlt());
}
return $pair;
}

private static function stringifyFloat($float)
{
// normalized output among locales
return rtrim(rtrim(sprintf('%F', $float), '0'), '.');
}

public static function fromPair($pair)
{
$pair = preg_replace('/^[a-zA-Z\(\)]+/', '', trim($pair));
list($lng, $lat) = explode(' ', trim($pair));
$splits = explode(' ', trim($pair));
$lng = $splits[0];
$lat = $splits[1];
if(count($splits) > 2) {
$alt = $splits[2];
}

return new static((float)$lat, (float)$lng);
return new static((float)$lat, (float)$lng, isset($alt) ? (float)$alt : null);
}

public function toWKT()
{
return sprintf('POINT(%s)', (string)$this);
$wktType = 'POINT';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, (string)$this);
}

public static function fromString($wktArgument)
Expand All @@ -74,6 +102,8 @@ public function __toString()
*/
public function jsonSerialize()
{
return new \GeoJson\Geometry\Point([$this->getLng(), $this->getLat()]);
$position = [$this->getLng(), $this->getLat()];
if($this->is3d()) $position[] = $this->getAlt();
return new \GeoJson\Geometry\Point($position);
}
}
9 changes: 8 additions & 1 deletion src/Geometries/Polygon.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

class Polygon extends MultiLineString implements Countable
{
public function is3d()
{
if(count($this->linestrings) === 0) return false;
return $this->linestrings[0]->is3d();
}

public function toWKT()
{
return sprintf('POLYGON(%s)', (string)$this);
$wktType = 'POLYGON';
if($this->is3d()) $wktType .= ' Z';
return sprintf('%s(%s)', $wktType, (string)$this);
}

/**
Expand Down
45 changes: 45 additions & 0 deletions tests/Eloquent/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,51 @@ public function testUpdateLinestring()

$builder->update(['linestring' => $linestring]);
}

public function testUpdate3d()
{
$this->queryBuilder
->shouldReceive('raw')
->with("public.ST_GeogFromText('POINT Z(2 1 0)')")
->andReturn(new Expression("public.ST_GeogFromText('POINT Z(2 1 0)')"));

$this->queryBuilder
->shouldReceive('update')
->andReturn(1);

$builder = m::mock(Builder::class, [$this->queryBuilder])->makePartial();
$builder->shouldAllowMockingProtectedMethods();
$builder
->shouldReceive('addUpdatedAtColumn')
->andReturn(['point' => new Point(1, 2, 0)]);

$builder->update(['point' => new Point(1, 2, 0)]);
}

public function testUpdateLinestring3d()
{
$this->queryBuilder
->shouldReceive('raw')
->with("public.ST_GeogFromText('LINESTRING Z(0 0 0, 1 1 1, 2 2 2)')")
->andReturn(new Expression("public.ST_GeogFromText('LINESTRING Z(0 0 0, 1 1 1, 2 2 2)')"));

$this->queryBuilder
->shouldReceive('update')
->andReturn(1);

$linestring = new LineString([new Point(0, 0, 0), new Point(1, 1, 1), new Point(2, 2, 2)]);

$builder = m::mock(Builder::class, [$this->queryBuilder])->makePartial();
$builder->shouldAllowMockingProtectedMethods();
$builder
->shouldReceive('addUpdatedAtColumn')
->andReturn(['linestring' => $linestring]);

$builder
->shouldReceive('asWKT')->with($linestring)->once();

$builder->update(['linestring' => $linestring]);
}
}

class TestBuilderModel extends Model
Expand Down
25 changes: 25 additions & 0 deletions tests/Eloquent/PostgisTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,31 @@ public function testUpdatePointHasCorrectSql()

$this->assertContains("public.ST_GeogFromText('POINT(4 2)')", $this->queries[0]);
}

public function testInsertPoint3dHasCorrectSql()
{
$this->model->point = new Point(1, 2, 3);
$this->model->save();

$this->assertContains("public.ST_GeogFromText('POINT Z(2 1 3)')", $this->queries[0]);
}

public function testInsertPoint3dGeometryHasCorrectSql()
{
$this->model->point2 = new Point(1, 2, 3);
$this->model->save();

$this->assertContains("public.ST_GeomFromText('POINT Z(2 1 3)', '27700')", $this->queries[0]);
}

public function testUpdatePoint3dHasCorrectSql()
{
$this->model->exists = true;
$this->model->point = new Point(2, 4, 6);
$this->model->save();

$this->assertContains("public.ST_GeogFromText('POINT Z(4 2 6)')", $this->queries[0]);
}
}

class TestModel extends Model
Expand Down
50 changes: 50 additions & 0 deletions tests/Geometries/GeometryCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class GeometryCollectionTest extends BaseTestCase
* @var GeometryCollection
*/
private $collection;
private $collection3d;

protected function setUp()
{
Expand All @@ -26,6 +27,20 @@ protected function setUp()
$point = new Point(100, 200);

$this->collection = new GeometryCollection([$collection, $point]);

$collection = new LineString(
[
new Point(1, 1, 1),
new Point(1, 2, 3),
new Point(2, 2, 2),
new Point(2, 1, 0),
new Point(1, 1, 1)
]
);

$point = new Point(100, 200, 300);

$this->collection3d = new GeometryCollection([$collection, $point]);
}


Expand All @@ -42,6 +57,19 @@ public function testFromWKT()
$this->assertInstanceOf(LineString::class, $geometryCollection->getGeometries()[1]);
}

public function testFromWKT3d()
{
/**
* @var GeometryCollection $geometryCollection
*/
$geometryCollection = GeometryCollection::fromWKT('GEOMETRYCOLLECTION(POINT Z(2 3 4),LINESTRING Z(2 3 4,3 4 5))');
$this->assertInstanceOf(GeometryCollection::class, $geometryCollection);

$this->assertEquals(2, $geometryCollection->count());
$this->assertInstanceOf(Point::class, $geometryCollection->getGeometries()[0]);
$this->assertInstanceOf(LineString::class, $geometryCollection->getGeometries()[1]);
}

public function testToWKT()
{
$this->assertEquals(
Expand All @@ -50,6 +78,14 @@ public function testToWKT()
);
}

public function testToWKT3d()
{
$this->assertEquals(
'GEOMETRYCOLLECTION(LINESTRING Z(1 1 1,2 1 3,2 2 2,1 2 0,1 1 1),POINT Z(200 100 300))',
$this->collection3d->toWKT()
);
}

public function testJsonSerialize()
{
$this->assertInstanceOf(
Expand All @@ -63,4 +99,18 @@ public function testJsonSerialize()
);

}

public function testJsonSerialize3d()
{
$this->assertInstanceOf(
\GeoJson\Geometry\GeometryCollection::class,
$this->collection3d->jsonSerialize()
);

$this->assertSame(
'{"type":"GeometryCollection","geometries":[{"type":"LineString","coordinates":[[1,1,1],[2,1,3],[2,2,2],[1,2,0],[1,1,1]]},{"type":"Point","coordinates":[200,100,300]}]}',
json_encode($this->collection3d->jsonSerialize())
);

}
}
Loading

0 comments on commit bc9f24b

Please sign in to comment.