-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
LineString.php
237 lines (200 loc) · 6.29 KB
/
LineString.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<?php
declare(strict_types=1);
namespace Brick\Geo;
use ArrayIterator;
use Brick\Geo\Attribute\NoProxy;
use Brick\Geo\Exception\CoordinateSystemException;
use Brick\Geo\Exception\EmptyGeometryException;
use Brick\Geo\Exception\InvalidGeometryException;
use Brick\Geo\Exception\NoSuchGeometryException;
use Brick\Geo\Projector\Projector;
/**
* A LineString is a Curve with linear interpolation between Points.
*
* Each consecutive pair of Points defines a line segment.
*/
class LineString extends Curve
{
/**
* The Points that compose this LineString.
*
* An empty LineString contains no points.
* A non-empty LineString contains a minimum of 2 points.
*
* @psalm-var list<Point>
*
* @var Point[]
*/
protected array $points = [];
/**
* A LineString must be composed of 2 points or more, or 0 points for an empty LineString.
* A LineString with exactly 1 point is not allowed.
*
* The coordinate system of each of the points must match the one of the LineString.
*
* @param CoordinateSystem $cs The coordinate system of the LineString.
* @param Point ...$points The points that compose the LineString.
*
* @throws InvalidGeometryException If only one point was given.
* @throws CoordinateSystemException If different coordinate systems are used.
*/
public function __construct(CoordinateSystem $cs, Point ...$points)
{
parent::__construct($cs, ! $points);
if (! $points) {
return;
}
CoordinateSystem::check($this, ...$points);
if (count($points) < 2) {
throw new InvalidGeometryException('A LineString must be composed of at least 2 points.');
}
$this->points = array_values($points);
}
/**
* Creates a non-empty LineString composed of the given points.
*
* @param Point $point1 The first point.
* @param Point ...$pointN The subsequent points.
*
* @throws InvalidGeometryException If only one point was given.
* @throws CoordinateSystemException If the points use different coordinate systems.
*/
public static function of(Point $point1, Point ...$pointN) : LineString
{
return new LineString($point1->coordinateSystem(), $point1, ...$pointN);
}
/**
* Creates a rectangle out of two 2D corner points.
*
* The result is a linear ring (closed and simple).
*
* @psalm-suppress PossiblyNullArgument
*
* @throws EmptyGeometryException If any of the points is empty.
* @throws CoordinateSystemException If the points use different coordinate systems, or are not 2D.
*/
public static function rectangle(Point $a, Point $b) : LineString
{
$cs = $a->coordinateSystem();
if (! $cs->isEqualTo($b->coordinateSystem())) {
throw CoordinateSystemException::dimensionalityMix($cs, $b->coordinateSystem());
}
if ($cs->coordinateDimension() !== 2) {
throw new CoordinateSystemException(__METHOD__ . ' expects 2D points.');
}
if ($a->isEmpty() || $b->isEmpty()) {
throw new EmptyGeometryException('Points cannot be empty.');
}
$x1 = min($a->x(), $b->x());
$x2 = max($a->x(), $b->x());
$y1 = min($a->y(), $b->y());
$y2 = max($a->y(), $b->y());
$p1 = new Point($cs, $x1, $y1);
$p2 = new Point($cs, $x2, $y1);
$p3 = new Point($cs, $x2, $y2);
$p4 = new Point($cs, $x1, $y2);
return new LineString($cs, $p1, $p2, $p3, $p4, $p1);
}
public function startPoint() : Point
{
if ($this->isEmpty) {
throw new EmptyGeometryException('The LineString is empty and has no start point.');
}
return $this->points[0];
}
public function endPoint() : Point
{
if ($this->isEmpty) {
throw new EmptyGeometryException('The LineString is empty and has no end point.');
}
return end($this->points);
}
/**
* Returns the number of Points in this LineString.
*/
public function numPoints() : int
{
return count($this->points);
}
/**
* Returns the specified Point N in this LineString.
*
* @param int $n The point number, 1-based.
*
* @throws NoSuchGeometryException If there is no Point at this index.
*/
public function pointN(int $n) : Point
{
if (! isset($this->points[$n - 1])) {
throw new NoSuchGeometryException('There is no Point in this LineString at index ' . $n);
}
return $this->points[$n - 1];
}
/**
* Returns the points that compose this LineString.
*
* @psalm-return list<Point>
*
* @return Point[]
*/
public function points() : array
{
return $this->points;
}
#[NoProxy]
public function geometryType() : string
{
return 'LineString';
}
#[NoProxy]
public function geometryTypeBinary() : int
{
return Geometry::LINESTRING;
}
public function getBoundingBox() : BoundingBox
{
$boundingBox = new BoundingBox();
foreach ($this->points as $point) {
$boundingBox = $boundingBox->extendedWithPoint($point);
}
return $boundingBox;
}
public function toArray() : array
{
$result = [];
foreach ($this->points as $point) {
$result[] = $point->toArray();
}
return $result;
}
public function project(Projector $projector): LineString
{
return new LineString(
$projector->getTargetCoordinateSystem($this->coordinateSystem),
...array_map(
fn (Point $point) => $point->project($projector),
$this->points,
),
);
}
/**
* Returns the number of points in this LineString.
*
* Required by interface Countable.
*/
public function count() : int
{
return count($this->points);
}
/**
* Returns an iterator for the points in this LineString.
*
* Required by interface IteratorAggregate.
*
* @psalm-return ArrayIterator<int, Point>
*/
public function getIterator() : ArrayIterator
{
return new ArrayIterator($this->points);
}
}