You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
6.3 KiB
237 lines
6.3 KiB
<?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); |
|
} |
|
}
|
|
|