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