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 |