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

brick / geo / 13399333180

18 Feb 2025 08:17PM UTC coverage: 83.425%. First build
13399333180

push

github

BenMorel
BoundingBox: readonly + promoted properties

23 of 28 new or added lines in 10 files covered. (82.14%)

1525 of 1828 relevant lines covered (83.42%)

1858.37 hits per line

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

84.42
/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 InvalidArgumentException;
14
use stdClass;
15

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

23
    private readonly bool $setBbox;
24

25
    /**
26
     * @param bool $prettyPrint Whether to pretty-print the JSON output.
27
     * @param bool $setBbox     Whether to set the bbox attribute of each non-empty GeoJSON object.
28
     */
29
    public function __construct(bool $prettyPrint = false, bool $setBbox = false)
30
    {
31
        $this->prettyPrint = $prettyPrint;
364✔
32
        $this->setBbox = $setBbox;
364✔
33
    }
34

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

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

64
        if ($object instanceof FeatureCollection) {
196✔
65
            return $this->writeFeatureCollection($object);
14✔
66
        }
67

68
        return $this->writeGeometry($object);
182✔
69
    }
70

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

83
        if ($geometry !== null) {
182✔
84
            if ($this->setBbox) {
168✔
85
                $boundingBox = $geometry->getBoundingBox();
×
86
            }
87

88
            $geometry = $this->writeGeometry($geometry);
168✔
89
        }
90

91
        $result = [
182✔
92
            'type' => 'Feature',
182✔
93
            'properties' => $feature->getProperties(),
182✔
94
            'geometry' => $geometry
182✔
95
        ];
182✔
96

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

101
        return (object) $result;
182✔
102
    }
103

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

116
        $result = [
14✔
117
            'type' => 'FeatureCollection',
14✔
118
            'features' => $features
14✔
119
        ];
14✔
120

121
        if ($this->setBbox) {
14✔
NEW
122
            $boundingBox = BoundingBox::new();
×
123

124
            foreach ($featureCollection->getFeatures() as $feature) {
×
125
                $featureGeometry = $feature->getGeometry();
×
126

127
                if ($featureGeometry !== null) {
×
128
                    $boundingBox = $boundingBox->extendedWithBoundingBox($featureGeometry->getBoundingBox());
×
129
                }
130
            }
131

132
            if (! $boundingBox->isEmpty()) {
×
133
                $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
×
134
            }
135
        }
136

137
        return (object) $result;
14✔
138
    }
139

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

152
        // filter out MultiPoint, MultiLineString and MultiPolygon
153
        if ($geometry instanceof GeometryCollection && $geometry->geometryType() === 'GeometryCollection') {
350✔
154
            return $this->writeGeometryCollection($geometry);
42✔
155
        }
156

157
        $geometryType = $geometry->geometryType();
336✔
158

159
        $validGeometries = [
336✔
160
            'Point',
336✔
161
            'LineString',
336✔
162
            'Polygon',
336✔
163
            'MultiPoint',
336✔
164
            'MultiLineString',
336✔
165
            'MultiPolygon'
336✔
166
        ];
336✔
167

168
        if (! in_array($geometryType, $validGeometries, true)) {
336✔
169
            throw GeometryIOException::unsupportedGeometryType($geometry->geometryType());
×
170
        }
171

172
        $result = [
336✔
173
            'type' => $geometryType,
336✔
174
            'coordinates' => $geometry->toArray()
336✔
175
        ];
336✔
176

177
        $boundingBox = $geometry->getBoundingBox();
336✔
178

179
        if ($this->setBbox && ! $boundingBox->isEmpty()) {
336✔
180
            $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
7✔
181
        }
182

183
        return (object) $result;
336✔
184
    }
185

186
    /**
187
     * @psalm-suppress MoreSpecificReturnType
188
     * @psalm-suppress LessSpecificReturnStatement
189
     * @see https://github.com/vimeo/psalm/issues/8187
190
     *
191
     * @throws GeometryIOException
192
     */
193
    private function writeGeometryCollection(GeometryCollection $geometryCollection): stdClass
194
    {
195
        $geometries = $geometryCollection->geometries();
42✔
196

197
        $geometries = array_map(function(Geometry $geometry) {
42✔
198
            if ($geometry instanceof GeometryCollection) {
28✔
199
                throw new GeometryIOException('GeoJSON does not allow nested GeometryCollections.');
×
200
            }
201

202
            return $this->writeGeometry($geometry);
28✔
203
        }, $geometries);
42✔
204

205
        $result = [
42✔
206
            'type' => 'GeometryCollection',
42✔
207
            'geometries' => $geometries
42✔
208
        ];
42✔
209

210
        $boundingBox = $geometryCollection->getBoundingBox();
42✔
211

212
        if ($this->setBbox && ! $boundingBox->isEmpty()) {
42✔
213
            $result['bbox'] = $this->bboxToCoordinateArray($boundingBox);
×
214
        }
215

216
        return (object) $result;
42✔
217
    }
218

219
    private function bboxToCoordinateArray(BoundingBox $boundingBox): array
220
    {
221
        return array_merge(
7✔
222
            $boundingBox->getSouthWest()->toArray(),
7✔
223
            $boundingBox->getNorthEast()->toArray()
7✔
224
        );
7✔
225
    }
226
}
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