• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / geo / 13632627167

03 Mar 2025 01:58PM UTC coverage: 47.859% (+0.3%) from 47.546%
13632627167

push

github

BenMorel
Final methods in abstract test classes

1632 of 3410 relevant lines covered (47.86%)

965.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.34
/src/IO/GeoJSONWriter.php
1
<?php
2

3
declare(strict_types = 1);
4

5
namespace Brick\Geo\IO;
6

7
use Brick\Geo\BoundingBox;
8
use Brick\Geo\Exception\GeometryIOException;
9
use Brick\Geo\Geometry;
10
use Brick\Geo\GeometryCollection;
11
use Brick\Geo\IO\GeoJSON\Feature;
12
use Brick\Geo\IO\GeoJSON\FeatureCollection;
13
use stdClass;
14

15
/**
16
 * Converter class from Geometry to GeoJSON.
17
 */
18
class GeoJSONWriter
19
{
20
    private readonly bool $prettyPrint;
21

22
    private readonly bool $setBbox;
23

24
    private readonly bool $lenient;
25

26
    /**
27
     * @param bool $prettyPrint Whether to pretty-print the JSON output.
28
     * @param bool $setBbox     Whether to set the bbox attribute of each non-empty GeoJSON object.
29
     * @param bool $lenient     Whether to allow nested GeometryCollections, forbidden by the GeoJSON spec.
30
     */
31
    public function __construct(bool $prettyPrint = false, bool $setBbox = false, bool $lenient = false)
32
    {
33
        $this->prettyPrint = $prettyPrint;
399✔
34
        $this->setBbox = $setBbox;
399✔
35
        $this->lenient = $lenient;
399✔
36
    }
37

38
    /**
39
     * Writes the given object as GeoJSON.
40
     *
41
     * @param Geometry|Feature|FeatureCollection $object The object to export as GeoJSON.
42
     *
43
     * @return string The GeoJSON representation of the given object.
44
     *
45
     * @throws GeometryIOException If the given geometry cannot be exported as GeoJSON.
46
     */
47
    public function write(Geometry|Feature|FeatureCollection $object) : string
48
    {
49
        return json_encode($this->writeRaw($object), $this->prettyPrint ? JSON_PRETTY_PRINT : 0);
399✔
50
    }
51

52
    /**
53
     * Writes the given object as a raw stdClass object that can be JSON-encoded.
54
     *
55
     * @param Geometry|Feature|FeatureCollection $object
56
     *
57
     * @return stdClass An object to be JSON-encoded.
58
     *
59
     * @throws GeometryIOException
60
     */
61
    public function writeRaw(Geometry|Feature|FeatureCollection $object): stdClass
62
    {
63
        if ($object instanceof Feature) {
399✔
64
            return $this->writeFeature($object);
168✔
65
        }
66

67
        if ($object instanceof FeatureCollection) {
231✔
68
            return $this->writeFeatureCollection($object);
14✔
69
        }
70

71
        return $this->writeGeometry($object);
217✔
72
    }
73

74
    /**
75
     * @psalm-suppress MoreSpecificReturnType
76
     * @psalm-suppress LessSpecificReturnStatement
77
     * @see https://github.com/vimeo/psalm/issues/8187
78
     *
79
     * @throws GeometryIOException
80
     */
81
    private function writeFeature(Feature $feature): stdClass
82
    {
83
        $boundingBox = null;
182✔
84
        $geometry = $feature->getGeometry();
182✔
85

86
        if ($geometry !== null) {
182✔
87
            if ($this->setBbox) {
168✔
88
                $boundingBox = $geometry->getBoundingBox();
×
89
            }
90

91
            $geometry = $this->writeGeometry($geometry);
168✔
92
        }
93

94
        $result = [
182✔
95
            'type' => 'Feature',
182✔
96
            'properties' => $feature->getProperties(),
182✔
97
            'geometry' => $geometry
182✔
98
        ];
182✔
99

100
        if ($boundingBox !== null && ! $boundingBox->isEmpty()) {
182✔
101
            $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
×
102
        }
103

104
        return (object) $result;
182✔
105
    }
106

107
    /**
108
     * @psalm-suppress MoreSpecificReturnType
109
     * @psalm-suppress LessSpecificReturnStatement
110
     * @see https://github.com/vimeo/psalm/issues/8187
111
     *
112
     * @throws GeometryIOException
113
     */
114
    private function writeFeatureCollection(FeatureCollection $featureCollection): stdClass
115
    {
116
        $features = $featureCollection->getFeatures();
14✔
117
        $features = array_map(fn(Feature $feature) => $this->writeFeature($feature), $features);
14✔
118

119
        $result = [
14✔
120
            'type' => 'FeatureCollection',
14✔
121
            'features' => $features
14✔
122
        ];
14✔
123

124
        if ($this->setBbox) {
14✔
125
            $boundingBox = new BoundingBox();
×
126

127
            foreach ($featureCollection->getFeatures() as $feature) {
×
128
                $featureGeometry = $feature->getGeometry();
×
129

130
                if ($featureGeometry !== null) {
×
131
                    $boundingBox = $boundingBox->extendedWithBoundingBox($featureGeometry->getBoundingBox());
×
132
                }
133
            }
134

135
            if (! $boundingBox->isEmpty()) {
×
136
                $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
×
137
            }
138
        }
139

140
        return (object) $result;
14✔
141
    }
142

143
    /**
144
     * @psalm-suppress MoreSpecificReturnType
145
     * @psalm-suppress LessSpecificReturnStatement
146
     * @see https://github.com/vimeo/psalm/issues/8187
147
     *
148
     * @throws GeometryIOException
149
     */
150
    private function writeGeometry(Geometry $geometry): stdClass
151
    {
152
        // GeoJSON supports XY & XYZ only
153
        $geometry = $geometry->withoutM();
385✔
154

155
        // filter out MultiPoint, MultiLineString and MultiPolygon
156
        if ($geometry instanceof GeometryCollection && $geometry->geometryType() === 'GeometryCollection') {
385✔
157
            return $this->writeGeometryCollection($geometry);
77✔
158
        }
159

160
        $geometryType = $geometry->geometryType();
364✔
161

162
        $validGeometries = [
364✔
163
            'Point',
364✔
164
            'LineString',
364✔
165
            'Polygon',
364✔
166
            'MultiPoint',
364✔
167
            'MultiLineString',
364✔
168
            'MultiPolygon'
364✔
169
        ];
364✔
170

171
        if (! in_array($geometryType, $validGeometries, true)) {
364✔
172
            throw GeometryIOException::unsupportedGeometryType($geometry->geometryType());
×
173
        }
174

175
        $result = [
364✔
176
            'type' => $geometryType,
364✔
177
            'coordinates' => $geometry->toArray()
364✔
178
        ];
364✔
179

180
        if ($this->setBbox) {
364✔
181
            $boundingBox = $geometry->getBoundingBox();
7✔
182

183
            if (! $boundingBox->isEmpty()) {
7✔
184
                $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
7✔
185
            }
186
        }
187

188
        return (object) $result;
364✔
189
    }
190

191
    /**
192
     * @psalm-suppress MoreSpecificReturnType
193
     * @psalm-suppress LessSpecificReturnStatement
194
     * @see https://github.com/vimeo/psalm/issues/8187
195
     *
196
     * @throws GeometryIOException
197
     */
198
    private function writeGeometryCollection(GeometryCollection $geometryCollection): stdClass
199
    {
200
        $geometries = $geometryCollection->geometries();
77✔
201

202
        $geometries = array_map(function(Geometry $geometry) {
77✔
203
            if ($geometry::class === GeometryCollection::class && ! $this->lenient) {
63✔
204
                throw new GeometryIOException(
7✔
205
                    'GeoJSON does not allow nested GeometryCollections. ' .
7✔
206
                    'You can allow this by setting the $lenient flag to true.',
7✔
207
                );
7✔
208
            }
209

210
            return $this->writeGeometry($geometry);
56✔
211
        }, $geometries);
77✔
212

213
        $result = [
70✔
214
            'type' => 'GeometryCollection',
70✔
215
            'geometries' => $geometries
70✔
216
        ];
70✔
217

218
        if ($this->setBbox) {
70✔
219
            $boundingBox = $geometryCollection->getBoundingBox();
×
220

221
            if (! $boundingBox->isEmpty()) {
×
222
                $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
×
223
            }
224
        }
225

226
        return (object) $result;
70✔
227
    }
228

229
    private function bboxToCoordinateArray(BoundingBox $boundingBox): array
230
    {
231
        return array_merge(
7✔
232
            $boundingBox->getSouthWest()->toArray(),
7✔
233
            $boundingBox->getNorthEast()->toArray()
7✔
234
        );
7✔
235
    }
236
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc