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

<?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);
}
}