GeoRSS::read()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
namespace geoPHP\Adapter;
3
4
use geoPHP\Geometry\Collection;
5
use geoPHP\geoPHP;
6
use geoPHP\Geometry\Geometry;
7
use geoPHP\Geometry\GeometryCollection;
8
use geoPHP\Geometry\Point;
9
use geoPHP\Geometry\LineString;
10
use geoPHP\Geometry\Polygon;
11
12
/*
13
 * Copyright (c) Patrick Hayes
14
 *
15
 * This code is open-source and licenced under the Modified BSD License.
16
 * For the full copyright and license information, please view the LICENSE
17
 * file that was distributed with this source code.
18
 */
19
20
/**
21
 * PHP Geometry/GeoRSS encoder/decoder
22
 */
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
216
    {
217
        $points = [];
218
        $circleElements = $this->xmlObject->getElementsByTagName('circle');
219
        foreach ($circleElements as $circle) {
220
            $parts = explode(' ', trim($circle->firstChild->nodeValue));
221
            $points[] = new Point($parts[1], $parts[0]);
222
        }
223
        return $points;
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
305
    {
306
        $georss = '<' . $this->nss . 'where>';
307
        $components = $geometry->getComponents();
308
        foreach ($components as $component) {
309
            $georss .= $this->geometryToGeoRSS($component);
310
        }
311
312
        $georss .= '</' . $this->nss . 'where>';
313
314
        return $georss;
315
    }
316
}
317