| Total Complexity | 42 |
| Total Lines | 292 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like GeoRSS often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GeoRSS, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 23 | class GeoRSS implements GeoAdapter |
||
| 24 | { |
||
| 25 | |||
| 26 | /** |
||
| 27 | * @var \DOMDocument $xmlObject |
||
| 28 | */ |
||
| 29 | protected $xmlObject; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * @var string Name-space string. eg 'georss:' |
||
| 33 | */ |
||
| 34 | private $nss = ''; |
||
| 35 | |||
| 36 | /** |
||
| 37 | * Read GeoRSS string into geometry objects |
||
| 38 | * |
||
| 39 | * @param string $georss - an XML feed containing geoRSS |
||
| 40 | * @return Geometry|GeometryCollection |
||
| 41 | * @throws \Exception |
||
| 42 | */ |
||
| 43 | public function read(string $georss): Geometry |
||
| 44 | { |
||
| 45 | return $this->geomFromText($georss); |
||
| 46 | } |
||
| 47 | |||
| 48 | /** |
||
| 49 | * Serialize geometries into a GeoRSS string. |
||
| 50 | * |
||
| 51 | * @param Geometry $geometry |
||
| 52 | * @param string $namespace |
||
| 53 | * @return string The georss string representation of the input geometries |
||
| 54 | */ |
||
| 55 | public function write(Geometry $geometry, $namespace = ''): string |
||
| 56 | { |
||
| 57 | $namespace = trim($namespace); |
||
| 58 | if (!empty($namespace)) { |
||
| 59 | $this->nss = $namespace . ':'; |
||
| 60 | } |
||
| 61 | return $this->geometryToGeoRSS($geometry); |
||
| 62 | } |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Creates a new geometry-object from input-string. |
||
| 66 | * |
||
| 67 | * @param string $text |
||
| 68 | * @return Geometry|GeometryCollection |
||
| 69 | * @throws \Exception |
||
| 70 | */ |
||
| 71 | public function geomFromText(string $text): Geometry |
||
| 72 | { |
||
| 73 | // Change to lower-case, strip all CDATA, and de-namespace |
||
| 74 | $text = strtolower($text); |
||
| 75 | $text = preg_replace('/<!\[cdata\[(.*?)\]\]>/s', '', $text); |
||
| 76 | |||
| 77 | // Load into DOMDocument |
||
| 78 | $xmlObject = new \DOMDocument(); |
||
| 79 | |||
| 80 | if ($xmlObject->loadXML($text) === false) { |
||
| 81 | throw new \Exception("Invalid GeoRSS: " . $text); |
||
| 82 | } |
||
| 83 | |||
| 84 | $this->xmlObject = $xmlObject; |
||
| 85 | try { |
||
| 86 | $geom = $this->geomFromXML(); |
||
| 87 | } catch (\Exception $e) { |
||
| 88 | throw new \Exception("Cannot read geometry from GeoRSS: " . $e->getMessage()); |
||
| 89 | } |
||
| 90 | |||
| 91 | return $geom; |
||
| 92 | } |
||
| 93 | |||
| 94 | /** |
||
| 95 | * @return Geometry|GeometryCollection |
||
| 96 | * @throws \Exception |
||
| 97 | */ |
||
| 98 | protected function geomFromXML() |
||
| 99 | { |
||
| 100 | $geometries = array_merge( |
||
| 101 | $this->parsePoints(), |
||
| 102 | $this->parseLines(), |
||
| 103 | $this->parsePolygons(), |
||
| 104 | $this->parseBoxes(), |
||
| 105 | $this->parseCircles() |
||
| 106 | ); |
||
| 107 | |||
| 108 | if (empty($geometries)) { |
||
| 109 | throw new \Exception("Invalid / empty GeoRSS."); |
||
| 110 | } |
||
| 111 | |||
| 112 | return geoPHP::geometryReduce($geometries); |
||
| 113 | } |
||
| 114 | |||
| 115 | /** |
||
| 116 | * @param string $string |
||
| 117 | * @return Point[] |
||
| 118 | */ |
||
| 119 | protected function getPointsFromCoordinates(string $string): array |
||
| 120 | { |
||
| 121 | $coordinates = []; |
||
| 122 | $latitudeAndLongitude = explode(' ', $string); |
||
| 123 | $lat = 0; |
||
| 124 | foreach ($latitudeAndLongitude as $key => $item) { |
||
| 125 | if (!($key % 2)) { |
||
| 126 | // It's a latitude |
||
| 127 | $lat = is_numeric($item) ? $item : null; |
||
| 128 | } else { |
||
| 129 | // It's a longitude |
||
| 130 | $lon = is_numeric($item) ? $item : null; |
||
| 131 | $coordinates[] = new Point($lon, $lat); |
||
| 132 | } |
||
| 133 | } |
||
| 134 | return $coordinates; |
||
| 135 | } |
||
| 136 | |||
| 137 | /** |
||
| 138 | * @return Point[] |
||
| 139 | */ |
||
| 140 | protected function parsePoints(): array |
||
| 141 | { |
||
| 142 | $points = []; |
||
| 143 | $pointElements = $this->xmlObject->getElementsByTagName('point'); |
||
| 144 | foreach ($pointElements as $pt) { |
||
| 145 | $pointArray = $this->getPointsFromCoordinates(trim($pt->firstChild->nodeValue)); |
||
| 146 | $points[] = !empty($pointArray) ? $pointArray[0] : new Point(); |
||
| 147 | } |
||
| 148 | return $points; |
||
| 149 | } |
||
| 150 | |||
| 151 | /** |
||
| 152 | * @return LineString[] |
||
| 153 | */ |
||
| 154 | protected function parseLines(): array |
||
| 155 | { |
||
| 156 | $lines = []; |
||
| 157 | $lineElements = $this->xmlObject->getElementsByTagName('line'); |
||
| 158 | foreach ($lineElements as $line) { |
||
| 159 | $components = $this->getPointsFromCoordinates(trim($line->firstChild->nodeValue)); |
||
| 160 | $lines[] = new LineString($components); |
||
| 161 | } |
||
| 162 | return $lines; |
||
| 163 | } |
||
| 164 | |||
| 165 | /** |
||
| 166 | * @return Polygon[] |
||
| 167 | */ |
||
| 168 | protected function parsePolygons(): array |
||
| 169 | { |
||
| 170 | $polygons = []; |
||
| 171 | $polygonElements = $this->xmlObject->getElementsByTagName('polygon'); |
||
| 172 | foreach ($polygonElements as $polygon) { |
||
| 173 | /** @noinspection PhpUndefinedMethodInspection */ |
||
| 174 | if ($polygon->hasChildNodes()) { |
||
| 175 | $points = $this->getPointsFromCoordinates(trim($polygon->firstChild->nodeValue)); |
||
| 176 | $exteriorRing = new LineString($points); |
||
| 177 | $polygons[] = new Polygon([$exteriorRing]); |
||
| 178 | } else { |
||
| 179 | // It's an EMPTY polygon |
||
| 180 | $polygons[] = new Polygon(); |
||
| 181 | } |
||
| 182 | } |
||
| 183 | return $polygons; |
||
| 184 | } |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Boxes are rendered into polygons. |
||
| 188 | * |
||
| 189 | * @return Polygon[] |
||
| 190 | */ |
||
| 191 | protected function parseBoxes(): array |
||
| 192 | { |
||
| 193 | $polygons = []; |
||
| 194 | $boxElements = $this->xmlObject->getElementsByTagName('box'); |
||
| 195 | foreach ($boxElements as $box) { |
||
| 196 | $parts = explode(' ', trim($box->firstChild->nodeValue)); |
||
| 197 | $components = [ |
||
| 198 | new Point($parts[3], $parts[2]), |
||
| 199 | new Point($parts[3], $parts[0]), |
||
| 200 | new Point($parts[1], $parts[0]), |
||
| 201 | new Point($parts[1], $parts[2]), |
||
| 202 | new Point($parts[3], $parts[2]), |
||
| 203 | ]; |
||
| 204 | $exteriorRing = new LineString($components); |
||
| 205 | $polygons[] = new Polygon([$exteriorRing]); |
||
| 206 | } |
||
| 207 | return $polygons; |
||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * Circles are rendered into points |
||
| 212 | * @todo Add good support once we have circular-string geometry support |
||
| 213 | * @return Point[] |
||
| 214 | */ |
||
| 215 | protected function parseCircles(): array |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * @param Geometry $geometry |
||
| 228 | * @return string |
||
| 229 | */ |
||
| 230 | protected function geometryToGeoRSS(Geometry $geometry): string |
||
| 231 | { |
||
| 232 | $type = $geometry->geometryType(); |
||
| 233 | switch ($type) { |
||
| 234 | case Geometry::POINT: |
||
| 235 | /** @var Point $geometry */ |
||
| 236 | return $this->pointToGeoRSS($geometry); |
||
| 237 | case Geometry::LINESTRING: |
||
| 238 | /** @noinspection PhpParamsInspection */ |
||
| 239 | /** @var LineString $geometry */ |
||
| 240 | return $this->linestringToGeoRSS($geometry); |
||
| 241 | case Geometry::POLYGON: |
||
| 242 | /** @noinspection PhpParamsInspection */ |
||
| 243 | /** @var Polygon $geometry */ |
||
| 244 | return $this->PolygonToGeoRSS($geometry); |
||
| 245 | case Geometry::MULTI_POINT: |
||
| 246 | case Geometry::MULTI_LINESTRING: |
||
| 247 | case Geometry::MULTI_POLYGON: |
||
| 248 | case Geometry::GEOMETRY_COLLECTION: |
||
| 249 | /** @noinspection PhpParamsInspection */ |
||
| 250 | /** @var GeometryCollection $geometry */ |
||
| 251 | return $this->collectionToGeoRSS($geometry); |
||
| 252 | } |
||
| 253 | return ''; |
||
| 254 | } |
||
| 255 | |||
| 256 | /** |
||
| 257 | * @param Point $geometry |
||
| 258 | * @return string |
||
| 259 | */ |
||
| 260 | private function pointToGeoRSS(Point $geometry): string |
||
| 261 | { |
||
| 262 | return '<' . $this->nss . 'point>' . $geometry->getY() . ' ' . $geometry->getX() . '</' . $this->nss . 'point>'; |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * @param LineString $geometry |
||
| 267 | * @return string |
||
| 268 | */ |
||
| 269 | private function linestringToGeoRSS(LineString $geometry): string |
||
| 270 | { |
||
| 271 | $output = '<' . $this->nss . 'line>'; |
||
| 272 | foreach ($geometry->getComponents() as $k => $point) { |
||
| 273 | $output .= $point->getY() . ' ' . $point->getX(); |
||
| 274 | if ($k < ($geometry->numGeometries() - 1)) { |
||
| 275 | $output .= ' '; |
||
| 276 | } |
||
| 277 | } |
||
| 278 | $output .= '</' . $this->nss . 'line>'; |
||
| 279 | return $output; |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * @param Polygon $geometry |
||
| 284 | * @return string |
||
| 285 | */ |
||
| 286 | private function polygonToGeoRSS(Polygon $geometry): string |
||
| 287 | { |
||
| 288 | $output = '<' . $this->nss . 'polygon>'; |
||
| 289 | $exteriorRing = $geometry->exteriorRing(); |
||
| 290 | foreach ($exteriorRing->getComponents() as $k => $point) { |
||
| 291 | $output .= $point->getY() . ' ' . $point->getX(); |
||
| 292 | if ($k < ($exteriorRing->numGeometries() - 1)) { |
||
| 293 | $output .= ' '; |
||
| 294 | } |
||
| 295 | } |
||
| 296 | $output .= '</' . $this->nss . 'polygon>'; |
||
| 297 | return $output; |
||
| 298 | } |
||
| 299 | |||
| 300 | /** |
||
| 301 | * @param Collection $geometry |
||
| 302 | * @return string |
||
| 303 | */ |
||
| 304 | public function collectionToGeoRSS(Collection $geometry): string |
||
| 315 | } |
||
| 316 | } |
||
| 317 |