PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_generate_grid.c
Go to the documentation of this file.
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 2020 Paul Ramsey <pramsey@cleverelephant.ca>
22 *
23 **********************************************************************/
24
25#include "postgres.h"
26#include "fmgr.h"
27#include "funcapi.h"
28#include "access/htup_details.h"
29
30#include "../postgis_config.h"
31#include "lwgeom_pg.h"
32#include "liblwgeom.h"
33
34#include <math.h>
35#include <float.h>
36#include <string.h>
37#include <stdio.h>
38
39
40Datum ST_Hexagon(PG_FUNCTION_ARGS);
41Datum ST_Square(PG_FUNCTION_ARGS);
42Datum ST_ShapeGrid(PG_FUNCTION_ARGS);
43
44/* ********* ********* ********* ********* ********* ********* ********* ********* */
45
52
53typedef struct GeometryGridState
54{
56 bool done;
58 int32_t srid;
59 double size;
60 int32_t i, j;
61}
63
64/* ********* ********* ********* ********* ********* ********* ********* ********* */
65
66/* Ratio of hexagon edge length to edge->center vertical distance = cos(M_PI/6.0) */
67#define H 0.8660254037844387
68
69/* Build origin hexagon centered around origin point */
70static const double hex_x[] = {-1.0, -0.5, 0.5, 1.0, 0.5, -0.5, -1.0};
71static const double hex_y[] = {0.0, -0.5, -0.5, 0.0, 0.5, 0.5, 0.0};
72
73static LWGEOM *
74hexagon(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
75{
76 POINTARRAY **ppa = lwalloc(sizeof(POINTARRAY*));
77 POINTARRAY *pa = ptarray_construct(0, 0, 7);
78
79 for (uint32_t i = 0; i < 7; ++i)
80 {
81 double height = size * 2 * H;
82 POINT4D pt;
83 pt.x = origin_x + size * (1.5 * cell_i + hex_x[i]);
84 pt.y = origin_y + height * (cell_j + 0.5 * (abs(cell_i) % 2) + hex_y[i]);
85 ptarray_set_point4d(pa, i, &pt);
86 }
87
88 ppa[0] = pa;
89 return lwpoly_as_lwgeom(lwpoly_construct(srid, NULL, 1 /* nrings */, ppa));
90}
91
92
106
107static HexagonGridState *
108hexagon_grid_state(double size, const GBOX *gbox, int32_t srid)
109{
110 HexagonGridState *state = palloc0(sizeof(HexagonGridState));
111 double col_width = 1.5 * size;
112 double row_height = size * 2 * H;
113
114 /* fill in state */
115 state->cell_shape = SHAPE_HEXAGON;
116 state->size = size;
117 state->srid = srid;
118 state->done = false;
119 state->bounds = *gbox;
120
121 /* Column address is just width / column width, with an */
122 /* adjustment to account for partial overlaps */
123 state->column_min = floor(gbox->xmin / col_width);
124 if(gbox->xmin - state->column_min * col_width > size)
125 state->column_min++;
126
127 state->column_max = ceil(gbox->xmax / col_width);
128 if(state->column_max * col_width - gbox->xmax > size)
129 state->column_max--;
130
131 /* Row address range depends on the odd/even column we're on */
132 state->row_min_even = floor(gbox->ymin/row_height + 0.5);
133 state->row_max_even = floor(gbox->ymax/row_height + 0.5);
134 state->row_min_odd = floor(gbox->ymin/row_height);
135 state->row_max_odd = floor(gbox->ymax/row_height);
136
137 /* Set initial state */
138 state->i = state->column_min;
139 state->j = (state->i % 2) ? state->row_min_odd : state->row_min_even;
140
141 return state;
142}
143
144static void
146{
147 if (!state || state->done) return;
148 /* Move up one row */
149 state->j++;
150 /* Off the end, increment column counter, reset row counter back to (appropriate) minimum */
151 if (state->j > ((state->i % 2) ? state->row_max_odd : state->row_max_even))
152 {
153 state->i++;
154 state->j = ((state->i % 2) ? state->row_min_odd : state->row_min_even);
155 }
156 /* Column counter over max means we have used up all combinations */
157 if (state->i > state->column_max)
158 {
159 state->done = true;
160 }
161}
162
163/* ********* ********* ********* ********* ********* ********* ********* ********* */
164
165static LWGEOM *
166square(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
167{
168 double ll_x = origin_x + (size * cell_i);
169 double ll_y = origin_y + (size * cell_j);
170 double ur_x = origin_x + (size * (cell_i + 1));
171 double ur_y = origin_y + (size * (cell_j + 1));
172 return (LWGEOM*)lwpoly_construct_envelope(srid, ll_x, ll_y, ur_x, ur_y);
173}
174
175typedef struct SquareGridState
176{
178 bool done;
180 int32_t srid;
181 double size;
182 int32_t i, j;
184 int32_t row_min, row_max;
185}
187
188static SquareGridState *
189square_grid_state(double size, const GBOX *gbox, int32_t srid)
190{
191 SquareGridState *state = palloc0(sizeof(SquareGridState));
192
193 /* fill in state */
194 state->cell_shape = SHAPE_SQUARE;
195 state->size = size;
196 state->srid = srid;
197 state->done = false;
198 state->bounds = *gbox;
199
200 state->column_min = floor(gbox->xmin / size);
201 state->column_max = floor(gbox->xmax / size);
202 state->row_min = floor(gbox->ymin / size);
203 state->row_max = floor(gbox->ymax / size);
204 state->i = state->column_min;
205 state->j = state->row_min;
206 return state;
207}
208
209static void
211{
212 if (!state || state->done) return;
213 /* Move up one row */
214 state->j++;
215 /* Off the end, increment column counter, reset row counter back to (appropriate) minimum */
216 if (state->j > state->row_max)
217 {
218 state->i++;
219 state->j = state->row_min;
220 }
221 /* Column counter over max means we have used up all combinations */
222 if (state->i > state->column_max)
223 {
224 state->done = true;
225 }
226}
227
233Datum ST_ShapeGrid(PG_FUNCTION_ARGS)
234{
235 FuncCallContext *funcctx;
236
237 GSERIALIZED *gbounds;
238 GeometryGridState *state;
239
240 LWGEOM *lwgeom;
241 bool isnull[3] = {0,0,0}; /* needed to say no value is null */
242 Datum tuple_arr[3]; /* used to construct the composite return value */
243 HeapTuple tuple;
244 Datum result; /* the actual composite return value */
245
246 if (SRF_IS_FIRSTCALL())
247 {
248 MemoryContext oldcontext;
249 const char *func_name;
250 char gbounds_is_empty;
251 GBOX bounds;
252 double size;
253 funcctx = SRF_FIRSTCALL_INIT();
254
255 /* Get a local copy of the bounds we are going to fill with shapes */
256 gbounds = PG_GETARG_GSERIALIZED_P(1);
257 size = PG_GETARG_FLOAT8(0);
258
259 gbounds_is_empty = (gserialized_get_gbox_p(gbounds, &bounds) == LW_FAILURE);
260
261 /* quick opt-out if we get nonsensical inputs */
262 if (size <= 0.0 || gbounds_is_empty)
263 {
264 funcctx = SRF_PERCALL_SETUP();
265 SRF_RETURN_DONE(funcctx);
266 }
267
268 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
269
270 /*
271 * Support both hexagon and square grids with one function,
272 * by checking the calling signature up front.
273 */
274 func_name = get_func_name(fcinfo->flinfo->fn_oid);
275 if (strcmp(func_name, "st_hexagongrid") == 0)
276 {
277 state = (GeometryGridState*)hexagon_grid_state(size, &bounds, gserialized_get_srid(gbounds));
278 }
279 else if (strcmp(func_name, "st_squaregrid") == 0)
280 {
281 state = (GeometryGridState*)square_grid_state(size, &bounds, gserialized_get_srid(gbounds));
282 }
283 else
284 {
285 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
286 errmsg("%s called from unsupported functional context '%s'", __func__, func_name)));
287 }
288
289 funcctx->user_fctx = state;
290
291 /* get tuple description for return type */
292 if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE)
293 {
294 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
295 errmsg("set-valued function called in context that cannot accept a set")));
296 }
297
298 BlessTupleDesc(funcctx->tuple_desc);
299 MemoryContextSwitchTo(oldcontext);
300 }
301
302 /* stuff done on every call of the function */
303 funcctx = SRF_PERCALL_SETUP();
304
305 /* get state */
306 state = funcctx->user_fctx;
307
308 /* Stop when we've used up all the grid squares */
309 if (state->done)
310 {
311 SRF_RETURN_DONE(funcctx);
312 }
313
314 /* Store grid cell coordinates */
315 tuple_arr[1] = Int32GetDatum(state->i);
316 tuple_arr[2] = Int32GetDatum(state->j);
317
318 /* Generate geometry */
319 switch (state->cell_shape)
320 {
321 case SHAPE_HEXAGON:
322 lwgeom = hexagon(0.0, 0.0, state->size, state->i, state->j, state->srid);
323 /* Increment to next cell */
325 break;
326 case SHAPE_SQUARE:
327 lwgeom = square(0.0, 0.0, state->size, state->i, state->j, state->srid);
328 /* Increment to next cell */
330 break;
331 default:
332 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
333 errmsg("%s called from with unsupported shape '%d'", __func__, state->cell_shape)));
334 }
335
336 tuple_arr[0] = PointerGetDatum(geometry_serialize(lwgeom));
337 lwfree(lwgeom);
338
339 /* Form tuple and return */
340 tuple = heap_form_tuple(funcctx->tuple_desc, tuple_arr, isnull);
341 result = HeapTupleGetDatum(tuple);
342 SRF_RETURN_NEXT(funcctx, result);
343}
344
349Datum ST_Hexagon(PG_FUNCTION_ARGS)
350{
351 LWPOINT *lwpt;
352 double size = PG_GETARG_FLOAT8(0);
353 GSERIALIZED *ghex;
354 int cell_i = PG_GETARG_INT32(1);
355 int cell_j = PG_GETARG_INT32(2);
356 GSERIALIZED *gorigin = PG_GETARG_GSERIALIZED_P(3);
357 LWGEOM *lwhex;
358 LWGEOM *lworigin = lwgeom_from_gserialized(gorigin);
359
360 if (lwgeom_is_empty(lworigin))
361 {
362 elog(ERROR, "%s: origin point is empty", __func__);
363 PG_RETURN_NULL();
364 }
365
366 lwpt = lwgeom_as_lwpoint(lworigin);
367 if (!lwpt)
368 {
369 elog(ERROR, "%s: origin argument is not a point", __func__);
370 PG_RETURN_NULL();
371 }
372
373 lwhex = hexagon(lwpoint_get_x(lwpt), lwpoint_get_y(lwpt),
374 size, cell_i, cell_j,
375 lwgeom_get_srid(lworigin));
376
377 ghex = geometry_serialize(lwhex);
378 PG_FREE_IF_COPY(gorigin, 3);
379 PG_RETURN_POINTER(ghex);
380}
381
382
387Datum ST_Square(PG_FUNCTION_ARGS)
388{
389 LWPOINT *lwpt;
390 double size = PG_GETARG_FLOAT8(0);
391 GSERIALIZED *gsqr;
392 int cell_i = PG_GETARG_INT32(1);
393 int cell_j = PG_GETARG_INT32(2);
394 GSERIALIZED *gorigin = PG_GETARG_GSERIALIZED_P(3);
395 LWGEOM *lwsqr;
396 LWGEOM *lworigin = lwgeom_from_gserialized(gorigin);
397
398 if (lwgeom_is_empty(lworigin))
399 {
400 elog(ERROR, "%s: origin point is empty", __func__);
401 PG_RETURN_NULL();
402 }
403
404 lwpt = lwgeom_as_lwpoint(lworigin);
405 if (!lwpt)
406 {
407 elog(ERROR, "%s: origin argument is not a point", __func__);
408 PG_RETURN_NULL();
409 }
410
411 lwsqr = square(lwpoint_get_x(lwpt), lwpoint_get_y(lwpt),
412 size, cell_i, cell_j,
413 lwgeom_get_srid(lworigin));
414
415 gsqr = geometry_serialize(lwsqr);
416 PG_FREE_IF_COPY(gorigin, 3);
417 PG_RETURN_POINTER(gsqr);
418}
char result[OUT_DOUBLE_BUFFER_SIZE]
Definition cu_print.c:267
int32_t gserialized_get_srid(const GSERIALIZED *g)
Extract the SRID from the serialized form (it is packed into three bytes so this is a handy function)...
LWGEOM * lwgeom_from_gserialized(const GSERIALIZED *g)
Allocate a new LWGEOM from a GSERIALIZED.
int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *gbox)
Read the box from the GSERIALIZED or calculate it if necessary.
Definition gserialized.c:94
int32_t lwgeom_get_srid(const LWGEOM *geom)
Return SRID number.
Definition lwgeom.c:955
#define LW_FAILURE
Definition liblwgeom.h:96
double lwpoint_get_x(const LWPOINT *point)
Definition lwpoint.c:63
LWPOLY * lwpoly_construct_envelope(int32_t srid, double x1, double y1, double x2, double y2)
Definition lwpoly.c:98
void * lwalloc(size_t size)
Definition lwutil.c:227
void lwfree(void *mem)
Definition lwutil.c:248
LWPOLY * lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
Definition lwpoly.c:43
void ptarray_set_point4d(POINTARRAY *pa, uint32_t n, const POINT4D *p4d)
Definition lwgeom_api.c:369
LWGEOM * lwpoly_as_lwgeom(const LWPOLY *obj)
Definition lwgeom.c:357
double lwpoint_get_y(const LWPOINT *point)
Definition lwpoint.c:76
POINTARRAY * ptarray_construct(char hasz, char hasm, uint32_t npoints)
Construct an empty pointarray, allocating storage and setting the npoints, but not filling in any inf...
Definition ptarray.c:51
This library is the generic geometry handling section of PostGIS.
static LWGEOM * square(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
static HexagonGridState * hexagon_grid_state(double size, const GBOX *gbox, int32_t srid)
static SquareGridState * square_grid_state(double size, const GBOX *gbox, int32_t srid)
@ SHAPE_SQUARE
@ SHAPE_TRIANGLE
@ SHAPE_HEXAGON
static void square_state_next(SquareGridState *state)
static void hexagon_state_next(HexagonGridState *state)
PG_FUNCTION_INFO_V1(ST_ShapeGrid)
ST_HexagonGrid(size float8, bounds geometry) ST_SquareGrid(size float8, bounds geometry)
static const double hex_x[]
static const double hex_y[]
Datum ST_Square(PG_FUNCTION_ARGS)
Datum ST_ShapeGrid(PG_FUNCTION_ARGS)
#define H
static LWGEOM * hexagon(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
Datum ST_Hexagon(PG_FUNCTION_ARGS)
static LWPOINT * lwgeom_as_lwpoint(const LWGEOM *lwgeom)
Definition lwinline.h:127
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition lwinline.h:199
double ymax
Definition liblwgeom.h:357
double xmax
Definition liblwgeom.h:355
double ymin
Definition liblwgeom.h:356
double xmin
Definition liblwgeom.h:354
double x
Definition liblwgeom.h:414
double y
Definition liblwgeom.h:414