Coverage Report

Created: 2026-07-16 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/io/GeoJSONWriter.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2021 Jared Erickson
7
 *
8
 * This is free software; you can redistribute and/or modify it under
9
 * the terms of the GNU Lesser General Public Licence as published
10
 * by the Free Software Foundation.
11
 * See the COPYING file for more information.
12
 *
13
 **********************************************************************/
14
15
#include <geos/io/GeoJSONWriter.h>
16
#include <geos/util/IllegalArgumentException.h>
17
#include <geos/geom/Coordinate.h>
18
#include <geos/geom/Point.h>
19
#include <geos/geom/LinearRing.h>
20
#include <geos/geom/LineString.h>
21
#include <geos/geom/Polygon.h>
22
#include <geos/geom/MultiPoint.h>
23
#include <geos/geom/MultiLineString.h>
24
#include <geos/geom/MultiPolygon.h>
25
#include <geos/geom/CoordinateSequence.h>
26
#include <geos/geom/PrecisionModel.h>
27
28
#include <algorithm>
29
#include <ostream>
30
#include <sstream>
31
#include <cassert>
32
#include <cmath>
33
34
#include "geos/util.h"
35
36
#define GEOS_COMPILATION
37
38
using namespace geos::geom;
39
using json = geos_nlohmann::ordered_json;
40
41
namespace geos {
42
namespace io { // geos.io
43
44
45
/* public */
46
void
47
GeoJSONWriter::setOutputDimension(uint8_t dims)
48
0
{
49
0
    if(dims < 2 || dims > 3) {
50
0
        throw util::IllegalArgumentException("GeoJSON output dimension must be 2 or 3");
51
0
    }
52
0
    defaultOutputDimension = dims;
53
0
}
54
55
0
void GeoJSONWriter::setForceCCW(bool newIsForceCCW) {
56
0
    isForceCCW = newIsForceCCW;
57
0
}
58
59
std::string GeoJSONWriter::write(const geom::Geometry* geometry, GeoJSONType type)
60
0
{
61
0
    json j;
62
0
    encode(geometry, type, j);
63
0
    return j.dump();
64
0
}
65
66
std::string GeoJSONWriter::writeFormatted(const geom::Geometry* geometry, GeoJSONType type, int indent)
67
0
{
68
0
    json j;
69
0
    encode(geometry, type, j);
70
0
    return j.dump(indent);
71
0
}
72
73
std::string GeoJSONWriter::write(const GeoJSONFeature& feature)
74
0
{
75
0
    json j;
76
0
    encodeFeature(feature, j);
77
0
    return j.dump();
78
0
}
79
80
void GeoJSONWriter::encodeGeoJSONValue(const std::string& key, const GeoJSONValue& value,
81
                                       geos_nlohmann::ordered_json& j)
82
0
{
83
0
    if (value.isNumber()) {
84
0
        if (j.is_object()) {
85
0
            j[key] = value.getNumber();
86
0
        }
87
0
        else {
88
0
            j.push_back(value.getNumber());
89
0
        }
90
0
    }
91
0
    else if (value.isString()) {
92
0
        if (j.is_object()) {
93
0
            j[key] = value.getString();
94
0
        }
95
0
        else {
96
0
            j.push_back(value.getString());
97
0
        }
98
0
    }
99
0
    else if (value.isBoolean()) {
100
0
        if (j.is_object()) {
101
0
            j[key] = value.getBoolean();
102
0
        }
103
0
        else {
104
0
            j.push_back(value.getBoolean());
105
0
        }
106
0
    }
107
0
    else if (value.isNull()) {
108
0
        if (j.is_object()) {
109
0
            j[key] = nullptr;
110
0
        }
111
0
        else {
112
0
            j.push_back(nullptr);
113
0
        }
114
0
    }
115
0
    else if (value.isArray()) {
116
0
        if (j.is_object()) {
117
0
          j[key] = json::array();
118
0
          for (const GeoJSONValue& v : value.getArray()) {
119
0
              encodeGeoJSONValue("", v, j[key]);
120
0
          }
121
0
        }
122
0
        else {
123
0
          json sub_array = json::array();
124
0
          for (const GeoJSONValue& v : value.getArray()) {
125
0
              encodeGeoJSONValue("", v, sub_array);
126
0
          }
127
0
          j.push_back(sub_array);
128
0
        }
129
0
    }
130
0
    else if (value.isObject()) {
131
0
        if (j.is_object()) {
132
0
          j[key] = json::object();
133
0
          for (const auto& entry : value.getObject()) {
134
0
              encodeGeoJSONValue(entry.first, entry.second, j[key]);
135
0
          }
136
0
        }
137
0
        else {
138
0
          json sub_obj = json::object();
139
0
          for (const auto& entry : value.getObject()) {
140
0
              encodeGeoJSONValue(entry.first, entry.second, sub_obj);
141
0
          }
142
0
          j.push_back(sub_obj);
143
0
        }
144
0
    }
145
0
}
146
147
std::string GeoJSONWriter::write(const GeoJSONFeatureCollection& features)
148
0
{
149
0
    json j;
150
0
    j["type"] = "FeatureCollection";
151
0
    json featuresJson = json::array();
152
0
    for (auto const& feature : features.getFeatures()) {
153
0
        json featureJson;
154
0
        encodeFeature(feature, featureJson);
155
0
        featuresJson.push_back(featureJson);
156
0
    }
157
0
    j["features"] = featuresJson;
158
0
    return j.dump();
159
0
}
160
161
void GeoJSONWriter::encodeFeature(const GeoJSONFeature& feature, geos_nlohmann::ordered_json& j)
162
0
{
163
0
    j["type"] = "Feature";
164
165
0
    if (feature.getId().size() > 0) j["id"] = feature.getId();
166
167
0
    json geometryJson;
168
0
    encodeGeometry(feature.getGeometry(), geometryJson);
169
0
    j["geometry"] = geometryJson;
170
171
0
    json propertiesJson = json::object();
172
0
    for (auto const& property : feature.getProperties()) {
173
0
        std::string key = property.first;
174
0
        GeoJSONValue value = property.second;
175
0
        encodeGeoJSONValue(key, value, propertiesJson);
176
0
    }
177
0
    j["properties"] = propertiesJson;
178
0
}
179
180
void GeoJSONWriter::encode(const geom::Geometry* geometry, GeoJSONType geojsonType, geos_nlohmann::ordered_json& j)
181
0
{
182
0
    if (geojsonType == GeoJSONType::GEOMETRY) {
183
0
        encodeGeometry(geometry, j);
184
0
    }
185
0
    else if (geojsonType == GeoJSONType::FEATURE) {
186
0
        encodeFeature(geometry, j);
187
0
    }
188
0
    else if (geojsonType == GeoJSONType::FEATURE_COLLECTION) {
189
0
        encodeFeatureCollection(geometry, j);
190
0
    }
191
0
}
192
193
void GeoJSONWriter::encodeFeature(const geom::Geometry* g, geos_nlohmann::ordered_json& j)
194
0
{
195
0
    json geometryJson;
196
0
    encodeGeometry(g, geometryJson);
197
0
    j["type"] = "Feature";
198
0
    j["geometry"] = geometryJson;
199
0
}
200
201
void GeoJSONWriter::encodeFeatureCollection(const geom::Geometry* g, geos_nlohmann::ordered_json& j)
202
0
{
203
0
    json featureJson;
204
0
    encodeFeature(g, featureJson);
205
0
    std::vector<json> features;
206
0
    features.push_back(featureJson);
207
0
    j["type"] = "FeatureCollection";
208
0
    j["features"] = features;
209
0
}
210
211
void GeoJSONWriter::encodeGeometry(const geom::Geometry* geometry, geos_nlohmann::ordered_json& j)
212
0
{
213
0
    util::ensureNoCurvedComponents(geometry);
214
215
0
    auto type = geometry->getGeometryTypeId();
216
0
    if (type == GEOS_POINT) {
217
0
        auto point = static_cast<const geom::Point*>(geometry);
218
0
        encodePoint(point, j);
219
0
    }
220
0
    else if (type == GEOS_LINESTRING) {
221
0
        auto line = static_cast<const geom::LineString*>(geometry);
222
0
        encodeLineString(line, j);
223
0
    }
224
0
    else if (type == GEOS_LINEARRING) {
225
0
        auto line = static_cast<const geom::LineString*>(geometry);
226
0
        encodeLineString(line, j);
227
0
    }
228
0
    else if (type == GEOS_POLYGON) {
229
0
        auto poly = static_cast<const geom::Polygon*>(geometry);
230
0
        if (isForceCCW) {
231
0
            auto ccwPoly = poly->clone();
232
0
            ccwPoly->orientRings(false);
233
0
            encodePolygon(ccwPoly.get(), j);
234
0
        } else {
235
0
            encodePolygon(poly, j);
236
0
        }
237
0
    }
238
0
    else if (type == GEOS_MULTIPOINT) {
239
0
        auto multiPoint = static_cast<const geom::MultiPoint*>(geometry);
240
0
        encodeMultiPoint(multiPoint, j);
241
0
    }
242
0
    else if (type == GEOS_MULTILINESTRING) {
243
0
        auto multiLineString = static_cast<const geom::MultiLineString*>(geometry);
244
0
        encodeMultiLineString(multiLineString, j);
245
0
    }
246
0
    else if (type == GEOS_MULTIPOLYGON) {
247
0
        auto multiPolygon = static_cast<const geom::MultiPolygon*>(geometry);
248
0
        encodeMultiPolygon(multiPolygon, j);
249
0
    }
250
0
    else if (type == GEOS_GEOMETRYCOLLECTION) {
251
0
        auto geometryCollection = static_cast<const geom::GeometryCollection*>(geometry);
252
0
        encodeGeometryCollection(geometryCollection, j);
253
0
    }
254
0
}
255
256
void GeoJSONWriter::encodePoint(const geom::Point* point, geos_nlohmann::ordered_json& j)
257
0
{
258
0
    j["type"] = "Point";
259
0
    if (!point->isEmpty()) {
260
0
        auto as_coord = Coordinate { point->getX(), point->getY(), point->getZ()};
261
0
        j["coordinates"] = convertCoordinate(&as_coord);
262
0
    }
263
0
    else {
264
0
        j["coordinates"] = j.array();
265
0
    }
266
0
}
267
268
void GeoJSONWriter::encodeLineString(const geom::LineString* line, geos_nlohmann::ordered_json& j)
269
0
{
270
0
    j["type"] = "LineString";
271
0
    j["coordinates"] = convertCoordinateSequence(line->getCoordinates().get());
272
0
}
273
274
void GeoJSONWriter::encodePolygon(const geom::Polygon* poly, geos_nlohmann::ordered_json& j)
275
0
{
276
0
    j["type"] = "Polygon";
277
0
    j["coordinates"] = convertLinearRings(poly);
278
0
}
279
280
void GeoJSONWriter::encodeMultiPoint(const geom::MultiPoint* multiPoint, geos_nlohmann::ordered_json& j)
281
0
{
282
0
    j["type"] = "MultiPoint";
283
0
    j["coordinates"] = convertCoordinateSequence(multiPoint->getCoordinates().get());
284
0
}
285
286
void GeoJSONWriter::encodeMultiLineString(const geom::MultiLineString* multiLineString, geos_nlohmann::ordered_json& j)
287
0
{
288
0
    j["type"] = "MultiLineString";
289
0
    std::vector<std::vector<std::vector<double>>> lines;
290
0
    lines.reserve(multiLineString->getNumGeometries());
291
0
    for (size_t i = 0; i < multiLineString->getNumGeometries(); i++) {
292
0
        lines.push_back(convertCoordinateSequence(multiLineString->getGeometryN(i)->getCoordinates().get()));
293
0
    }
294
0
    j["coordinates"] = lines;
295
0
}
296
297
void GeoJSONWriter::encodeMultiPolygon(const geom::MultiPolygon* multiPolygon, geos_nlohmann::ordered_json& json)
298
0
{
299
0
    json["type"] = "MultiPolygon";
300
0
    std::vector<std::vector<std::vector<std::vector<double>>>> polygons;
301
0
    polygons.reserve(multiPolygon->getNumGeometries());
302
0
    for (size_t i = 0; i < multiPolygon->getNumGeometries(); i++) {
303
0
        const Polygon* polygon = multiPolygon->getGeometryN(i);
304
0
        if (isForceCCW) {
305
0
            auto ccwPolygon = polygon->clone();
306
0
            ccwPolygon->orientRings(false);
307
0
            polygons.push_back(convertLinearRings(ccwPolygon.get()));
308
0
        } else {
309
0
            polygons.push_back(convertLinearRings(polygon));
310
0
        }
311
0
    }
312
0
    json["coordinates"] = polygons;
313
0
}
314
315
void GeoJSONWriter::encodeGeometryCollection(const geom::GeometryCollection* g, geos_nlohmann::ordered_json& j)
316
0
{
317
0
    j["type"] = "GeometryCollection";
318
0
    auto geometryArray = j.array();
319
0
    for (size_t i = 0; i < g->getNumGeometries(); i++) {
320
0
        auto geometryObj = j.object();
321
0
        encodeGeometry(g->getGeometryN(i), geometryObj);
322
0
        geometryArray.push_back(geometryObj);
323
0
    }
324
0
    j["geometries"] = geometryArray;
325
0
}
326
327
std::vector<double> GeoJSONWriter::convertCoordinate(const Coordinate* c)
328
0
{
329
0
    if (std::isnan(c->z) || defaultOutputDimension == 2) {
330
0
        return std::vector<double> { c->x, c->y };
331
0
    }
332
0
    return std::vector<double> { c->x, c->y, c->z };
333
0
}
334
335
std::vector<std::vector<double>> GeoJSONWriter::convertCoordinateSequence(const CoordinateSequence*
336
                                    coordinateSequence)
337
0
{
338
0
    std::vector<std::vector<double>> coordinates;
339
0
    coordinates.reserve(coordinateSequence->size());
340
0
    for (size_t i = 0; i<coordinateSequence->size(); i++) {
341
0
        const geom::Coordinate& c = coordinateSequence->getAt(i);
342
0
        coordinates.push_back(convertCoordinate(&c));
343
0
    }
344
0
    return coordinates;
345
0
}
346
347
std::vector<std::vector<std::vector<double>>> GeoJSONWriter::convertLinearRings(const geom::Polygon* poly)
348
0
{
349
0
    std::vector<std::vector<std::vector<double>>> rings;
350
0
    auto ring = poly->getExteriorRing();
351
0
    rings.reserve(poly->getNumInteriorRing() + 1);
352
0
    rings.push_back(convertCoordinateSequence(ring->getCoordinates().get()));
353
0
    for (size_t i = 0; i < poly->getNumInteriorRing(); i++) {
354
0
        rings.push_back(convertCoordinateSequence(poly->getInteriorRingN(i)->getCoordinates().get()));
355
0
    }
356
0
    return rings;
357
0
}
358
359
} // namespace geos.io
360
} // namespace geos