Skip to content

Commit bc9f24b

Browse files
authored
Merge pull request mstaack#102 from v1r0x/3d-geometries
Add 3D coordinates
2 parents 7c2b8cf + 1a691ea commit bc9f24b

18 files changed

+548
-17
lines changed

src/Geometries/Factory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
class Factory implements \GeoIO\Factory {
44
public function createPoint( $dimension, array $coordinates, $srid = null )
55
{
6-
return new Point( $coordinates['y'], $coordinates['x'] );
6+
return new Point( $coordinates['y'], $coordinates['x'], isset($coordinates['z']) ? $coordinates['z'] : null );
77
}
88

99
public function createLineString( $dimension, array $points, $srid = null )

src/Geometries/Geometry.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,28 @@ public static function getWKTClass($value)
3030

3131
switch (strtoupper($type)) {
3232
case 'POINT':
33+
case 'POINTZ':
34+
case 'POINT Z':
3335
return Point::class;
3436
case 'LINESTRING':
37+
case 'LINESTRINGZ':
38+
case 'LINESTRING Z':
3539
return LineString::class;
3640
case 'POLYGON':
41+
case 'POLYGONZ':
42+
case 'POLYGON Z':
3743
return Polygon::class;
3844
case 'MULTIPOINT':
45+
case 'MULTIPOINTZ':
46+
case 'MULTIPOINT Z':
3947
return MultiPoint::class;
4048
case 'MULTILINESTRING':
49+
case 'MULTILINESTRINGZ':
50+
case 'MULTILINESTRING Z':
4151
return MultiLineString::class;
4252
case 'MULTIPOLYGON':
53+
case 'MULTIPOLYGONZ':
54+
case 'MULTIPOLYGON Z':
4355
return MultiPolygon::class;
4456
case 'GEOMETRYCOLLECTION':
4557
return GeometryCollection::class;

src/Geometries/LineString.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
class LineString extends PointCollection implements GeometryInterface
44
{
5+
public function is3d()
6+
{
7+
if(count($this->points) === 0) return false;
8+
return $this->points[0]->is3d();
9+
}
10+
511
public function toWKT()
612
{
7-
return sprintf('LINESTRING(%s)', $this->toPairList());
13+
$wktType = 'LINESTRING';
14+
if($this->is3d()) $wktType .= ' Z';
15+
return sprintf('%s(%s)', $wktType, $this->toPairList());
816
}
917

1018
public static function fromWKT($wkt)

src/Geometries/MultiLineString.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,17 @@ public function getLineStrings()
3535
return $this->linestrings;
3636
}
3737

38+
public function is3d()
39+
{
40+
if(count($this->linestrings) === 0) return false;
41+
return $this->linestrings[0]->is3d();
42+
}
43+
3844
public function toWKT()
3945
{
40-
return sprintf('MULTILINESTRING(%s)', (string)$this);
46+
$wktType = 'MULTILINESTRING';
47+
if($this->is3d()) $wktType .= ' Z';
48+
return sprintf('%s(%s)', $wktType, (string)$this);
4149
}
4250

4351
public static function fromString($wktArgument)

src/Geometries/MultiPoint.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
class MultiPoint extends PointCollection implements GeometryInterface, \JsonSerializable
44
{
5+
public function is3d()
6+
{
7+
if(count($this->points) === 0) return false;
8+
return $this->points[0]->is3d();
9+
}
10+
511
public function toWKT()
612
{
7-
return sprintf('MULTIPOINT(%s)', (string)$this);
13+
$wktType = 'MULTIPOINT';
14+
if($this->is3d()) $wktType .= ' Z';
15+
return sprintf('%s(%s)', $wktType, (string)$this);
816
}
917

1018
public static function fromWKT($wkt)
@@ -17,7 +25,7 @@ public static function fromWKT($wkt)
1725
public static function fromString($wktArgument)
1826
{
1927
$matches = [];
20-
preg_match_all('/\(\s*(\d+\s+\d+)\s*\)/', trim($wktArgument), $matches);
28+
preg_match_all('/\(\s*(\d+\s+\d+(\s+\d+)?)\s*\)/', trim($wktArgument), $matches);
2129

2230
if (count($matches) < 2) {
2331
return new static([]);

src/Geometries/MultiPolygon.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,17 @@ public function __construct(array $polygons)
2525
$this->polygons = $polygons;
2626
}
2727

28+
public function is3d()
29+
{
30+
if(count($this->polygons) === 0) return false;
31+
return $this->polygons[0]->is3d();
32+
}
33+
2834
public function toWKT()
2935
{
30-
return sprintf('MULTIPOLYGON(%s)', (string) $this);
36+
$wktType = 'MULTIPOLYGON';
37+
if($this->is3d()) $wktType .= ' Z';
38+
return sprintf('%s(%s)', $wktType, (string) $this);
3139
}
3240

3341
public function __toString()

src/Geometries/Point.php

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ class Point extends Geometry
66
{
77
protected $lat;
88
protected $lng;
9+
protected $alt;
910

10-
public function __construct($lat, $lng)
11+
public function __construct($lat, $lng, $alt = null)
1112
{
1213
$this->lat = (float)$lat;
1314
$this->lng = (float)$lng;
15+
$this->alt = isset($alt) ? (float)$alt : null;
1416
}
1517

1618
public function getLat()
@@ -33,28 +35,54 @@ public function setLng($lng)
3335
$this->lng = (float)$lng;
3436
}
3537

38+
public function getAlt()
39+
{
40+
return $this->alt;
41+
}
42+
43+
public function setAlt($alt)
44+
{
45+
$this->alt = (float)$alt;
46+
}
47+
48+
public function is3d()
49+
{
50+
return isset($this->alt);
51+
}
52+
3653
public function toPair()
3754
{
38-
return self::stringifyFloat($this->getLng()) . ' ' . self::stringifyFloat($this->getLat());
55+
$pair = self::stringifyFloat($this->getLng()) . ' ' . self::stringifyFloat($this->getLat());
56+
if($this->is3d()) {
57+
$pair .= ' ' . self::stringifyFloat($this->getAlt());
58+
}
59+
return $pair;
3960
}
40-
61+
4162
private static function stringifyFloat($float)
4263
{
4364
// normalized output among locales
4465
return rtrim(rtrim(sprintf('%F', $float), '0'), '.');
4566
}
46-
67+
4768
public static function fromPair($pair)
4869
{
4970
$pair = preg_replace('/^[a-zA-Z\(\)]+/', '', trim($pair));
50-
list($lng, $lat) = explode(' ', trim($pair));
71+
$splits = explode(' ', trim($pair));
72+
$lng = $splits[0];
73+
$lat = $splits[1];
74+
if(count($splits) > 2) {
75+
$alt = $splits[2];
76+
}
5177

52-
return new static((float)$lat, (float)$lng);
78+
return new static((float)$lat, (float)$lng, isset($alt) ? (float)$alt : null);
5379
}
5480

5581
public function toWKT()
5682
{
57-
return sprintf('POINT(%s)', (string)$this);
83+
$wktType = 'POINT';
84+
if($this->is3d()) $wktType .= ' Z';
85+
return sprintf('%s(%s)', $wktType, (string)$this);
5886
}
5987

6088
public static function fromString($wktArgument)
@@ -74,6 +102,8 @@ public function __toString()
74102
*/
75103
public function jsonSerialize()
76104
{
77-
return new \GeoJson\Geometry\Point([$this->getLng(), $this->getLat()]);
105+
$position = [$this->getLng(), $this->getLat()];
106+
if($this->is3d()) $position[] = $this->getAlt();
107+
return new \GeoJson\Geometry\Point($position);
78108
}
79109
}

src/Geometries/Polygon.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@
44

55
class Polygon extends MultiLineString implements Countable
66
{
7+
public function is3d()
8+
{
9+
if(count($this->linestrings) === 0) return false;
10+
return $this->linestrings[0]->is3d();
11+
}
712

813
public function toWKT()
914
{
10-
return sprintf('POLYGON(%s)', (string)$this);
15+
$wktType = 'POLYGON';
16+
if($this->is3d()) $wktType .= ' Z';
17+
return sprintf('%s(%s)', $wktType, (string)$this);
1118
}
1219

1320
/**

tests/Eloquent/BuilderTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,51 @@ public function testUpdateLinestring()
8686

8787
$builder->update(['linestring' => $linestring]);
8888
}
89+
90+
public function testUpdate3d()
91+
{
92+
$this->queryBuilder
93+
->shouldReceive('raw')
94+
->with("public.ST_GeogFromText('POINT Z(2 1 0)')")
95+
->andReturn(new Expression("public.ST_GeogFromText('POINT Z(2 1 0)')"));
96+
97+
$this->queryBuilder
98+
->shouldReceive('update')
99+
->andReturn(1);
100+
101+
$builder = m::mock(Builder::class, [$this->queryBuilder])->makePartial();
102+
$builder->shouldAllowMockingProtectedMethods();
103+
$builder
104+
->shouldReceive('addUpdatedAtColumn')
105+
->andReturn(['point' => new Point(1, 2, 0)]);
106+
107+
$builder->update(['point' => new Point(1, 2, 0)]);
108+
}
109+
110+
public function testUpdateLinestring3d()
111+
{
112+
$this->queryBuilder
113+
->shouldReceive('raw')
114+
->with("public.ST_GeogFromText('LINESTRING Z(0 0 0, 1 1 1, 2 2 2)')")
115+
->andReturn(new Expression("public.ST_GeogFromText('LINESTRING Z(0 0 0, 1 1 1, 2 2 2)')"));
116+
117+
$this->queryBuilder
118+
->shouldReceive('update')
119+
->andReturn(1);
120+
121+
$linestring = new LineString([new Point(0, 0, 0), new Point(1, 1, 1), new Point(2, 2, 2)]);
122+
123+
$builder = m::mock(Builder::class, [$this->queryBuilder])->makePartial();
124+
$builder->shouldAllowMockingProtectedMethods();
125+
$builder
126+
->shouldReceive('addUpdatedAtColumn')
127+
->andReturn(['linestring' => $linestring]);
128+
129+
$builder
130+
->shouldReceive('asWKT')->with($linestring)->once();
131+
132+
$builder->update(['linestring' => $linestring]);
133+
}
89134
}
90135

91136
class TestBuilderModel extends Model

tests/Eloquent/PostgisTraitTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,31 @@ public function testUpdatePointHasCorrectSql()
5353

5454
$this->assertContains("public.ST_GeogFromText('POINT(4 2)')", $this->queries[0]);
5555
}
56+
57+
public function testInsertPoint3dHasCorrectSql()
58+
{
59+
$this->model->point = new Point(1, 2, 3);
60+
$this->model->save();
61+
62+
$this->assertContains("public.ST_GeogFromText('POINT Z(2 1 3)')", $this->queries[0]);
63+
}
64+
65+
public function testInsertPoint3dGeometryHasCorrectSql()
66+
{
67+
$this->model->point2 = new Point(1, 2, 3);
68+
$this->model->save();
69+
70+
$this->assertContains("public.ST_GeomFromText('POINT Z(2 1 3)', '27700')", $this->queries[0]);
71+
}
72+
73+
public function testUpdatePoint3dHasCorrectSql()
74+
{
75+
$this->model->exists = true;
76+
$this->model->point = new Point(2, 4, 6);
77+
$this->model->save();
78+
79+
$this->assertContains("public.ST_GeogFromText('POINT Z(4 2 6)')", $this->queries[0]);
80+
}
5681
}
5782

5883
class TestModel extends Model

tests/Geometries/GeometryCollectionTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class GeometryCollectionTest extends BaseTestCase
1010
* @var GeometryCollection
1111
*/
1212
private $collection;
13+
private $collection3d;
1314

1415
protected function setUp()
1516
{
@@ -26,6 +27,20 @@ protected function setUp()
2627
$point = new Point(100, 200);
2728

2829
$this->collection = new GeometryCollection([$collection, $point]);
30+
31+
$collection = new LineString(
32+
[
33+
new Point(1, 1, 1),
34+
new Point(1, 2, 3),
35+
new Point(2, 2, 2),
36+
new Point(2, 1, 0),
37+
new Point(1, 1, 1)
38+
]
39+
);
40+
41+
$point = new Point(100, 200, 300);
42+
43+
$this->collection3d = new GeometryCollection([$collection, $point]);
2944
}
3045

3146

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

60+
public function testFromWKT3d()
61+
{
62+
/**
63+
* @var GeometryCollection $geometryCollection
64+
*/
65+
$geometryCollection = GeometryCollection::fromWKT('GEOMETRYCOLLECTION(POINT Z(2 3 4),LINESTRING Z(2 3 4,3 4 5))');
66+
$this->assertInstanceOf(GeometryCollection::class, $geometryCollection);
67+
68+
$this->assertEquals(2, $geometryCollection->count());
69+
$this->assertInstanceOf(Point::class, $geometryCollection->getGeometries()[0]);
70+
$this->assertInstanceOf(LineString::class, $geometryCollection->getGeometries()[1]);
71+
}
72+
4573
public function testToWKT()
4674
{
4775
$this->assertEquals(
@@ -50,6 +78,14 @@ public function testToWKT()
5078
);
5179
}
5280

81+
public function testToWKT3d()
82+
{
83+
$this->assertEquals(
84+
'GEOMETRYCOLLECTION(LINESTRING Z(1 1 1,2 1 3,2 2 2,1 2 0,1 1 1),POINT Z(200 100 300))',
85+
$this->collection3d->toWKT()
86+
);
87+
}
88+
5389
public function testJsonSerialize()
5490
{
5591
$this->assertInstanceOf(
@@ -63,4 +99,18 @@ public function testJsonSerialize()
6399
);
64100

65101
}
102+
103+
public function testJsonSerialize3d()
104+
{
105+
$this->assertInstanceOf(
106+
\GeoJson\Geometry\GeometryCollection::class,
107+
$this->collection3d->jsonSerialize()
108+
);
109+
110+
$this->assertSame(
111+
'{"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]}]}',
112+
json_encode($this->collection3d->jsonSerialize())
113+
);
114+
115+
}
66116
}

0 commit comments

Comments
 (0)