Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 86 additions & 34 deletions src/Line.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Location\Bearing\BearingInterface;
use Location\Distance\DistanceInterface;
use Location\Intersection\Intersection;
use Location\Utility\Cartesian;
use RuntimeException;

Expand Down Expand Up @@ -62,7 +61,7 @@ public function getSegments(): array
* Calculates the length of the line (distance between the two
* coordinates in meters).
*
* @param DistanceInterface $calculator instance of distance calculation class
* @param DistanceInterface $calculator instance of distance calculation class
*/
public function getLength(DistanceInterface $calculator): float
{
Expand Down Expand Up @@ -117,7 +116,7 @@ public function getMidpoint(): Coordinate
* @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)
* @param float $fraction 0.0 ... 1.0 (smaller or larger values work too)
*
* @throws RuntimeException
*/
Expand All @@ -133,7 +132,7 @@ public function getIntermediatePoint(float $fraction): Coordinate
if ($lat1 + $lat2 == 0.0 && abs($lng1 - $lng2) == M_PI) {
throw new RuntimeException(
'Start and end points are antipodes, route is therefore undefined.',
5382449689
5382449689,
);
}

Expand All @@ -153,6 +152,71 @@ public function getIntermediatePoint(float $fraction): Coordinate
return new Coordinate(rad2deg($lat), rad2deg($lng));
}

/**
* 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 line segments are overlapping
*/
public function intersectsLine(Line $line): bool
{
$orientation = [];
$orientation[11] = $this->getOrientation($line->point1);
$orientation[12] = $this->getOrientation($line->point2);
$orientation[21] = $line->getOrientation($this->point1);
$orientation[22] = $line->getOrientation($this->point2);

// 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)) {
return false;
}

if (
$orientation[11] === self::ORIENTATION_COLLINEAR && $this->isPointInBounds(
$line->point1,
$this->getBounds(),
)
) {
return true;
}
if (
$orientation[12] === self::ORIENTATION_COLLINEAR && $this->isPointInBounds(
$line->point2,
$this->getBounds(),
)
) {
return true;
}
if (
$orientation[21] === self::ORIENTATION_COLLINEAR && $this->isPointInBounds(
$this->point1,
$line->getBounds(),
)
) {
return true;
}
if (
$orientation[22] === self::ORIENTATION_COLLINEAR && $this->isPointInBounds(
$this->point2,
$line->getBounds(),
)
) {
return true;
}

// the lines do not overlap
return false;
}

/**
* Compares the location of a given coordinate to this line returning
* its orientation as:
Expand All @@ -164,9 +228,9 @@ public function getIntermediatePoint(float $fraction): Coordinate
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());
* $this->getDeltaLng($this->point2, $coordinate);
$crossproduct2 = $this->getDeltaLng($this->point1, $this->point2)
* ($coordinate->getLat() - $this->point2->getLat());
$delta = $crossproduct1 - $crossproduct2;

if ($delta > 0) {
Expand All @@ -180,38 +244,26 @@ public function getOrientation(Coordinate $coordinate): int
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
private function getDeltaLng(Coordinate $point1, Coordinate $point2): float
{
$orientation = [];
$orientation[11] = $this->getOrientation($line->point1);
$orientation[12] = $this->getOrientation($line->point2);
$orientation[21] = $line->getOrientation($this->point1);
$orientation[22] = $line->getOrientation($this->point2);
$deltaLng = $point2->getLng() - $point1->getLng();

// the lines cross
if (
$orientation[11] !== $orientation[12]
&& $orientation[21] !== $orientation[22]
) {
return true;
if ($deltaLng > 180) {
return $deltaLng - 360;
}

// the lines are collinear or touch
if (
in_array(self::ORIENTATION_COLLINEAR, $orientation, true)
&& (new Intersection())->intersects($this, $line, false)
) {
return true;
if ($deltaLng < -180) {
return $deltaLng + 360;
}

// the lines do not overlap
return false;
return $deltaLng;
}

private function isPointInBounds(Coordinate $point, Bounds $bounds): bool
{
return $point->getLat() >= $bounds->getSouth()
&& $point->getLat() <= $bounds->getNorth()
&& $point->getLng() >= $bounds->getWest()
&& $point->getLng() <= $bounds->getEast();
}
}
66 changes: 66 additions & 0 deletions tests/Location/LineIntersectionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Location;

use PHPUnit\Framework\TestCase;

class LineIntersectionTest extends TestCase
{
public function testIntersectsLineSimpleCrossing(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 10));
$line2 = new Line(new Coordinate(0, 10), new Coordinate(10, 0));

$this->assertTrue($line1->intersectsLine($line2));
}

public function testIntersectsLineParallelDisjoint(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 0));
$line2 = new Line(new Coordinate(0, 1), new Coordinate(10, 1));

$this->assertFalse($line1->intersectsLine($line2));
}

public function testIntersectsLineCollinearOverlapping(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 0));
$line2 = new Line(new Coordinate(5, 0), new Coordinate(15, 0));

$this->assertTrue($line1->intersectsLine($line2));
}

public function testIntersectsLineCollinearDisjoint(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 0));
$line2 = new Line(new Coordinate(12, 0), new Coordinate(15, 0));

$this->assertFalse($line1->intersectsLine($line2));
}

public function testIntersectsLineSharingEndpoint(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 0));
$line2 = new Line(new Coordinate(10, 0), new Coordinate(10, 10));

$this->assertTrue($line1->intersectsLine($line2));
}

public function testIntersectsLineTJunction(): void
{
$line1 = new Line(new Coordinate(0, 0), new Coordinate(10, 0));
$line2 = new Line(new Coordinate(5, 0), new Coordinate(5, 5));

$this->assertTrue($line1->intersectsLine($line2));
}

public function testIntersectsLineDatelineCrossing(): void
{
$line1 = new Line(new Coordinate(0, 179), new Coordinate(0, -179));
$line2 = new Line(new Coordinate(1, 180), new Coordinate(-1, 180));

$this->assertTrue($line1->intersectsLine($line2));
}
}
38 changes: 38 additions & 0 deletions tests/Regression/Github/96/Issue96Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Location;

use PHPUnit\Framework\TestCase;

class Issue96Test extends TestCase
{
public function testFailingForIssue96(): void
{
$line1 = new Line(
new Coordinate(0.0, 0.0),
new Coordinate(0.0, 2.0),
);
$line2 = new Line(
new Coordinate(2.0, 2.0),
new Coordinate(0.0, 10.0),
);

$this->assertFalse($line1->intersectsLine($line2));
}

public function testPassingForIssue96(): void
{
$line1 = new Line(
new Coordinate(0.0, 0.0),
new Coordinate(0.0, 1.999999),
);
$line2 = new Line(
new Coordinate(2.0, 2.0),
new Coordinate(0.0, 10.0),
);

$this->assertFalse($line1->intersectsLine($line2));
}
}