1 /**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright 2019 Raúl Marín
22 *
23 **********************************************************************/
24
25 #include "lwgeom_wagyu.h"
26
27 #define USE_WAGYU_INTERRUPT
28 #include "mapbox/geometry/wagyu/wagyu.hpp"
29
30 #include <iterator>
31 #include <limits>
32 #include <vector>
33
34 using namespace mapbox::geometry;
35
36 using wagyu_coord_type = std::int32_t;
37 using wagyu_polygon = mapbox::geometry::polygon<wagyu_coord_type>;
38 using wagyu_multipolygon = mapbox::geometry::multi_polygon<wagyu_coord_type>;
39 using wagyu_linearring = mapbox::geometry::linear_ring<wagyu_coord_type>;
40 using vwpolygon = std::vector<wagyu_polygon>;
41 using wagyu_point = mapbox::geometry::point<wagyu_coord_type>;
42
43 static wagyu_linearring
ptarray_to_wglinearring(const POINTARRAY * pa)44 ptarray_to_wglinearring(const POINTARRAY *pa)
45 {
46 wagyu_linearring lr;
47 lr.reserve(pa->npoints);
48
49 size_t point_size = ptarray_point_size(pa);
50 size_t pa_size = pa->npoints;
51 uint8_t *buffer = pa->serialized_pointlist;
52 for (std::uint32_t i = 0; i < pa_size; i++)
53 {
54 const wagyu_coord_type x = static_cast<wagyu_coord_type>(*(double*) buffer);
55 const wagyu_coord_type y = static_cast<wagyu_coord_type>(((double*) buffer)[1]);
56 buffer += point_size;
57
58 lr.emplace_back(static_cast<wagyu_coord_type>(x), static_cast<wagyu_coord_type>(y));
59 }
60
61 return lr;
62 }
63
64 static vwpolygon
lwpoly_to_vwgpoly(const LWPOLY * geom,const GBOX * box)65 lwpoly_to_vwgpoly(const LWPOLY *geom, const GBOX *box)
66 {
67 LWCOLLECTION *geom_clipped_x = lwgeom_clip_to_ordinate_range((LWGEOM *)geom, 'X', box->xmin, box->xmax, 0);
68 LWCOLLECTION *geom_clipped = lwgeom_clip_to_ordinate_range((LWGEOM *)geom_clipped_x, 'Y', box->ymin, box->ymax, 0);
69
70 vwpolygon vp;
71 for (std::uint32_t i = 0; i < geom_clipped->ngeoms; i++)
72 {
73 assert(geom_clipped->geoms[i]->type == POLYGONTYPE);
74 LWPOLY *poly = (LWPOLY *)geom_clipped->geoms[i];
75 for (std::uint32_t ring = 0; ring < poly->nrings; ring++)
76 {
77 wagyu_polygon pol;
78 pol.push_back(ptarray_to_wglinearring(poly->rings[ring]));
79
80 /* If it has an inner ring, push it too */
81 ring++;
82 if (ring != poly->nrings)
83 {
84 pol.push_back(ptarray_to_wglinearring(poly->rings[ring]));
85 }
86
87 vp.push_back(pol);
88 }
89 }
90
91 lwgeom_free((LWGEOM *)geom_clipped_x);
92 lwgeom_free((LWGEOM *)geom_clipped);
93
94 return vp;
95 }
96
97 static vwpolygon
lwmpoly_to_vwgpoly(const LWMPOLY * geom,const GBOX * box)98 lwmpoly_to_vwgpoly(const LWMPOLY *geom, const GBOX *box)
99 {
100 vwpolygon vp;
101 for (std::uint32_t i = 0; i < geom->ngeoms; i++)
102 {
103 vwpolygon vp_intermediate = lwpoly_to_vwgpoly(geom->geoms[i], box);
104 vp.insert(vp.end(),
105 std::make_move_iterator(vp_intermediate.begin()),
106 std::make_move_iterator(vp_intermediate.end()));
107 }
108
109 return vp;
110 }
111
112 /**
113 * Transforms a liblwgeom geometry (types POLYGONTYPE or MULTIPOLYGONTYPE)
114 * into an array of mapbox::geometry (polygon)
115 * A single lwgeom polygon can lead to N mapbox polygon as odd numbered rings
116 * are treated as new polygons (and even numbered treated as holes in the
117 * previous rings)
118 */
119 static vwpolygon
lwgeom_to_vwgpoly(const LWGEOM * geom,const GBOX * box)120 lwgeom_to_vwgpoly(const LWGEOM *geom, const GBOX *box)
121 {
122 switch (geom->type)
123 {
124 case POLYGONTYPE:
125 return lwpoly_to_vwgpoly(reinterpret_cast<const LWPOLY *>(geom), box);
126 case MULTIPOLYGONTYPE:
127 return lwmpoly_to_vwgpoly(reinterpret_cast<const LWMPOLY *>(geom), box);
128 default:
129 return vwpolygon();
130 }
131 }
132
133 static POINTARRAY *
wglinearring_to_ptarray(const wagyu_polygon::linear_ring_type & lr)134 wglinearring_to_ptarray(const wagyu_polygon::linear_ring_type &lr)
135 {
136 const uint32_t npoints = lr.size();
137 POINTARRAY *pa = ptarray_construct(0, 0, npoints);
138
139 for (uint32_t i = 0; i < npoints; i++)
140 {
141 const wagyu_polygon::linear_ring_type::point_type &p = lr[i];
142 POINT4D point = {static_cast<double>(p.x), static_cast<double>(p.y), 0.0, 0.0};
143 ptarray_set_point4d(pa, i, &point);
144 }
145
146 return pa;
147 }
148
149 static LWGEOM *
wgpoly_to_lwgeom(const wagyu_multipolygon::polygon_type & p)150 wgpoly_to_lwgeom(const wagyu_multipolygon::polygon_type &p)
151 {
152 const uint32_t nrings = p.size();
153 POINTARRAY **ppa = reinterpret_cast<POINTARRAY **>(lwalloc(sizeof(POINTARRAY *) * nrings));
154
155 for (uint32_t i = 0; i < nrings; i++)
156 {
157 ppa[i] = wglinearring_to_ptarray(p[i]);
158 }
159
160 return reinterpret_cast<LWGEOM *>(lwpoly_construct(0, NULL, nrings, ppa));
161 }
162
163 static LWGEOM *
wgmpoly_to_lwgeom(const wagyu_multipolygon & mp)164 wgmpoly_to_lwgeom(const wagyu_multipolygon &mp)
165 {
166 const uint32_t ngeoms = mp.size();
167 if (ngeoms == 0)
168 {
169 return NULL;
170 }
171 else if (ngeoms == 1)
172 {
173 return wgpoly_to_lwgeom(mp[0]);
174 }
175 else
176 {
177 LWGEOM **geoms = reinterpret_cast<LWGEOM **>(lwalloc(sizeof(LWGEOM *) * ngeoms));
178
179 for (uint32_t i = 0; i < ngeoms; i++)
180 {
181 geoms[i] = wgpoly_to_lwgeom(mp[i]);
182 }
183
184 return reinterpret_cast<LWGEOM *>(lwcollection_construct(MULTIPOLYGONTYPE, 0, NULL, ngeoms, geoms));
185 }
186 }
187
188 static LWGEOM *
__lwgeom_wagyu_clip_by_box(const LWGEOM * geom,const GBOX * bbox)189 __lwgeom_wagyu_clip_by_box(const LWGEOM *geom, const GBOX *bbox)
190 {
191 if (!geom || !bbox)
192 {
193 return NULL;
194 }
195
196 if (geom->type != POLYGONTYPE && geom->type != MULTIPOLYGONTYPE)
197 {
198 return NULL;
199 }
200
201 if (lwgeom_is_empty(geom))
202 {
203 LWGEOM *out = lwgeom_construct_empty(MULTIPOLYGONTYPE, geom->srid, 0, 0);
204 out->flags = geom->flags;
205 return out;
206 }
207
208 wagyu_multipolygon solution;
209 vwpolygon vpsubject = lwgeom_to_vwgpoly(geom, bbox);
210 if (vpsubject.size() == 0)
211 {
212 LWGEOM *out = lwgeom_construct_empty(MULTIPOLYGONTYPE, geom->srid, 0, 0);
213 out->flags = geom->flags;
214 return out;
215 }
216
217 wagyu::wagyu<wagyu_coord_type> clipper;
218 for (auto &poly : vpsubject)
219 {
220 for (auto &lr : poly)
221 {
222 if (!lr.empty())
223 {
224 clipper.add_ring(lr, wagyu::polygon_type_subject);
225 }
226 }
227 }
228
229 clipper.execute(wagyu::clip_type_union, solution, wagyu::fill_type_even_odd, wagyu::fill_type_even_odd);
230
231 LWGEOM *g = wgmpoly_to_lwgeom(solution);
232 if (g)
233 g->srid = geom->srid;
234
235 return g;
236 }
237
238 LWGEOM *
lwgeom_wagyu_clip_by_box(const LWGEOM * geom,const GBOX * bbox)239 lwgeom_wagyu_clip_by_box(const LWGEOM *geom, const GBOX *bbox)
240 {
241 mapbox::geometry::wagyu::interrupt_reset();
242 try
243 {
244 return __lwgeom_wagyu_clip_by_box(geom, bbox);
245 }
246 catch (...)
247 {
248 return NULL;
249 }
250 }
251
252 const char *
libwagyu_version()253 libwagyu_version()
254 {
255 static char str[50] = {0};
256 snprintf(
257 str, sizeof(str), "%d.%d.%d (Internal)", WAGYU_MAJOR_VERSION, WAGYU_MINOR_VERSION, WAGYU_PATCH_VERSION);
258
259 return str;
260 }
261
262 void
lwgeom_wagyu_interruptRequest()263 lwgeom_wagyu_interruptRequest()
264 {
265 mapbox::geometry::wagyu::interrupt_request();
266 }
267