Passed
Pull Request — master (#4)
by Mark
01:50
created

KML   F

Complexity

Total Complexity 73

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 157
dl 0
loc 328
rs 2.56
c 0
b 0
f 0
wmc 73

15 Methods

Rating   Name   Duplication   Size   Complexity  
A geomFromText() 0 22 3
B parseLineString() 0 19 11
A parseGeometryCollection() 0 17 5
A pointToKML() 0 9 3
B geomFromXML() 0 37 11
A extractCoordinates() 0 18 5
A parsePolygon() 0 28 5
A collectionToKML() 0 10 2
A write() 0 7 2
A parsePoint() 0 11 4
A polygonToKML() 0 13 3
A childElements() 0 11 5
B geometryToKML() 0 21 8
A linestringToKML() 0 25 5
A read() 0 3 1

How to fix   Complexity   

Complex Class

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
2
3
namespace geoPHP\Adapter;
4
5
use geoPHP\Geometry\Collection;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use geoPHP\geoPHP;
7
use geoPHP\Geometry\Geometry;
8
use geoPHP\Geometry\GeometryCollection;
9
use geoPHP\Geometry\Point;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Point was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use geoPHP\Geometry\LineString;
11
use geoPHP\Geometry\Polygon;
12
13
/*
14
 * Copyright (c) Patrick Hayes
15
 * Copyright (c) 2010-2011, Arnaud Renevier
16
 *
17
 * This code is open-source and licenced under the Modified BSD License.
18
 * For the full copyright and license information, please view the LICENSE
19
 * file that was distributed with this source code.
20
 */
21
22
/**
23
 * PHP Geometry/KML encoder/decoder
24
 *
25
 * Mainly inspired/adapted from OpenLayers( http://www.openlayers.org )
26
 *   Openlayers/format/WKT.js
27
 *
28
 * @package    sfMapFishPlugin
29
 * @subpackage GeoJSON
30
 * @author     Camptocamp <[email protected]>
31
 */
32
class KML implements GeoAdapter
33
{
34
35
    /**
36
     * @var \DOMDocument
37
     */
38
    protected $xmlObject;
39
40
    private $namespace = false;
41
42
    private $nss = ''; // Name-space string. eg 'georss:'
43
44
    /**
45
     * Read KML string into geometry objects
46
     *
47
     * @param string $kml A KML string
48
     *
49
     * @return Geometry|GeometryCollection
50
     */
51
    public function read($kml)
52
    {
53
        return $this->geomFromText($kml);
54
    }
55
56
    public function geomFromText($text)
57
    {
58
59
        // Change to lower-case and strip all CDATA
60
        $text = mb_strtolower($text, mb_detect_encoding($text));
61
        $text = preg_replace('/<!\[cdata\[(.*?)\]\]>/s', '', $text);
62
63
        // Load into DOMDocument
64
        $xmlObject = new \DOMDocument();
65
        @$xmlObject->loadXML($text);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for loadXML(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

65
        /** @scrutinizer ignore-unhandled */ @$xmlObject->loadXML($text);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
66
        if ($xmlObject === false) {
0 ignored issues
show
introduced by
The condition $xmlObject === false is always false.
Loading history...
67
            throw new \Exception("Invalid KML: " . $text);
68
        }
69
70
        $this->xmlObject = $xmlObject;
71
        try {
72
            $geom = $this->geomFromXML();
73
        } catch (\Exception $e) {
74
            throw new \Exception("Cannot Read Geometry From KML. " . $e->getMessage());
75
        }
76
77
        return $geom;
78
    }
79
80
    protected function geomFromXML()
81
    {
82
        $geometries = [];
83
        $placemarkElements = $this->xmlObject->getElementsByTagName('placemark');
84
        if ($placemarkElements->length) {
85
            foreach ($placemarkElements as $placemark) {
86
                $data = [];
87
                /** @var Geometry|null $geometry */
88
                $geometry = null;
89
                foreach ($placemark->childNodes as $child) {
90
                    // Node names are all the same, except for MultiGeometry, which maps to GeometryCollection
91
                    $nodeName = $child->nodeName == 'multigeometry' ? 'geometrycollection' : $child->nodeName;
92
                    if (array_key_exists($nodeName, geoPHP::getGeometryList())) {
93
                        $function = 'parse' . geoPHP::getGeometryList()[$nodeName];
94
                        $geometry = $this->$function($child);
95
                    } elseif ($child->nodeType === 1) {
96
                        $data[$child->nodeName] = $child->nodeValue;
97
                    }
98
                }
99
                if ($geometry) {
100
                    if (count($data)) {
101
                        $geometry->setData($data);
102
                    }
103
                    $geometries[] = $geometry;
104
                }
105
            }
106
            return new GeometryCollection($geometries);
107
        } else {
108
            // The document does not have a placemark, try to create a valid geometry from the root element
109
            $nodeName = $this->xmlObject->documentElement->nodeName == 'multigeometry' ? 'geometrycollection' : $this->xmlObject->documentElement->nodeName;
110
            if (array_key_exists($nodeName, geoPHP::getGeometryList())) {
111
                $function = 'parse' . geoPHP::getGeometryList()[$nodeName];
112
                return $this->$function($this->xmlObject->documentElement);
113
            }
114
        }
115
        //return geoPHP::geometryReduce($geometries);
116
        return new GeometryCollection();
117
    }
118
119
    protected function childElements($xml, $nodeName = '')
120
    {
121
        $children = [];
122
        if ($xml && $xml->childNodes) {
123
            foreach ($xml->childNodes as $child) {
124
                if ($child->nodeName == $nodeName) {
125
                    $children[] = $child;
126
                }
127
            }
128
        }
129
        return $children;
130
    }
131
132
    protected function parsePoint($xml)
133
    {
134
        $coordinates = $this->extractCoordinates($xml);
135
        if (empty($coordinates)) {
136
            return new Point();
137
        }
138
        return new Point(
139
            $coordinates[0][0],
140
            $coordinates[0][1],
141
            (isset($coordinates[0][2]) ? $coordinates[0][2] : null),
142
            (isset($coordinates[0][3]) ? $coordinates[0][3] : null)
143
        );
144
    }
145
146
    protected function parseLineString($xml)
147
    {
148
        $coordinates = $this->extractCoordinates($xml);
149
        $pointArray = [];
150
        $hasZ = false;
151
        $hasM = false;
152
        foreach ($coordinates as $set) {
153
            $hasZ = $hasZ || (isset($set[2]) && $set[2]);
154
            $hasM = $hasM || (isset($set[3]) && $set[3]);
155
        }
156
        foreach ($coordinates as $set) {
157
            $pointArray[] = new Point(
158
                $set[0],
159
                $set[1],
160
                ($hasZ ? (isset($set[2]) ? $set[2] : 0) : null),
161
                ($hasM ? (isset($set[3]) ? $set[3] : 0) : null)
162
            );
163
        }
164
        return new LineString($pointArray);
165
    }
166
167
    protected function parsePolygon($xml)
168
    {
169
        $components = [];
170
171
        /** @noinspection SpellCheckingInspection */
172
        $outerBoundaryIs = $this->childElements($xml, 'outerboundaryis');
173
        if (!$outerBoundaryIs) {
174
            return new Polygon();
175
        }
176
        $outerBoundaryElement = $outerBoundaryIs[0];
177
        /** @noinspection SpellCheckingInspection */
178
        $outerRingElement = @$this->childElements($outerBoundaryElement, 'linearring')[0];
179
        $components[] = $this->parseLineString($outerRingElement);
180
181
        if (count($components) != 1) {
182
            throw new \Exception("Invalid KML");
183
        }
184
185
        /** @noinspection SpellCheckingInspection */
186
        $innerBoundaryElementIs = $this->childElements($xml, 'innerboundaryis');
187
        foreach ($innerBoundaryElementIs as $innerBoundaryElement) {
188
            /** @noinspection SpellCheckingInspection */
189
            foreach ($this->childElements($innerBoundaryElement, 'linearring') as $innerRingElement) {
190
                $components[] = $this->parseLineString($innerRingElement);
191
            }
192
        }
193
194
        return new Polygon($components);
195
    }
196
197
    protected function parseGeometryCollection($xml)
198
    {
199
        $components = [];
200
        $geometryTypes = geoPHP::getGeometryList();
201
        foreach ($xml->childNodes as $child) {
202
            /** @noinspection SpellCheckingInspection */
203
            $nodeName = ($child->nodeName == 'linearring')
204
                    ? 'linestring'
205
                    : ($child->nodeName == 'multigeometry'
206
                            ? 'geometrycollection'
207
                            : $child->nodeName);
208
            if (array_key_exists($nodeName, $geometryTypes)) {
209
                $function = 'parse' . $geometryTypes[$nodeName];
210
                $components[] = $this->$function($child);
211
            }
212
        }
213
        return new GeometryCollection($components);
214
    }
215
216
    protected function extractCoordinates($xml)
217
    {
218
        $coordinateElements = $this->childElements($xml, 'coordinates');
219
        $coordinates = [];
220
        if (!empty($coordinateElements)) {
221
            $coordinateSets = explode(' ', preg_replace('/[\r\n\s\t]+/', ' ', $coordinateElements[0]->nodeValue));
222
223
            foreach ($coordinateSets as $setString) {
224
                $setString = trim($setString);
225
                if ($setString) {
226
                    $setArray = explode(',', $setString);
227
                    if (count($setArray) >= 2) {
228
                        $coordinates[] = $setArray;
229
                    }
230
                }
231
            }
232
        }
233
        return $coordinates;
234
    }
235
236
237
    /**
238
     * Serialize geometries into a KML string.
239
     *
240
     * @param Geometry $geometry
241
     * @param bool $namespace
242
     * @return string The KML string representation of the input geometries
243
     */
244
    public function write(Geometry $geometry, $namespace = false)
245
    {
246
        if ($namespace) {
247
            $this->namespace = $namespace;
248
            $this->nss = $namespace . ':';
0 ignored issues
show
Bug introduced by
Are you sure $namespace of type true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

248
            $this->nss = /** @scrutinizer ignore-type */ $namespace . ':';
Loading history...
249
        }
250
        return $this->geometryToKML($geometry);
251
    }
252
253
    /**
254
     * @param Geometry $geometry
255
     * @return string
256
     */
257
    private function geometryToKML($geometry)
258
    {
259
        $type = $geometry->geometryType();
260
        switch ($type) {
261
            case Geometry::POINT:
262
                /** @var Point $geometry */
263
                return $this->pointToKML($geometry);
264
            case Geometry::LINE_STRING:
265
                /** @var LineString $geometry */
266
                return $this->linestringToKML($geometry);
267
            case Geometry::POLYGON:
268
                /** @var Polygon $geometry */
269
                return $this->polygonToKML($geometry);
270
            case Geometry::MULTI_POINT:
271
            case Geometry::MULTI_LINE_STRING:
272
            case Geometry::MULTI_POLYGON:
273
            case Geometry::GEOMETRY_COLLECTION:
274
            /** @var Collection $geometry */
275
                return $this->collectionToKML($geometry);
276
        }
277
        return '';
278
    }
279
280
    /**
281
     * @param Point $geometry
282
     * @return string
283
     */
284
    private function pointToKML($geometry)
285
    {
286
        $str = '<' . $this->nss . "Point>\n<" . $this->nss . 'coordinates>';
287
        if ($geometry->isEmpty()) {
288
            $str .= "0,0";
289
        } else {
290
            $str .= $geometry->x() . ',' . $geometry->y() . ($geometry->hasZ() ? ',' . $geometry->z() : '');
291
        }
292
        return $str . '</' . $this->nss . 'coordinates></' . $this->nss . "Point>\n";
293
    }
294
295
    /**
296
     * @param LineString $geometry
297
     * @param string|boolean $type
298
     * @return string
299
     */
300
    private function linestringToKML($geometry, $type = false)
301
    {
302
        if (!$type) {
303
            $type = $geometry->geometryType();
304
        }
305
306
        $str = '<' . $this->nss . $type . ">\n";
307
308
        if (!$geometry->isEmpty()) {
309
            $str .= '<' . $this->nss . 'coordinates>';
310
            $i = 0;
311
            foreach ($geometry->getComponents() as $comp) {
312
                if ($i != 0) {
313
                    $str .= ' ';
314
                }
315
                $str .= $comp->x() . ',' . $comp->y();
316
                $i++;
317
            }
318
319
            $str .= '</' . $this->nss . 'coordinates>';
320
        }
321
322
        $str .= '</' . $this->nss . $type . ">\n";
323
324
        return $str;
325
    }
326
327
    /**
328
     * @param Polygon $geometry
329
     * @return string
330
     */
331
    public function polygonToKML($geometry)
332
    {
333
        $components = $geometry->getComponents();
334
        $str = '';
335
        if (!empty($components)) {
336
            /** @noinspection PhpParamsInspection */
337
            $str = '<' . $this->nss . 'outerBoundaryIs>' . $this->linestringToKML($components[0], 'LinearRing') . '</' . $this->nss . 'outerBoundaryIs>';
338
            foreach (array_slice($components, 1) as $comp) {
339
                $str .= '<' . $this->nss . 'innerBoundaryIs>' . $this->linestringToKML($comp) . '</' . $this->nss . 'innerBoundaryIs>';
340
            }
341
        }
342
343
        return '<' . $this->nss . "Polygon>\n" . $str . '</' . $this->nss . "Polygon>\n";
344
    }
345
346
    /**
347
     * @param Collection $geometry
348
     * @return string
349
     */
350
    public function collectionToKML($geometry)
351
    {
352
        $components = $geometry->getComponents();
353
        $str = '<' . $this->nss . "MultiGeometry>\n";
354
        foreach ($components as $component) {
355
            $subAdapter = new KML();
356
            $str .= $subAdapter->write($component);
357
        }
358
359
        return $str . '</' . $this->nss . "MultiGeometry>\n";
360
    }
361
}
362