From be0ce05480a17dc2b87db7845dc654b02056ba51 Mon Sep 17 00:00:00 2001 From: Patrick Hayes Date: Wed, 18 Apr 2012 18:35:24 -0700 Subject: [PATCH] Various clean-up, finishing basic geometric methods --- geoPHP.inc | 3 +- lib/adapters/WKB.class.php | 3 -- lib/geometry/Collection.class.php | 76 ++++++++++++++++++++++++++++++- lib/geometry/Geometry.class.php | 43 +++++++++-------- lib/geometry/LineString.class.php | 68 +++++++++++++++++++++++++-- lib/geometry/MultiPoint.class.php | 12 +++++ lib/geometry/Point.class.php | 19 +++++++- lib/geometry/Polygon.class.php | 19 ++++++++ 8 files changed, 212 insertions(+), 31 deletions(-) diff --git a/geoPHP.inc b/geoPHP.inc index baa795d8..16eea20b 100644 --- a/geoPHP.inc +++ b/geoPHP.inc @@ -114,8 +114,7 @@ class geoPHP } $wkb_writer = new GEOSWKBWriter(); $wkb = $wkb_writer->writeHEX($geos); - $geometry = geoPHP::load($wkb,'wkb',TRUE); - //@@TODO: We no longer need to check this once we have Empty geometries + $geometry = geoPHP::load($wkb, 'wkb', TRUE); if ($geometry) { $geometry->setGeos($geos); return $geometry; diff --git a/lib/adapters/WKB.class.php b/lib/adapters/WKB.class.php index e21ec421..3ef4f651 100644 --- a/lib/adapters/WKB.class.php +++ b/lib/adapters/WKB.class.php @@ -123,9 +123,6 @@ function getMulti(&$mem, $type) { // Get the number of items expected in this multi out of the first 4 bytes $multi_length = unpack('L',fread($mem,4)); - //@@TODO: create an EMPTY geometry instead of returning null - if (!$multi_length[1]) return NULL; - $components = array(); $i = 1; while ($i <= $multi_length[1]) { diff --git a/lib/geometry/Collection.class.php b/lib/geometry/Collection.class.php index 1b4ee505..8c83d0d6 100644 --- a/lib/geometry/Collection.class.php +++ b/lib/geometry/Collection.class.php @@ -195,6 +195,81 @@ public function isEmpty() { } } + public function numPoints() { + $num = 0; + foreach ($this->components as $component) { + $num += $component->numPoints(); + } + return $num; + } + + public function getPoints() { + $points = array(); + foreach ($this->components as $component) { + $points = array_merge($points, $component->getPoints()); + } + return $points; + } + + public function equals($geometry) { + if ($this->geos()) { + return $this->geos()->equals($geometry->geos()); + } + + // To test for equality we check to make sure that there is a matching point + // in the other geometry for every point in this geometry. + // This is slightly more strict than the standard, which + // uses Within(A,B) = true and Within(B,A) = true + // @@TODO: Eventually we could fix this by using some sort of simplification + // method that strips redundant vertices (that are all in a row) + + $this_points = $this->getPoints(); + $other_points = $geometry->getPoints(); + + // First do a check to make sure they have the same number of vertices + if (count($this_points) != count($other_points)) { + return FALSE; + } + + foreach ($this_points as $point) { + $found_match = FALSE; + foreach ($other_points as $key => $test_point) { + if ($point->equals($test_point)) { + $found_match = TRUE; + unset($other_points[$key]); + break; + } + } + if (!$found_match) { + return FALSE; + } + } + + // All points match, return TRUE + return TRUE; + } + + public function isSimple() { + if ($this->geos()) { + return $this->geos()->isSimple(); + } + + // A collection is simple if all it's components are simple + foreach ($this->components as $component) { + if (!$component->isSimple()) return FALSE; + } + + return TRUE; + } + + public function explode() { + $parts = array(); + foreach ($this->components as $component) { + $parts = array_merge($parts, $component->explode()); + } + return $parts; + } + // Not valid for this geometry type // -------------------------------- public function x() { return NULL; } @@ -203,7 +278,6 @@ public function startPoint() { return NULL; } public function endPoint() { return NULL; } public function isRing() { return NULL; } public function isClosed() { return NULL; } - public function numPoints() { return NULL; } public function pointN($n) { return NULL; } public function exteriorRing() { return NULL; } public function numInteriorRings() { return NULL; } diff --git a/lib/geometry/Geometry.class.php b/lib/geometry/Geometry.class.php index db76f517..097fb852 100644 --- a/lib/geometry/Geometry.class.php +++ b/lib/geometry/Geometry.class.php @@ -19,7 +19,7 @@ abstract public function y(); abstract public function x(); abstract public function numGeometries(); abstract public function geometryN($n); - abstract public function startPoint(); + abstract public function startPoint(); abstract public function endPoint(); abstract public function isRing(); // Mssing dependancy abstract public function isClosed(); // Missing dependancy @@ -29,12 +29,16 @@ abstract public function exteriorRing(); abstract public function numInteriorRings(); abstract public function interiorRingN($n); abstract public function dimension(); + abstract public function equals($geom); abstract public function isEmpty(); + abstract public function isSimple(); // Abtract: Non-Standard // --------------------- abstract public function getBBox(); abstract public function asArray(); + abstract public function getPoints(); + abstract public function explode(); // Public: Standard -- Common to all geometries @@ -127,7 +131,7 @@ public function asText() { return $this->out('wkt'); } - public function asBinary() { + public function asBinary() { return $this->out('wkb'); } @@ -159,22 +163,20 @@ public function pointOnSurface() { } } - public function equals($geometry) { - if ($this->geos()) { - return $this->geos()->equals($geometry->geos()); - } - } - public function equalsExact($geometry) { if ($this->geos()) { return $this->geos()->equalsExact($geometry->geos()); } } - public function relate($geometry) { - //@@TODO: Deal with second "pattern" parameter + public function relate($geometry, $pattern = NULL) { if ($this->geos()) { - return $this->geos()->relate($geometry->geos()); + if ($pattern) { + return $this->geos()->relate($geometry->geos(), $pattern); + } + else { + return $this->geos()->relate($geometry->geos()); + } } } @@ -183,12 +185,6 @@ public function checkValidity() { return $this->geos()->checkValidity(); } } - - public function isSimple() { - if ($this->geos()) { - return $this->geos()->isSimple(); - } - } public function buffer($distance) { if ($this->geos()) { @@ -220,10 +216,19 @@ public function symDifference($geometry) { } } + // Can pass in a geometry or an array of geometries public function union($geometry) { - //@@TODO: also does union cascade if ($this->geos()) { - return geoPHP::geosToGeometry($this->geos()->union($geometry->geos())); + if (is_array($geometry)) { + $geom = $this->geos(); + foreach ($geometry as $item) { + $geom = $geom->union($item->geos()); + } + return geoPHP::geosToGeometry($geos); + } + else { + return geoPHP::geosToGeometry($this->geos()->union($geometry->geos())); + } } } diff --git a/lib/geometry/LineString.class.php b/lib/geometry/LineString.class.php index f7bb34c2..f0be25a2 100644 --- a/lib/geometry/LineString.class.php +++ b/lib/geometry/LineString.class.php @@ -37,13 +37,11 @@ public function endPoint() { } public function isClosed() { - //@@TODO: Need to complete equal() first; - #return ($this->startPoint->equal($this->endPoint())); + return ($this->startPoint()->equals($this->endPoint())); } public function isRing() { - //@@TODO: need to complete isSimple first - #return ($this->isClosed() && $this->isSimple()); + return ($this->isClosed() && $this->isSimple()); } public function numPoints() { @@ -62,6 +60,68 @@ public function dimension() { public function area() { return 0; } + + public function explode() { + $parts = array(); + $points = $this->getPoints(); + + foreach ($points as $i => $point) { + if (isset($points[$i+1])) { + $parts[] = new LineString(array($point, $points[$i+1])); + } + } + return $parts; + } + + public function isSimple() { + if ($this->geos()) { + return $this->geos()->isSimple(); + } + + $segments = $this->explode(); + + foreach ($segments as $i => $segment) { + foreach ($segments as $j => $check_segment) { + if ($i != $j) { + if ($segment->lineSegmentIntersect($check_segment)) { + return FALSE; + } + } + } + } + return TRUE; + } + + // Utility function to check if any line sigments intersect + // Derived from http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect + private function lineSegmentIntersect($segment) { + $p0_x = $this->startPoint()->x(); + $p0_y = $this->startPoint()->y(); + $p1_x = $this->endPoint()->x(); + $p1_y = $this->endPoint()->y(); + $p2_x = $segment->startPoint()->x(); + $p2_y = $segment->startPoint()->y(); + $p3_x = $segment->endPoint()->x(); + $p3_y = $segment->endPoint()->y(); + + $s1_x = $p1_x - $p0_x; $s1_y = $p1_y - $p0_y; + $s2_x = $p3_x - $p2_x; $s2_y = $p3_y - $p2_y; + + $fps = (-$s2_x * $s1_y) + ($s1_x * $s2_y); + $fpt = (-$s2_x * $s1_y) + ($s1_x * $s2_y); + + if ($fps == 0 || $fpt == 0) { + return FALSE; + } + + $s = (-$s1_y * ($p0_x - $p2_x) + $s1_x * ($p0_y - $p2_y)) / $fps; + $t = ( $s2_x * ($p0_y - $p2_y) - $s2_y * ($p0_x - $p2_x)) / $fpt; + if ($s > 0 && $s < 1 && $t > 0 && $t < 1) { + // Collision detected + return TRUE; + } + return FALSE; + } } diff --git a/lib/geometry/MultiPoint.class.php b/lib/geometry/MultiPoint.class.php index 90b785d5..5b52736e 100644 --- a/lib/geometry/MultiPoint.class.php +++ b/lib/geometry/MultiPoint.class.php @@ -5,5 +5,17 @@ class MultiPoint extends Collection { protected $geom_type = 'MultiPoint'; + + public function numPoints() { + return $this->numGeometries(); + } + + public function isSimple() { + return TRUE; + } + + // Not valid for this geometry type + // -------------------------------- + public function explode() { return NULL; } } diff --git a/lib/geometry/Point.class.php b/lib/geometry/Point.class.php index dcaff1b7..3dac401c 100644 --- a/lib/geometry/Point.class.php +++ b/lib/geometry/Point.class.php @@ -113,6 +113,22 @@ public function dimension() { public function isEmpty() { return FALSE; } + + public function numPoints() { + return 1; + } + + public function getPoints() { + return array($this); + } + + public function equals($geometry) { + return ($this->x() == $geometry->x() && $this->y() == $geometry->y()); + } + + public function isSimple() { + return TRUE; + } // Not valid for this geometry type public function numGeometries() { return NULL; } @@ -121,12 +137,11 @@ public function startPoint() { return NULL; } public function endPoint() { return NULL; } public function isRing() { return NULL; } public function isClosed() { return NULL; } - public function numPoints() { return NULL; } public function pointN($n) { return NULL; } public function exteriorRing() { return NULL; } public function numInteriorRings() { return NULL; } public function interiorRingN($n) { return NULL; } public function pointOnSurface() { return NULL; } - + public function explode() { return NULL; } } diff --git a/lib/geometry/Polygon.class.php b/lib/geometry/Polygon.class.php index 10c369ef..a0d683a6 100644 --- a/lib/geometry/Polygon.class.php +++ b/lib/geometry/Polygon.class.php @@ -94,6 +94,25 @@ public function dimension() { return 2; } + public function isSimple() { + if ($this->geos()) { + return $this->geos()->isSimple(); + } + + $segments = $this->explode(); + + foreach ($segments as $i => $segment) { + foreach ($segments as $j => $check_segment) { + if ($i != $j) { + if ($segment->lineSegmentIntersect($check_segment)) { + return FALSE; + } + } + } + } + return TRUE; + } + // Not valid for this geometry type // -------------------------------- public function length() { return NULL; }