Skip to content

Commit 4bb981a

Browse files
authored
Merge pull request #56 from Maxxen/feat/geojson
Add initial support GeoJson
2 parents a3897e7 + be6bbe7 commit 4bb981a

File tree

9 files changed

+14954
-0
lines changed

9 files changed

+14954
-0
lines changed

spatial/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ add_subdirectory(src)
1919
include_directories(third_party/ryu/include)
2020
add_subdirectory(third_party/ryu)
2121

22+
include_directories(third_party/yyjson/include)
23+
add_subdirectory(third_party/yyjson)
24+
25+
2226
add_library(${EXTENSION_NAME} STATIC ${EXTENSION_SOURCES})
2327

2428
# Build dependencies

spatial/include/spatial/core/functions/scalar.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ struct CoreScalarFunctions {
1010
public:
1111
static void Register(ClientContext &context) {
1212
RegisterStArea(context);
13+
RegisterStAsGeoJSON(context);
1314
RegisterStAsText(context);
1415
RegisterStAsWKB(context);
1516
RegisterStAsHEXWKB(context);
@@ -32,6 +33,9 @@ struct CoreScalarFunctions {
3233
// ST_Area
3334
static void RegisterStArea(ClientContext &context);
3435

36+
// ST_AsGeoJSON
37+
static void RegisterStAsGeoJSON(ClientContext &context);
38+
3539
// ST_AsText
3640
static void RegisterStAsText(ClientContext &context);
3741

spatial/src/spatial/core/functions/scalar/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
set(EXTENSION_SOURCES
22
${EXTENSION_SOURCES}
33
${CMAKE_CURRENT_SOURCE_DIR}/st_area.cpp
4+
${CMAKE_CURRENT_SOURCE_DIR}/st_asgeojson.cpp
45
${CMAKE_CURRENT_SOURCE_DIR}/st_ashexwkb.cpp
56
${CMAKE_CURRENT_SOURCE_DIR}/st_astext.cpp
67
${CMAKE_CURRENT_SOURCE_DIR}/st_aswkb.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#include "duckdb/common/vector_operations/generic_executor.hpp"
2+
#include "duckdb/common/vector_operations/unary_executor.hpp"
3+
#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
4+
#include "duckdb/planner/expression/bound_function_expression.hpp"
5+
#include "duckdb/common/types/cast_helpers.hpp"
6+
7+
#include "spatial/common.hpp"
8+
#include "spatial/core/functions/scalar.hpp"
9+
#include "spatial/core/functions/common.hpp"
10+
#include "spatial/core/geometry/geometry_factory.hpp"
11+
#include "spatial/core/types.hpp"
12+
13+
#include "yyjson.h"
14+
15+
namespace spatial {
16+
17+
namespace core {
18+
19+
//------------------------------------------------------------------------------
20+
// Operations
21+
//------------------------------------------------------------------------------
22+
static void VerticesToGeoJSON(const VertexVector &vertices, yyjson_mut_doc* doc, yyjson_mut_val* arr) {
23+
// TODO: If the vertexvector is empty, do we null, add an empty array or a pair of NaN?
24+
for (uint32_t i = 0; i < vertices.count; i++) {
25+
auto &vertex = vertices[i];
26+
auto coord = yyjson_mut_arr(doc);
27+
yyjson_mut_arr_add_real(doc, coord, vertex.x);
28+
yyjson_mut_arr_add_real(doc, coord, vertex.y);
29+
yyjson_mut_arr_append(arr, coord);
30+
}
31+
}
32+
33+
static void ToGeoJSON(const Point &point, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
34+
yyjson_mut_obj_add_str(doc, obj, "type", "Point");
35+
36+
auto coords = yyjson_mut_arr(doc);
37+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
38+
39+
if(!point.IsEmpty()) {
40+
auto &vertex = point.GetVertex();
41+
yyjson_mut_arr_add_real(doc, coords, vertex.x);
42+
yyjson_mut_arr_add_real(doc, coords, vertex.y);
43+
}
44+
}
45+
46+
static void ToGeoJSON(const LineString &line, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
47+
yyjson_mut_obj_add_str(doc, obj, "type", "LineString");
48+
49+
auto coords = yyjson_mut_arr(doc);
50+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
51+
VerticesToGeoJSON(line.points, doc, coords);
52+
}
53+
54+
static void ToGeoJSON(const Polygon &poly, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
55+
yyjson_mut_obj_add_str(doc, obj, "type", "Polygon");
56+
57+
auto coords = yyjson_mut_arr(doc);
58+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
59+
for (uint32_t i = 0; i < poly.num_rings; i++) {
60+
auto &ring = poly.rings[i];
61+
auto ring_coords = yyjson_mut_arr(doc);
62+
VerticesToGeoJSON(ring, doc, ring_coords);
63+
yyjson_mut_arr_append(coords, ring_coords);
64+
}
65+
}
66+
67+
static void ToGeoJSON(const MultiPoint &mpoint, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
68+
yyjson_mut_obj_add_str(doc, obj, "type", "MultiPoint");
69+
70+
auto coords = yyjson_mut_arr(doc);
71+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
72+
for (uint32_t i = 0; i < mpoint.Count(); i++) {
73+
auto &point = mpoint.points[i];
74+
VerticesToGeoJSON(point.data, doc, coords);
75+
}
76+
}
77+
78+
static void ToGeoJSON(const MultiLineString &mline, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
79+
yyjson_mut_obj_add_str(doc, obj, "type", "MultiLineString");
80+
81+
auto coords = yyjson_mut_arr(doc);
82+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
83+
84+
for (uint32_t i = 0; i < mline.Count(); i++) {
85+
auto &line = mline.linestrings[i];
86+
auto line_coords = yyjson_mut_arr(doc);
87+
VerticesToGeoJSON(line.points, doc, line_coords);
88+
yyjson_mut_arr_append(coords, line_coords);
89+
}
90+
}
91+
92+
static void ToGeoJSON(const MultiPolygon &mpoly, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
93+
yyjson_mut_obj_add_str(doc, obj, "type", "MultiPolygon");
94+
95+
auto coords = yyjson_mut_arr(doc);
96+
yyjson_mut_obj_add_val(doc, obj, "coordinates", coords);
97+
98+
for (uint32_t i = 0; i < mpoly.Count(); i++) {
99+
auto &poly = mpoly.polygons[i];
100+
auto poly_coords = yyjson_mut_arr(doc);
101+
for (uint32_t j = 0; j < poly.num_rings; j++) {
102+
auto &ring = poly.rings[j];
103+
auto ring_coords = yyjson_mut_arr(doc);
104+
VerticesToGeoJSON(ring, doc, ring_coords);
105+
yyjson_mut_arr_append(poly_coords, ring_coords);
106+
}
107+
yyjson_mut_arr_append(coords, poly_coords);
108+
}
109+
}
110+
111+
static void ToGeoJSON(const Geometry &geom, yyjson_mut_doc* doc, yyjson_mut_val* obj);
112+
static void ToGeoJSON(const GeometryCollection &collection, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
113+
yyjson_mut_obj_add_str(doc, obj, "type", "GeometryCollection");
114+
auto arr = yyjson_mut_arr(doc);
115+
yyjson_mut_obj_add_val(doc, obj, "geometries", arr);
116+
117+
for (idx_t i = 0; i < collection.num_geometries; i++) {
118+
auto &geom = collection.geometries[i];
119+
auto geom_obj = yyjson_mut_obj(doc);
120+
ToGeoJSON(geom, doc, geom_obj);
121+
yyjson_mut_arr_append(arr, geom_obj);
122+
}
123+
}
124+
125+
static void ToGeoJSON(const Geometry &geom, yyjson_mut_doc* doc, yyjson_mut_val* obj) {
126+
switch (geom.Type()) {
127+
case GeometryType::POINT: ToGeoJSON(geom.GetPoint(), doc, obj); break;
128+
case GeometryType::LINESTRING: ToGeoJSON(geom.GetLineString(), doc, obj); break;
129+
case GeometryType::POLYGON: ToGeoJSON(geom.GetPolygon(), doc, obj); break;
130+
case GeometryType::MULTIPOINT: ToGeoJSON(geom.GetMultiPoint(), doc, obj); break;
131+
case GeometryType::MULTILINESTRING: ToGeoJSON(geom.GetMultiLineString(), doc, obj); break;
132+
case GeometryType::MULTIPOLYGON: ToGeoJSON(geom.GetMultiPolygon(), doc, obj); break;
133+
case GeometryType::GEOMETRYCOLLECTION: ToGeoJSON(geom.GetGeometryCollection(), doc, obj); break;
134+
default: {
135+
throw NotImplementedException("Geometry type not supported");
136+
}
137+
}
138+
}
139+
140+
//------------------------------------------------------------------------------
141+
// GEOMETRY -> GEOJSON Fragment
142+
//------------------------------------------------------------------------------
143+
class JSONAllocator {
144+
// Stolen from the JSON extension :)
145+
public:
146+
explicit JSONAllocator(ArenaAllocator &allocator)
147+
: allocator(allocator), yyjson_allocator({Allocate, Reallocate, Free, &allocator}) {
148+
}
149+
150+
inline yyjson_alc *GetYYJSONAllocator() {
151+
return &yyjson_allocator;
152+
}
153+
154+
void Reset() {
155+
allocator.Reset();
156+
}
157+
158+
private:
159+
static inline void *Allocate(void *ctx, size_t size) {
160+
auto alloc = (ArenaAllocator *)ctx;
161+
return alloc->AllocateAligned(size);
162+
}
163+
164+
static inline void *Reallocate(void *ctx, void *ptr, size_t old_size, size_t size) {
165+
auto alloc = (ArenaAllocator *)ctx;
166+
return alloc->ReallocateAligned((data_ptr_t)ptr, old_size, size);
167+
}
168+
169+
static inline void Free(void *ctx, void *ptr) {
170+
// NOP because ArenaAllocator can't free
171+
}
172+
173+
private:
174+
ArenaAllocator &allocator;
175+
yyjson_alc yyjson_allocator;
176+
};
177+
178+
static void GeometryToGeoJSONFragmentFunction(DataChunk &args, ExpressionState &state, Vector &result) {
179+
D_ASSERT(args.data.size() == 1);
180+
auto &input = args.data[0];
181+
auto count = args.size();
182+
183+
auto &lstate = GeometryFunctionLocalState::ResetAndGet(state);
184+
185+
JSONAllocator json_allocator(lstate.factory.allocator);
186+
187+
UnaryExecutor::Execute<string_t, string_t>(input, result, count, [&](string_t input) {
188+
auto geometry = lstate.factory.Deserialize(input);
189+
190+
auto doc = yyjson_mut_doc_new(json_allocator.GetYYJSONAllocator());
191+
auto obj = yyjson_mut_obj(doc);
192+
yyjson_mut_doc_set_root(doc, obj);
193+
194+
ToGeoJSON(geometry, doc, obj);
195+
196+
size_t json_size = 0;
197+
// TODO: YYJSON_WRITE_PRETTY
198+
auto json_data = yyjson_mut_write(doc, 0, &json_size);
199+
auto json_str = StringVector::AddString(result, json_data, json_size);
200+
return json_str;
201+
});
202+
}
203+
204+
//------------------------------------------------------------------------------
205+
// Register functions
206+
//------------------------------------------------------------------------------
207+
void CoreScalarFunctions::RegisterStAsGeoJSON(ClientContext &context) {
208+
auto &catalog = Catalog::GetSystemCatalog(context);
209+
210+
ScalarFunctionSet geojson("ST_AsGeoJSON");
211+
geojson.AddFunction(ScalarFunction({GeoTypes::GEOMETRY()}, LogicalType::VARCHAR, GeometryToGeoJSONFragmentFunction,
212+
nullptr, nullptr, nullptr, GeometryFunctionLocalState::Init));
213+
214+
CreateScalarFunctionInfo info(geojson);
215+
info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT;
216+
catalog.CreateFunction(context, &info);
217+
}
218+
219+
} // namespace core
220+
221+
} // namespace spatial
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require spatial
2+
3+
# Geometry
4+
statement ok
5+
CREATE TABLE types (geom GEOMETRY);
6+
7+
statement ok
8+
INSERT INTO types VALUES
9+
(ST_GeomFromText('POINT EMPTY')),
10+
(ST_GeomFromText('POINT(0 0)')),
11+
(ST_GeomFromText('LINESTRING EMPTY')),
12+
(ST_GeomFromText('LINESTRING(0 0, 1 1)')),
13+
(ST_GeomFromText('POLYGON EMPTY')),
14+
(ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))')),
15+
(ST_GeomFromText('MULTIPOINT EMPTY')),
16+
(ST_GeomFromText('MULTIPOINT(0 0, 1 1)')),
17+
(ST_GeomFromText('MULTILINESTRING EMPTY')),
18+
(ST_GeomFromText('MULTILINESTRING((0 0, 1 1), (2 2, 3 3))')),
19+
(ST_GeomFromText('MULTIPOLYGON EMPTY')),
20+
(ST_GeomFromText('MULTIPOLYGON(((0 0, 1 0, 1 1, 0 1, 0 0)), ((2 2, 3 2, 3 3, 2 3, 2 2)))')),
21+
(ST_GeomFromText('GEOMETRYCOLLECTION EMPTY')),
22+
(ST_GeomFromText('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0, 1 1))'));
23+
24+
query I
25+
SELECT ST_AsGeoJSON(geom) FROM types;
26+
----
27+
{"type":"Point","coordinates":[]}
28+
{"type":"Point","coordinates":[0.0,0.0]}
29+
{"type":"LineString","coordinates":[]}
30+
{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0]]}
31+
{"type":"Polygon","coordinates":[]}
32+
{"type":"Polygon","coordinates":[[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0],[0.0,0.0]]]}
33+
{"type":"MultiPoint","coordinates":[]}
34+
{"type":"MultiPoint","coordinates":[[0.0,0.0],[1.0,1.0]]}
35+
{"type":"MultiLineString","coordinates":[]}
36+
{"type":"MultiLineString","coordinates":[[[0.0,0.0],[1.0,1.0]],[[2.0,2.0],[3.0,3.0]]]}
37+
{"type":"MultiPolygon","coordinates":[]}
38+
{"type":"MultiPolygon","coordinates":[[[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0],[0.0,0.0]]],[[[2.0,2.0],[3.0,2.0],[3.0,3.0],[2.0,3.0],[2.0,2.0]]]]}
39+
{"type":"GeometryCollection","geometries":[]}
40+
{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[0.0,0.0]},{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0]]}]}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
set(EXTENSION_SOURCES
3+
${EXTENSION_SOURCES}
4+
${CMAKE_CURRENT_SOURCE_DIR}/yyjson.c
5+
PARENT_SCOPE
6+
)

spatial/third_party/yyjson/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 YaoYuan <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

0 commit comments

Comments
 (0)