*/ class Line implements GeometryInterface { use GetBoundsTrait; public const ORIENTATION_COLLINEAR = 0; public const ORIENTATION_CLOCKWISE = 1; public const ORIENTATION_ANTI_CLOCKWISE = 2; /** * @var Coordinate */ protected $point1; /** * @var Coordinate */ protected $point2; /** * @param Coordinate $point1 * @param Coordinate $point2 */ public function __construct(Coordinate $point1, Coordinate $point2) { $this->point1 = $point1; $this->point2 = $point2; } /** * @param Coordinate $point1 * * @return void * * @deprecated */ public function setPoint1(Coordinate $point1) { $this->point1 = $point1; } /** * @return Coordinate */ public function getPoint1(): Coordinate { return $this->point1; } /** * @param Coordinate $point2 * * @return void * * @deprecated */ public function setPoint2(Coordinate $point2) { $this->point2 = $point2; } /** * @return Coordinate */ public function getPoint2(): Coordinate { return $this->point2; } /** * Returns an array containing the two points. * * @return array */ public function getPoints(): array { return [$this->point1, $this->point2]; } /** * Returns an array containing the line segment. * * @return array */ public function getSegments(): array { return [$this]; } /** * Calculates the length of the line (distance between the two * coordinates). * * @param DistanceInterface $calculator instance of distance calculation class * * @return float */ public function getLength(DistanceInterface $calculator): float { return $calculator->getDistance($this->point1, $this->point2); } /** * @param BearingInterface $bearingCalculator * * @return float */ public function getBearing(BearingInterface $bearingCalculator): float { return $bearingCalculator->calculateBearing($this->point1, $this->point2); } /** * @param BearingInterface $bearingCalculator * * @return float */ public function getFinalBearing(BearingInterface $bearingCalculator): float { return $bearingCalculator->calculateFinalBearing($this->point1, $this->point2); } /** * Create a new instance with reversed point order, i. e. reversed direction. * * @return Line */ public function getReverse(): Line { return new self($this->point2, $this->point1); } /** * Get the midpoint of a Line segment * * @see http://www.movable-type.co.uk/scripts/latlong.html#midpoint * * @return Coordinate */ public function getMidpoint(): Coordinate { $lat1 = deg2rad($this->point1->getLat()); $lng1 = deg2rad($this->point1->getLng()); $lat2 = deg2rad($this->point2->getLat()); $lng2 = deg2rad($this->point2->getLng()); $deltaLng = $lng2 - $lng1; $A = new Cartesian(cos($lat1), 0, sin($lat1)); $B = new Cartesian(cos($lat2) * cos($deltaLng), cos($lat2) * sin($deltaLng), sin($lat2)); $C = $A->add($B); $latMid = atan2($C->getZ(), sqrt($C->getX() ** 2 + $C->getY() ** 2)); $lngMid = $lng1 + atan2($C->getY(), $C->getX()); return new Coordinate(rad2deg($latMid), rad2deg($lngMid)); } /** * Returns the point which is located on the line at the * given fraction (starting at point 1). * * @see http://www.movable-type.co.uk/scripts/latlong.html#intermediate-point * @see http://www.edwilliams.org/avform.htm#Intermediate * * @param float $fraction 0.0 ... 1.0 (smaller or larger values work too) * * @return Coordinate * * @throws RuntimeException */ public function getIntermediatePoint(float $fraction): Coordinate { $lat1 = deg2rad($this->point1->getLat()); $lng1 = deg2rad($this->point1->getLng()); $lat2 = deg2rad($this->point2->getLat()); $lng2 = deg2rad($this->point2->getLng()); $deltaLat = $lat2 - $lat1; $deltaLng = $lng2 - $lng1; if ($lat1 + $lat2 == 0.0 && abs($lng1 - $lng2) == M_PI) { throw new RuntimeException( 'Start and end points are antipodes, route is therefore undefined.', 5382449689 ); } $a = sin($deltaLat / 2) ** 2 + cos($lat1) * cos($lat2) * sin($deltaLng / 2) ** 2; $delta = 2 * atan2(sqrt($a), sqrt(1 - $a)); $A = sin((1 - $fraction) * $delta) / sin($delta); $B = sin($fraction * $delta) / sin($delta); $x = $A * cos($lat1) * cos($lng1) + $B * cos($lat2) * cos($lng2); $y = $A * cos($lat1) * sin($lng1) + $B * cos($lat2) * sin($lng2); $z = $A * sin($lat1) + $B * sin($lat2); $lat = atan2($z, sqrt($x ** 2 + $y ** 2)); $lng = atan2($y, $x); return new Coordinate(rad2deg($lat), rad2deg($lng)); } /** * Compares the location of a given coordinate to this line returning * its orientation as: * * - 0 if the coordinate is collinear to this line segment * - 1 if the coordinate's orientation is clockwise to this line segment * - 2 if the coordinate's orientation is anti-clockwise to this line segment */ public function getOrientation(Coordinate $coordinate): int { $crossproduct1 = ($this->point2->getLat() - $this->point1->getLat()) * ($coordinate->getLng() - $this->point2->getLng()); $crossproduct2 = ($this->point2->getLng() - $this->point1->getLng()) * ($coordinate->getLat() - $this->point2->getLat()); $delta = $crossproduct1 - $crossproduct2; if ($delta > 0) { return self::ORIENTATION_CLOCKWISE; } if ($delta < 0) { return self::ORIENTATION_ANTI_CLOCKWISE; } return self::ORIENTATION_COLLINEAR; } /** * Two lines intersect if: * * 1. the points of the given line are oriented into opposite directions * 2. the points of this line are oriented into opposite directions * 3. the points are collinear and the two line segments are overlapping */ public function intersectsLine(Line $line): bool { $orientation = []; $orientation[11] = $this->getOrientation($line->getPoint1()); $orientation[12] = $this->getOrientation($line->getPoint2()); $orientation[21] = $line->getOrientation($this->getPoint1()); $orientation[22] = $line->getOrientation($this->getPoint2()); // the lines cross if ( $orientation[11] !== $orientation[12] && $orientation[21] !== $orientation[22] ) { return true; } // the lines are collinear or touch if ( in_array(self::ORIENTATION_COLLINEAR, $orientation, true) && (new Intersection())->intersects($this, $line, false) ) { return true; } // the lines do not overlap return false; } }