GeoJSON::arrayToMultiLineString()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 1
1
<?php
2
namespace geoPHP\Adapter;
3
4
use geoPHP\geoPHP;
5
use geoPHP\Geometry\Geometry;
6
use geoPHP\Geometry\GeometryCollection;
7
use geoPHP\Geometry\Point;
8
use geoPHP\Geometry\MultiPoint;
9
use geoPHP\Geometry\LineString;
10
use geoPHP\Geometry\MultiLineString;
11
use geoPHP\Geometry\Polygon;
12
use geoPHP\Geometry\MultiPolygon;
13
14
/**
15
 * GeoJSON class : a geoJSON reader/writer.
16
 *
17
 * Note that it will always return a GeoJSON geometry. This
18
 * means that if you pass it a feature, it will return the
19
 * geometry of that feature strip everything else.
20
 */
21
class GeoJSON implements GeoAdapter
22
{
23
24
    /**
25
     * Given an object or a string, return a Geometry
26
     *
27
     * @param string $input The GeoJSON string or object
28
     * @return Geometry
29
     * @throws \Exception
30
     */
31
    public function read(string $input): Geometry
32
    {
33
        #if (is_string($input)) {
34
            $input = json_decode($input);
35
        #}
36
        if (!is_object($input)) {
37
            throw new \Exception('Invalid JSON');
38
        }
39
        if (!isset($input->type) || !is_string($input->type)) {
40
            throw new \Exception('Invalid GeoJSON');
41
        }
42
43
        /** @var \stdClass $input */
44
        return $this->parseJSONObjects($input);
45
    }
46
    
47
    /**
48
     * @param \stdClass $input stdClass
49
     * @return Geometry
50
     */
51
    private function parseJSONObjects(\stdClass $input): Geometry
52
    {
53
        // FeatureCollection
54
        if ($input->type === 'FeatureCollection') {
55
            $geometries = [];
56
            foreach ($input->features as $feature) {
57
                $geometries[] = $this->parseJSONObjects($feature);
58
            }
59
            return geoPHP::buildGeometry($geometries);
60
        }
61
62
        // Feature
63
        if ($input->type === 'Feature') {
64
            return $this->geoJSONFeatureToGeometry($input);
65
        }
66
67
        // Geometry
68
        return $this->geoJSONObjectToGeometry($input);
69
    }
70
71
    /**
72
     * @param \stdClass $input
73
     * @return int|null
74
     */
75
    private function getSRID($input)
76
    {
77
        if (isset($input->crs->properties->name)) {
78
            $m = [];
79
            // parse CRS codes in forms "EPSG:1234" and "urn:ogc:def:crs:EPSG::1234"
80
            preg_match('#EPSG[:]+(\d+)#', $input->crs->properties->name, $m);
81
            return isset($m[1]) ? (int) $m[1] : null;
82
        }
83
        
84
        return null;
85
    }
86
87
    /**
88
     * @param \stdClass $obj
89
     * @return Geometry
90
     * @throws \Exception
91
     */
92
    private function geoJSONFeatureToGeometry($obj): Geometry
93
    {
94
        $geometry = $this->parseJSONObjects($obj->geometry);
95
        if (isset($obj->properties)) {
96
            foreach ($obj->properties as $property => $value) {
97
                $geometry->setData($property, $value);
98
            }
99
        }
100
101
        return $geometry;
102
    }
103
104
    /**
105
     * @param \stdClass $obj
106
     * @return Geometry
107
     * @throws \Exception
108
     */
109
    private function geoJSONObjectToGeometry($obj): Geometry
110
    {
111
        $type = $obj->type;
112
113
        if ($type === 'GeometryCollection') {
114
            return $this->geoJSONObjectToGeometryCollection($obj);
115
        }
116
        $method = 'arrayTo' . $type;
117
        /** @var GeometryCollection $geometry */
118
        $geometry = $this->$method($obj->coordinates);
119
        $geometry->setSRID($this->getSRID($obj));
120
        
121
        return $geometry;
122
    }
123
124
    /**
125
     * @param array<int|float> $coordinates Array of coordinates
126
     * @return Point
127
     */
128
    private function arrayToPoint(array $coordinates): Point
129
    {
130
        switch (count($coordinates)) {
131
            case 2:
132
                return new Point($coordinates[0], $coordinates[1]);
133
            case 3:
134
                return new Point($coordinates[0], $coordinates[1], $coordinates[2]);
135
            case 4:
136
                return new Point($coordinates[0], $coordinates[1], $coordinates[2], $coordinates[3]);
137
            default:
138
                return new Point();
139
        }
140
    }
141
142
    /**
143
     * @param array<array> $array
144
     * @return LineString
145
     */
146
    private function arrayToLineString(array $array): LineString
147
    {
148
        $points = [];
149
        foreach ($array as $componentArray) {
150
            $points[] = $this->arrayToPoint($componentArray);
151
        }
152
        return new LineString($points);
153
    }
154
155
    /**
156
     * @param array<array> $array
157
     * @return Polygon
158
     */
159
    private function arrayToPolygon(array $array): Polygon
160
    {
161
        $lines = [];
162
        foreach ($array as $componentArray) {
163
            $lines[] = $this->arrayToLineString($componentArray);
164
        }
165
        return new Polygon($lines);
166
    }
167
168
    /**
169
     * @noinspection PhpUnusedPrivateMethodInspection
170
     * @param array<array> $array
171
     * @return MultiPoint
172
     */
173
    private function arrayToMultiPoint(array $array): MultiPoint
174
    {
175
        $points = [];
176
        foreach ($array as $componentArray) {
177
            $points[] = $this->arrayToPoint($componentArray);
178
        }
179
        return new MultiPoint($points);
180
    }
181
182
    /**
183
     * @noinspection PhpUnusedPrivateMethodInspection
184
     * @param array<array> $array
185
     * @return MultiLineString
186
     */
187
    private function arrayToMultiLineString(array $array): MultiLineString
188
    {
189
        $lines = [];
190
        foreach ($array as $componentArray) {
191
            $lines[] = $this->arrayToLineString($componentArray);
192
        }
193
        return new MultiLineString($lines);
194
    }
195
196
    /**
197
     * @noinspection PhpUnusedPrivateMethodInspection
198
     * @param array<array> $array
199
     * @return MultiPolygon
200
     */
201
    private function arrayToMultiPolygon(array $array): MultiPolygon
202
    {
203
        $polygons = [];
204
        foreach ($array as $componentArray) {
205
            $polygons[] = $this->arrayToPolygon($componentArray);
206
        }
207
        return new MultiPolygon($polygons);
208
    }
209
210
    /**
211
     * @param \stdClass $obj
212
     * @throws \Exception
213
     * @return GeometryCollection
214
     */
215
    private function geoJSONObjectToGeometryCollection($obj): GeometryCollection
216
    {
217
        if (!isset($obj->geometries) || !is_array($obj->geometries)) {
218
            throw new \Exception('Invalid GeoJSON: GeometryCollection with no component geometries.');
219
        }
220
        
221
        $geometries = [];
222
        foreach ($obj->geometries as $componentObject) {
223
            $geometries[] = $this->geoJSONObjectToGeometry($componentObject);
224
        }
225
        
226
        $collection = new GeometryCollection($geometries);
227
        $collection->setSRID($this->getSRID($obj));
228
        
229
        return $collection;
230
    }
231
232
    /**
233
     * Serializes an object into a geojson string
234
     *
235
     * @param Geometry $geometry The object to serialize
236
     * @return string The GeoJSON string
237
     */
238
    public function write(Geometry $geometry): string
239
    {
240
        $response = json_encode($this->getArray($geometry));
241
        return $response ? $response : '';
242
    }
243
244
    /**
245
     * Creates a geoJSON array
246
     *
247
     * If the root geometry is a GeometryCollection and any of its geometries has data,
248
     * the root element will be a FeatureCollection with Feature elements (with the data).
249
     * If the root geometry has data, it will be included in a Feature object that contains the data.
250
     *
251
     * The geometry should have geographical coordinates since CRS support has been removed from from geoJSON specification (RFC 7946)
252
     * The geometry should'nt be measured, since geoJSON specification (RFC 7946) only supports the dimensional positions.
253
     *
254
     * @param Geometry|GeometryCollection $geometry
255
     * @param bool $isRoot Is geometry the root geometry?
256
     * @return array<string, mixed>
257
     */
258
    public function getArray(Geometry $geometry, bool $isRoot = true): array
259
    {
260
        if ($geometry->geometryType() === Geometry::GEOMETRY_COLLECTION) {
261
            $components = [];
262
            $isFeatureCollection = false;
263
            foreach ($geometry->getComponents() as $component) {
264
                if ($component->getData() !== null) {
265
                    $isFeatureCollection = true;
266
                }
267
                $components[] = $this->getArray($component, false);
268
            }
269
            if (!$isFeatureCollection || !$isRoot) {
270
                return [
271
                    'type' => 'GeometryCollection',
272
                    'geometries' => $components
273
                ];
274
            } else {
275
                $features = [];
276
                foreach ($geometry->getComponents() as $i => $component) {
277
                    $features[] = [
278
                        'type' => 'Feature',
279
                        'properties' => $component->getData(),
280
                        'geometry' => $components[$i],
281
                    ];
282
                }
283
                return [
284
                    'type' => 'FeatureCollection',
285
                    'features' => $features
286
                ];
287
            }
288
        }
289
290
        if ($isRoot && $geometry->getData() !== null) {
291
            return [
292
                'type' => 'Feature',
293
                'properties' => $geometry->getData(),
294
                'geometry' => [
295
                    'type' => $geometry->geometryType(),
296
                    'coordinates' => $geometry->isEmpty() ? [] : $geometry->asArray()
297
                ]
298
            ];
299
        }
300
        $object = [
301
            'type' => $geometry->geometryType(),
302
            'coordinates' => $geometry->isEmpty() ? [] : $geometry->asArray()
303
        ];
304
        return $object;
305
    }
306
}
307