Total Complexity | 72 |
Total Lines | 379 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like KML 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 KML, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class KML implements GeoAdapter |
||
27 | { |
||
28 | |||
29 | /** |
||
30 | * @var \DOMDocument |
||
31 | */ |
||
32 | protected $xmlObject; |
||
33 | |||
34 | /** |
||
35 | * @var string Name-space string. eg 'georss:' |
||
36 | */ |
||
37 | private $nss = ''; |
||
38 | |||
39 | /** |
||
40 | * Read KML string into geometry objects |
||
41 | * |
||
42 | * @param string $kml A KML string |
||
43 | * @return Geometry|GeometryCollection |
||
44 | */ |
||
45 | public function read(string $kml): Geometry |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * @param string $text |
||
52 | * @return Geometry|GeometryCollection |
||
53 | * @throws \Exception |
||
54 | */ |
||
55 | public function geomFromText(string $text): Geometry |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * @return Geometry|GeometryCollection |
||
79 | */ |
||
80 | protected function geomFromXML(): Geometry |
||
81 | { |
||
82 | $geometries = []; |
||
83 | $placemarkElements = $this->xmlObject->getElementsByTagName('placemark'); |
||
84 | |||
85 | if ($placemarkElements->length) { |
||
86 | foreach ($placemarkElements as $placemark) { |
||
87 | $data = []; |
||
88 | /** @var Geometry|null $geometry */ |
||
89 | $geometry = null; |
||
90 | foreach ($placemark->childNodes as $child) { |
||
91 | // Node names are all the same, except for MultiGeometry, which maps to GeometryCollection |
||
92 | $nodeName = $child->nodeName === 'multigeometry' ? 'geometrycollection' : $child->nodeName; |
||
93 | if (array_key_exists($nodeName, geoPHP::getGeometryList())) { |
||
94 | $function = 'parse' . geoPHP::getGeometryList()[$nodeName]; |
||
95 | $geometry = $this->$function($child); |
||
96 | } elseif ($child->nodeType === 1) { |
||
97 | $data[$child->nodeName] = $child->nodeValue; |
||
98 | } |
||
99 | } |
||
100 | |||
101 | if (isset($geometry)) { |
||
102 | if (!empty($data)) { |
||
103 | $geometry->setData($data); |
||
104 | } |
||
105 | $geometries[] = $geometry; |
||
106 | } |
||
107 | } |
||
108 | |||
109 | return new GeometryCollection($geometries); |
||
110 | } |
||
111 | |||
112 | // The document does not have a placemark, try to create a valid geometry from the root element |
||
113 | $nodeName = $this->xmlObject->documentElement->nodeName === 'multigeometry' ? |
||
114 | 'geometrycollection' : $this->xmlObject->documentElement->nodeName; |
||
115 | |||
116 | if (array_key_exists($nodeName, geoPHP::getGeometryList())) { |
||
117 | $function = 'parse' . geoPHP::getGeometryList()[$nodeName]; |
||
118 | return $this->$function($this->xmlObject->documentElement); |
||
119 | } |
||
120 | |||
121 | return new GeometryCollection(); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * @param \DOMNode $xml |
||
126 | * @param string $nodeName |
||
127 | * @return \DOMNode[] |
||
128 | */ |
||
129 | protected function childElements(\DOMNode $xml, string $nodeName = ''): array |
||
130 | { |
||
131 | $children = []; |
||
132 | foreach ($xml->childNodes as $child) { |
||
133 | if ($child->nodeName == $nodeName) { |
||
134 | $children[] = $child; |
||
135 | } |
||
136 | } |
||
137 | |||
138 | return $children; |
||
139 | } |
||
140 | |||
141 | /** |
||
142 | * @param \DOMNode $xml |
||
143 | * @return Point |
||
144 | */ |
||
145 | protected function parsePoint(\DOMNode $xml): Point |
||
146 | { |
||
147 | $coordinates = $this->extractCoordinates($xml); |
||
148 | |||
149 | if (empty($coordinates)) { |
||
150 | return new Point(); |
||
151 | } |
||
152 | |||
153 | return new Point( |
||
154 | $coordinates[0][0], |
||
155 | $coordinates[0][1], |
||
156 | (isset($coordinates[0][2]) ? $coordinates[0][2] : null), |
||
157 | (isset($coordinates[0][3]) ? $coordinates[0][3] : null) |
||
158 | ); |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * @param \DOMNode $xml |
||
163 | * @return LineString |
||
164 | */ |
||
165 | protected function parseLineString(\DOMNode $xml): LineString |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param \DOMNode $xml |
||
195 | * @return Polygon |
||
196 | * @throws \Exception |
||
197 | */ |
||
198 | protected function parsePolygon(\DOMNode $xml): Polygon |
||
199 | { |
||
200 | $components = []; |
||
201 | |||
202 | /** @noinspection SpellCheckingInspection */ |
||
203 | $outerBoundaryIs = $this->childElements($xml, 'outerboundaryis'); |
||
204 | if (empty($outerBoundaryIs)) { |
||
205 | return new Polygon(); |
||
206 | } |
||
207 | $outerBoundaryElement = $outerBoundaryIs[0]; |
||
208 | /** @noinspection SpellCheckingInspection */ |
||
209 | $outerRingElement = @$this->childElements($outerBoundaryElement, 'linearring')[0]; |
||
210 | $components[] = $this->parseLineString($outerRingElement); |
||
211 | |||
212 | if (count($components) != 1) { |
||
213 | throw new \Exception("Invalid KML"); |
||
214 | } |
||
215 | |||
216 | /** @noinspection SpellCheckingInspection */ |
||
217 | $innerBoundaryElementIs = $this->childElements($xml, 'innerboundaryis'); |
||
218 | foreach ($innerBoundaryElementIs as $innerBoundaryElement) { |
||
219 | /** @noinspection SpellCheckingInspection */ |
||
220 | foreach ($this->childElements($innerBoundaryElement, 'linearring') as $innerRingElement) { |
||
221 | $components[] = $this->parseLineString($innerRingElement); |
||
222 | } |
||
223 | } |
||
224 | |||
225 | return new Polygon($components); |
||
226 | } |
||
227 | |||
228 | /** |
||
229 | * @param \DOMNode $xml |
||
230 | * @return GeometryCollection |
||
231 | */ |
||
232 | protected function parseGeometryCollection(\DOMNode $xml): GeometryCollection |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * @param \DOMNode $xml |
||
255 | * @return array<array> |
||
256 | */ |
||
257 | protected function extractCoordinates(\DOMNode $xml): array |
||
258 | { |
||
259 | $coordinateElements = $this->childElements($xml, 'coordinates'); |
||
260 | $coordinates = []; |
||
261 | |||
262 | if (!empty($coordinateElements)) { |
||
263 | $coordinateSets = explode(' ', preg_replace('/[\r\n\s\t]+/', ' ', $coordinateElements[0]->nodeValue)); |
||
264 | |||
265 | foreach ($coordinateSets as $setString) { |
||
266 | $setString = trim($setString); |
||
267 | if ($setString) { |
||
268 | $setArray = explode(',', $setString); |
||
269 | if (count($setArray) >= 2) { |
||
270 | $coordinates[] = $setArray; |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | } |
||
275 | |||
276 | return $coordinates; |
||
277 | } |
||
278 | |||
279 | |||
280 | /** |
||
281 | * Serialize geometries into a KML string. |
||
282 | * |
||
283 | * @param Geometry $geometry |
||
284 | * @param string $namespace |
||
285 | * @return string The KML string representation of the input geometries |
||
286 | */ |
||
287 | public function write(Geometry $geometry, string $namespace = ''): string |
||
288 | { |
||
289 | $namespace = trim($namespace); |
||
290 | if (!empty($namespace)) { |
||
291 | $this->nss = $namespace . ':'; |
||
292 | } |
||
293 | |||
294 | return $this->geometryToKML($geometry); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param Geometry $geometry |
||
299 | * @return string |
||
300 | */ |
||
301 | private function geometryToKML(Geometry $geometry): string |
||
302 | { |
||
303 | $type = $geometry->geometryType(); |
||
304 | switch ($type) { |
||
305 | case Geometry::POINT: |
||
306 | /** @var Point $geometry */ |
||
307 | return $this->pointToKML($geometry); |
||
308 | case Geometry::LINESTRING: |
||
309 | /** @var LineString $geometry */ |
||
310 | return $this->linestringToKML($geometry); |
||
311 | case Geometry::POLYGON: |
||
312 | /** @var Polygon $geometry */ |
||
313 | return $this->polygonToKML($geometry); |
||
314 | case Geometry::MULTI_POINT: |
||
315 | case Geometry::MULTI_LINESTRING: |
||
316 | case Geometry::MULTI_POLYGON: |
||
317 | case Geometry::GEOMETRY_COLLECTION: |
||
318 | /** @var Collection $geometry */ |
||
319 | return $this->collectionToKML($geometry); |
||
320 | } |
||
321 | return ''; |
||
322 | } |
||
323 | |||
324 | /** |
||
325 | * @param Point $geometry |
||
326 | * @return string |
||
327 | */ |
||
328 | private function pointToKML(Geometry $geometry): string |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * @param LineString $geometry |
||
341 | * @param string $type |
||
342 | * @return string |
||
343 | */ |
||
344 | private function linestringToKML(Geometry $geometry, $type = null): string |
||
345 | { |
||
346 | if (!isset($type)) { |
||
347 | $type = $geometry->geometryType(); |
||
348 | } |
||
349 | |||
350 | $str = '<' . $this->nss . $type . ">\n"; |
||
351 | |||
352 | if (!$geometry->isEmpty()) { |
||
353 | $str .= '<' . $this->nss . 'coordinates>'; |
||
354 | $i = 0; |
||
355 | foreach ($geometry->getComponents() as $comp) { |
||
356 | if ($i != 0) { |
||
357 | $str .= ' '; |
||
358 | } |
||
359 | $str .= $comp->getX() . ',' . $comp->getY(); |
||
360 | $i++; |
||
361 | } |
||
362 | |||
363 | $str .= '</' . $this->nss . 'coordinates>'; |
||
364 | } |
||
365 | |||
366 | $str .= '</' . $this->nss . $type . ">\n"; |
||
367 | |||
368 | return $str; |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * @param Polygon $geometry |
||
373 | * @return string |
||
374 | */ |
||
375 | public function polygonToKML(Geometry $geometry): string |
||
376 | { |
||
377 | /** @var LineString[] $components */ |
||
378 | $components = $geometry->getComponents(); |
||
379 | $str = ''; |
||
380 | if (!empty($components)) { |
||
381 | /** @noinspection PhpParamsInspection */ |
||
382 | $str = '<' . $this->nss . 'outerBoundaryIs>' . $this->linestringToKML($components[0], 'LinearRing') . '</' . $this->nss . 'outerBoundaryIs>'; |
||
383 | foreach (array_slice($components, 1) as $comp) { |
||
384 | $str .= '<' . $this->nss . 'innerBoundaryIs>' . $this->linestringToKML($comp) . '</' . $this->nss . 'innerBoundaryIs>'; |
||
385 | } |
||
386 | } |
||
387 | |||
388 | return '<' . $this->nss . "Polygon>\n" . $str . '</' . $this->nss . "Polygon>\n"; |
||
389 | } |
||
390 | |||
391 | /** |
||
392 | * @param Collection $geometry |
||
393 | * @return string |
||
394 | */ |
||
395 | public function collectionToKML(Geometry $geometry): string |
||
405 | } |
||
406 | } |
||
407 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.