1<?php
2/**
3 * LineString. A collection of Points representing a line.
4 * A line can have more than one segment.
5 */
6class LineString extends Collection
7{
8  protected $geom_type = 'LineString';
9
10  /**
11   * Constructor
12   *
13   * @param array $points An array of at least two points with
14   * which to build the LineString
15   */
16  public function __construct($points = array()) {
17    if (count($points) == 1) {
18      throw new Exception("Cannot construct a LineString with a single point");
19    }
20
21    // Call the Collection constructor to build the LineString
22    parent::__construct($points);
23  }
24
25  // The boundary of a linestring is itself
26  public function boundary() {
27    return $this;
28  }
29
30  public function startPoint() {
31    return $this->pointN(1);
32  }
33
34  public function endPoint() {
35    $last_n = $this->numPoints();
36    return $this->pointN($last_n);
37  }
38
39  public function isClosed() {
40    return ($this->startPoint()->equals($this->endPoint()));
41  }
42
43  public function isRing() {
44    return ($this->isClosed() && $this->isSimple());
45  }
46
47  public function numPoints() {
48    return $this->numGeometries();
49  }
50
51  public function pointN($n) {
52    return $this->geometryN($n);
53  }
54
55  public function dimension() {
56    if ($this->isEmpty()) return 0;
57    return 1;
58  }
59
60  public function area() {
61    return 0;
62  }
63
64  public function length() {
65    if ($this->geos()) {
66      return $this->geos()->length();
67    }
68    $length = 0;
69    foreach ($this->getPoints() as $delta => $point) {
70      $previous_point = $this->geometryN($delta);
71      if ($previous_point) {
72        $length += sqrt(pow(($previous_point->getX() - $point->getX()), 2) + pow(($previous_point->getY()- $point->getY()), 2));
73      }
74    }
75    return $length;
76  }
77
78  public function greatCircleLength($radius = 6378137) {
79    $length = 0;
80    $points = $this->getPoints();
81    for($i=0; $i<$this->numPoints()-1; $i++) {
82      $point = $points[$i];
83      $next_point = $points[$i+1];
84      if (!is_object($next_point)) {continue;}
85      // Great circle method
86      $lat1 = deg2rad($point->getY());
87      $lat2 = deg2rad($next_point->getY());
88      $lon1 = deg2rad($point->getX());
89      $lon2 = deg2rad($next_point->getX());
90      $dlon = $lon2 - $lon1;
91      $length +=
92        $radius *
93          atan2(
94            sqrt(
95              pow(cos($lat2) * sin($dlon), 2) +
96                pow(cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($dlon), 2)
97            )
98            ,
99            sin($lat1) * sin($lat2) +
100              cos($lat1) * cos($lat2) * cos($dlon)
101          );
102    }
103    // Returns length in meters.
104    return $length;
105  }
106
107  public function haversineLength() {
108    $degrees = 0;
109    $points = $this->getPoints();
110    for($i=0; $i<$this->numPoints()-1; $i++) {
111      $point = $points[$i];
112      $next_point = $points[$i+1];
113      if (!is_object($next_point)) {continue;}
114      $degree = rad2deg(
115        acos(
116          sin(deg2rad($point->getY())) * sin(deg2rad($next_point->getY())) +
117            cos(deg2rad($point->getY())) * cos(deg2rad($next_point->getY())) *
118              cos(deg2rad(abs($point->getX() - $next_point->getX())))
119        )
120      );
121      $degrees += $degree;
122    }
123    // Returns degrees
124    return $degrees;
125  }
126
127  public function explode() {
128    $parts = array();
129    $points = $this->getPoints();
130
131    foreach ($points as $i => $point) {
132      if (isset($points[$i+1])) {
133        $parts[] = new LineString(array($point, $points[$i+1]));
134      }
135    }
136    return $parts;
137  }
138
139  public function isSimple() {
140    if ($this->geos()) {
141      return $this->geos()->isSimple();
142    }
143
144    $segments = $this->explode();
145
146    foreach ($segments as $i => $segment) {
147      foreach ($segments as $j => $check_segment) {
148        if ($i != $j) {
149          if ($segment->lineSegmentIntersect($check_segment)) {
150            return FALSE;
151          }
152        }
153      }
154    }
155    return TRUE;
156  }
157
158  // Utility function to check if any line sigments intersect
159  // Derived from http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
160  public function lineSegmentIntersect($segment) {
161    $p0_x = $this->startPoint()->x();
162    $p0_y = $this->startPoint()->y();
163    $p1_x = $this->endPoint()->x();
164    $p1_y = $this->endPoint()->y();
165    $p2_x = $segment->startPoint()->x();
166    $p2_y = $segment->startPoint()->y();
167    $p3_x = $segment->endPoint()->x();
168    $p3_y = $segment->endPoint()->y();
169
170    $s1_x = $p1_x - $p0_x;     $s1_y = $p1_y - $p0_y;
171    $s2_x = $p3_x - $p2_x;     $s2_y = $p3_y - $p2_y;
172
173    $fps = (-$s2_x * $s1_y) + ($s1_x * $s2_y);
174    $fpt = (-$s2_x * $s1_y) + ($s1_x * $s2_y);
175
176    if ($fps == 0 || $fpt == 0) {
177      return FALSE;
178    }
179
180    $s = (-$s1_y * ($p0_x - $p2_x) + $s1_x * ($p0_y - $p2_y)) / $fps;
181    $t = ( $s2_x * ($p0_y - $p2_y) - $s2_y * ($p0_x - $p2_x)) / $fpt;
182
183    if ($s > 0 && $s < 1 && $t > 0 && $t < 1) {
184      // Collision detected
185      return TRUE;
186    }
187    return FALSE;
188  }
189}
190
191