1<?php
2/*
3 * Copyright (c) Patrick Hayes
4 *
5 * This code is open-source and licenced under the Modified BSD License.
6 * For the full copyright and license information, please view the LICENSE
7 * file that was distributed with this source code.
8 */
9
10/**
11 * PHP Geometry/GeoRSS encoder/decoder
12 */
13class GeoRSS extends GeoAdapter
14{
15  private $namespace = FALSE;
16  private $nss = ''; // Name-space string. eg 'georss:'
17
18  /**
19   * Read GeoRSS string into geometry objects
20   *
21   * @param string $georss - an XML feed containing geoRSS
22   *
23   * @return Geometry|GeometryCollection
24   */
25  public function read($gpx) {
26    return $this->geomFromText($gpx);
27  }
28
29  /**
30   * Serialize geometries into a GeoRSS string.
31   *
32   * @param Geometry $geometry
33   *
34   * @return string The georss string representation of the input geometries
35   */
36  public function write(Geometry $geometry, $namespace = FALSE) {
37    if ($namespace) {
38      $this->namespace = $namespace;
39      $this->nss = $namespace.':';
40    }
41    return $this->geometryToGeoRSS($geometry);
42  }
43
44  public function geomFromText($text) {
45    // Change to lower-case, strip all CDATA, and de-namespace
46    $text = strtolower($text);
47    $text = preg_replace('/<!\[cdata\[(.*?)\]\]>/s','',$text);
48
49    // Load into DOMDOcument
50    $xmlobj = new DOMDocument();
51    @$xmlobj->loadXML($text);
52    if ($xmlobj === false) {
53      throw new Exception("Invalid GeoRSS: ". $text);
54    }
55
56    $this->xmlobj = $xmlobj;
57    try {
58      $geom = $this->geomFromXML();
59    } catch(InvalidText $e) {
60        throw new Exception("Cannot Read Geometry From GeoRSS: ". $text);
61    } catch(Exception $e) {
62        throw $e;
63    }
64
65    return $geom;
66  }
67
68  protected function geomFromXML() {
69    $geometries = array();
70    $geometries = array_merge($geometries, $this->parsePoints());
71    $geometries = array_merge($geometries, $this->parseLines());
72    $geometries = array_merge($geometries, $this->parsePolygons());
73    $geometries = array_merge($geometries, $this->parseBoxes());
74    $geometries = array_merge($geometries, $this->parseCircles());
75
76    if (empty($geometries)) {
77      throw new Exception("Invalid / Empty GeoRSS");
78    }
79
80    return geoPHP::geometryReduce($geometries);
81  }
82
83  protected function getPointsFromCoords($string) {
84    $coords = array();
85
86    if (empty($string)) {
87      return $coords;
88    }
89
90    $latlon = explode(' ',$string);
91    foreach ($latlon as $key => $item) {
92      if (!($key % 2)) {
93        // It's a latitude
94        $lat = $item;
95      }
96      else {
97        // It's a longitude
98        $lon = $item;
99        $coords[] = new Point($lon, $lat);
100      }
101    }
102    return $coords;
103  }
104
105  protected function parsePoints() {
106    $points = array();
107    $pt_elements = $this->xmlobj->getElementsByTagName('point');
108    foreach ($pt_elements as $pt) {
109      if ($pt->hasChildNodes()) {
110        $point_array = $this->getPointsFromCoords(trim($pt->firstChild->nodeValue));
111      }
112      if (!empty($point_array)) {
113        $points[] = $point_array[0];
114      }
115      else {
116        $points[] = new Point();
117      }
118    }
119    return $points;
120  }
121
122  protected function parseLines() {
123    $lines = array();
124    $line_elements = $this->xmlobj->getElementsByTagName('line');
125    foreach ($line_elements as $line) {
126      $components = $this->getPointsFromCoords(trim($line->firstChild->nodeValue));
127      $lines[] = new LineString($components);
128    }
129    return $lines;
130  }
131
132  protected function parsePolygons() {
133    $polygons = array();
134    $poly_elements = $this->xmlobj->getElementsByTagName('polygon');
135    foreach ($poly_elements as $poly) {
136      if ($poly->hasChildNodes()) {
137        $points = $this->getPointsFromCoords(trim($poly->firstChild->nodeValue));
138        $exterior_ring = new LineString($points);
139        $polygons[] = new Polygon(array($exterior_ring));
140      }
141      else {
142        // It's an EMPTY polygon
143        $polygons[] = new Polygon();
144      }
145    }
146    return $polygons;
147  }
148
149  // Boxes are rendered into polygons
150  protected function parseBoxes() {
151    $polygons = array();
152    $box_elements = $this->xmlobj->getElementsByTagName('box');
153    foreach ($box_elements as $box) {
154      $parts = explode(' ',trim($box->firstChild->nodeValue));
155      $components = array(
156        new Point($parts[3], $parts[2]),
157        new Point($parts[3], $parts[0]),
158        new Point($parts[1], $parts[0]),
159        new Point($parts[1], $parts[2]),
160        new Point($parts[3], $parts[2]),
161      );
162      $exterior_ring = new LineString($components);
163      $polygons[] = new Polygon(array($exterior_ring));
164    }
165    return $polygons;
166  }
167
168  // Circles are rendered into points
169  // @@TODO: Add good support once we have circular-string geometry support
170  protected function parseCircles() {
171    $points = array();
172    $circle_elements = $this->xmlobj->getElementsByTagName('circle');
173    foreach ($circle_elements as $circle) {
174      $parts = explode(' ',trim($circle->firstChild->nodeValue));
175      $points[] = new Point($parts[1], $parts[0]);
176    }
177    return $points;
178  }
179
180  protected function geometryToGeoRSS($geom) {
181    $type = strtolower($geom->getGeomType());
182    switch ($type) {
183      case 'point':
184        return $this->pointToGeoRSS($geom);
185        break;
186      case 'linestring':
187        return $this->linestringToGeoRSS($geom);
188        break;
189      case 'polygon':
190        return $this->PolygonToGeoRSS($geom);
191        break;
192      case 'multipoint':
193      case 'multilinestring':
194      case 'multipolygon':
195      case 'geometrycollection':
196        return $this->collectionToGeoRSS($geom);
197        break;
198    }
199    return $output;
200  }
201
202  private function pointToGeoRSS($geom) {
203    $out = '<'.$this->nss.'point>';
204    if (!$geom->isEmpty()) {
205      $out .= $geom->getY().' '.$geom->getX();
206    }
207    $out .= '</'.$this->nss.'point>';
208    return $out;
209  }
210
211  private function linestringToGeoRSS($geom) {
212    $output = '<'.$this->nss.'line>';
213    foreach ($geom->getComponents() as $k => $point) {
214      $output .= $point->getY().' '.$point->getX();
215      if ($k < ($geom->numGeometries() -1)) $output .= ' ';
216    }
217    $output .= '</'.$this->nss.'line>';
218    return $output;
219  }
220
221  private function polygonToGeoRSS($geom) {
222    $output = '<'.$this->nss.'polygon>';
223    $exterior_ring = $geom->exteriorRing();
224    foreach ($exterior_ring->getComponents() as $k => $point) {
225      $output .= $point->getY().' '.$point->getX();
226      if ($k < ($exterior_ring->numGeometries() -1)) $output .= ' ';
227    }
228    $output .= '</'.$this->nss.'polygon>';
229    return $output;
230  }
231
232  public function collectionToGeoRSS($geom) {
233    $georss = '<'.$this->nss.'where>';
234    $components = $geom->getComponents();
235    foreach ($geom->getComponents() as $comp) {
236      $georss .= $this->geometryToGeoRSS($comp);
237    }
238
239    $georss .= '</'.$this->nss.'where>';
240
241    return $georss;
242  }
243
244}
245