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