1+ // Mapbox Vector Tiles (MVT) implementation
2+
3+ #include " spatial/modules/mvt/mvt_module.hpp"
4+
5+ #include " duckdb/common/vector_operations/generic_executor.hpp"
6+ #include " spatial/geometry/geometry_serialization.hpp"
7+ #include " spatial/geometry/sgl.hpp"
8+ #include " spatial/spatial_types.hpp"
9+ #include < spatial/util/function_builder.hpp>
10+
11+ namespace duckdb {
12+
13+ namespace {
14+ // ######################################################################################################################
15+ // Util
16+ // ######################################################################################################################
17+
18+ // ======================================================================================================================
19+ // LocalState
20+ // ======================================================================================================================
21+
22+ class LocalState final : public FunctionLocalState {
23+ public:
24+ explicit LocalState (ClientContext &context) : arena(BufferAllocator::Get(context)), allocator(arena) {
25+ }
26+
27+ static unique_ptr<FunctionLocalState> Init (ExpressionState &state, const BoundFunctionExpression &expr,
28+ FunctionData *bind_data);
29+ static LocalState &ResetAndGet (ExpressionState &state);
30+
31+ string_t Serialize (Vector &vector, const sgl::geometry &geom);
32+
33+ GeometryAllocator &GetAllocator () {
34+ return allocator;
35+ }
36+
37+ private:
38+ ArenaAllocator arena;
39+ GeometryAllocator allocator;
40+ };
41+
42+ unique_ptr<FunctionLocalState> LocalState::Init (ExpressionState &state, const BoundFunctionExpression &expr,
43+ FunctionData *bind_data) {
44+ return make_uniq_base<FunctionLocalState, LocalState>(state.GetContext ());
45+ }
46+
47+ LocalState &LocalState::ResetAndGet (ExpressionState &state) {
48+ auto &local_state = ExecuteFunctionState::GetFunctionState (state)->Cast <LocalState>();
49+ local_state.arena .Reset ();
50+ return local_state;
51+ }
52+
53+ string_t LocalState::Serialize (Vector &vector, const sgl::geometry &geom) {
54+ const auto size = Serde::GetRequiredSize (geom);
55+ auto blob = StringVector::EmptyString (vector, size);
56+ Serde::Serialize (geom, blob.GetDataWriteable (), size);
57+ blob.Finalize ();
58+ return blob;
59+ }
60+
61+ } // namespace
62+
63+ namespace {
64+
65+ struct ST_TileEnvelope {
66+ static constexpr double RADIUS = 6378137.0 ;
67+ static constexpr double PI = 3.141592653589793 ;
68+ static constexpr double CIRCUMFERENCE = 2 * PI * RADIUS ;
69+
70+ static void ExecuteWebMercator (DataChunk &args, ExpressionState &state, Vector &result) {
71+ auto &lstate = LocalState::ResetAndGet (state);
72+
73+ TernaryExecutor::Execute<int32_t , int32_t , int32_t , string_t >(
74+ args.data [0 ], args.data [1 ], args.data [2 ], result, args.size (),
75+ [&](int32_t tile_zoom, int32_t tile_x, int32_t tile_y) {
76+ validate_tile_zoom_argument (tile_zoom);
77+ uint32_t zoom_extent = 1u << tile_zoom;
78+ validate_tile_index_arguments (zoom_extent, tile_x, tile_y);
79+ sgl::geometry bbox;
80+ get_tile_bbox (lstate.GetAllocator (), zoom_extent, tile_x, tile_y, bbox);
81+ return lstate.Serialize (result, bbox);
82+ });
83+ }
84+
85+ static void validate_tile_zoom_argument (int32_t tile_zoom) {
86+ if ((tile_zoom < 0 ) || (tile_zoom > 30 )) {
87+ throw InvalidInputException (" ST_TileEnvelope: tile_zoom must be in the range [0,30]" );
88+ }
89+ }
90+
91+ static void validate_tile_index_arguments (uint32_t zoom_extent, int32_t tile_x, int32_t tile_y) {
92+ if ((tile_x < 0 ) || ((uint32_t )tile_x >= zoom_extent)) {
93+ throw InvalidInputException (" ST_TileEnvelope: tile_x is out of range for specified tile_zoom" );
94+ }
95+ if ((tile_y < 0 ) || ((uint32_t )tile_y >= zoom_extent)) {
96+ throw InvalidInputException (" ST_TileEnvelope: tile_y is out of range for specified tile_zoom" );
97+ }
98+ }
99+
100+ static void get_tile_bbox (GeometryAllocator &allocator, uint32_t zoom_extent,
101+ int32_t tile_x, int32_t tile_y, sgl::geometry &bbox) {
102+ double single_tile_width = CIRCUMFERENCE / zoom_extent;
103+ double single_tile_height = CIRCUMFERENCE / zoom_extent;
104+ double tile_left = get_tile_left (tile_x, single_tile_width);
105+ double tile_right = tile_left + single_tile_width;
106+ double tile_top = get_tile_top (tile_y, single_tile_height);
107+ double tile_bottom = tile_top - single_tile_height;
108+
109+ sgl::polygon::init_from_bbox (allocator, tile_left, tile_bottom, tile_right, tile_top, bbox);
110+ }
111+
112+ static double get_tile_left (uint32_t tile_x, double single_tile_width) {
113+ return -0.5 * CIRCUMFERENCE + (tile_x * single_tile_width);
114+ }
115+
116+ static double get_tile_top (uint32_t tile_y, double single_tile_height) {
117+ return 0.5 * CIRCUMFERENCE - (tile_y * single_tile_height);
118+ }
119+
120+ // ------------------------------------------------------------------------------------------------------------------
121+ // Documentation
122+ // ------------------------------------------------------------------------------------------------------------------
123+ static constexpr auto DESCRIPTION = R"(
124+ The `ST_TileEnvelope` scalar function generates tile envelope rectangular polygons from specified zoom level and tile indices.
125+
126+ This is used in MVT generation to select the features corresponding to the tile extent. The envelope is in the Web Mercator
127+ coordinate reference system (EPSG:3857). The tile pyramid starts at zoom level 0, corresponding to a single tile for the
128+ world. Each zoom level doubles the number of tiles in each direction, such that zoom level 1 is 2 tiles wide by 2 tiles high,
129+ zoom level 2 is 4 tiles wide by 4 tiles high, and so on. Tile indices start at `[x=0, y=0]` at the top left, and increase
130+ down and right. For example, at zoom level 2, the top right tile is `[x=3, y=0]`, the bottom left tile is `[x=0, y=3]`, and
131+ the bottom right is `[x=3, y=3]`.
132+
133+ ```sql
134+ SELECT ST_TileEnvelope(2, 3, 1);
135+ ```
136+ )" ;
137+ static constexpr auto EXAMPLE = R"(
138+ SELECT ST_TileEnvelope(2, 3, 1);
139+ ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────┐
140+ │ st_tileenvelope(2, 3, 1) │
141+ │ geometry │
142+ ├───────────────────────────────────────────────────────────────────────────────────────────────────────────┤
143+ │ POLYGON ((1.00188E+07 0, 1.00188E+07 1.00188E+07, 2.00375E+07 1.00188E+07, 2.00375E+07 0, 1.00188E+07 0)) │
144+ └───────────────────────────────────────────────────────────────────────────────────────────────────────────┘
145+ )" ;
146+
147+ // ------------------------------------------------------------------------------------------------------------------
148+ // Register
149+ // ------------------------------------------------------------------------------------------------------------------
150+ static void Register (DatabaseInstance &db) {
151+ FunctionBuilder::RegisterScalar (db, " ST_TileEnvelope" , [](ScalarFunctionBuilder &func) {
152+ func.AddVariant ([](ScalarFunctionVariantBuilder &variant) {
153+ variant.AddParameter (" tile_zoom" , LogicalType::INTEGER );
154+ variant.AddParameter (" tile_x" , LogicalType::INTEGER );
155+ variant.AddParameter (" tile_y" , LogicalType::INTEGER );
156+ variant.SetReturnType (GeoTypes::GEOMETRY ());
157+ variant.SetInit (LocalState::Init);
158+ variant.SetFunction (ExecuteWebMercator);
159+ });
160+
161+ func.SetDescription (DESCRIPTION );
162+ func.SetExample (EXAMPLE );
163+
164+ func.SetTag (" ext" , " spatial" );
165+ func.SetTag (" category" , " conversion" );
166+ });
167+ }
168+ };
169+
170+ } // namespace
171+ // ------------------------------------------------------------------------------
172+ // Register
173+ // ------------------------------------------------------------------------------
174+ void RegisterMapboxVectorTileModule (DatabaseInstance &db) {
175+ ST_TileEnvelope::Register (db);
176+ };
177+
178+ } // namespace duckdb
0 commit comments