419 lines
11 KiB
C
419 lines
11 KiB
C
/**********************************************************************
|
|
*
|
|
* PostGIS - Spatial Types for PostgreSQL
|
|
* http://postgis.net
|
|
*
|
|
* PostGIS is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* PostGIS is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
**********************************************************************
|
|
*
|
|
* Copyright 2020 Paul Ramsey <pramsey@cleverelephant.ca>
|
|
*
|
|
**********************************************************************/
|
|
|
|
#include "postgres.h"
|
|
#include "fmgr.h"
|
|
#include "funcapi.h"
|
|
#include "access/htup_details.h"
|
|
|
|
#include "../postgis_config.h"
|
|
#include "lwgeom_pg.h"
|
|
#include "liblwgeom.h"
|
|
|
|
#include <math.h>
|
|
#include <float.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
Datum ST_Hexagon(PG_FUNCTION_ARGS);
|
|
Datum ST_Square(PG_FUNCTION_ARGS);
|
|
Datum ST_ShapeGrid(PG_FUNCTION_ARGS);
|
|
|
|
/* ********* ********* ********* ********* ********* ********* ********* ********* */
|
|
|
|
typedef enum
|
|
{
|
|
SHAPE_SQUARE,
|
|
SHAPE_HEXAGON,
|
|
SHAPE_TRIANGLE
|
|
} GeometryShape;
|
|
|
|
typedef struct GeometryGridState
|
|
{
|
|
GeometryShape cell_shape;
|
|
bool done;
|
|
GBOX bounds;
|
|
int32_t srid;
|
|
double size;
|
|
int32_t i, j;
|
|
}
|
|
GeometryGridState;
|
|
|
|
/* ********* ********* ********* ********* ********* ********* ********* ********* */
|
|
|
|
/* Ratio of hexagon edge length to edge->center vertical distance = cos(M_PI/6.0) */
|
|
#define H 0.8660254037844387
|
|
|
|
/* Build origin hexagon centered around origin point */
|
|
static const double hex_x[] = {-1.0, -0.5, 0.5, 1.0, 0.5, -0.5, -1.0};
|
|
static const double hex_y[] = {0.0, -0.5, -0.5, 0.0, 0.5, 0.5, 0.0};
|
|
|
|
static LWGEOM *
|
|
hexagon(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
|
|
{
|
|
POINTARRAY **ppa = lwalloc(sizeof(POINTARRAY*));
|
|
POINTARRAY *pa = ptarray_construct(0, 0, 7);
|
|
|
|
for (uint32_t i = 0; i < 7; ++i)
|
|
{
|
|
double height = size * 2 * H;
|
|
POINT4D pt;
|
|
pt.x = origin_x + size * (1.5 * cell_i + hex_x[i]);
|
|
pt.y = origin_y + height * (cell_j + 0.5 * (abs(cell_i) % 2) + hex_y[i]);
|
|
ptarray_set_point4d(pa, i, &pt);
|
|
}
|
|
|
|
ppa[0] = pa;
|
|
return lwpoly_as_lwgeom(lwpoly_construct(srid, NULL, 1 /* nrings */, ppa));
|
|
}
|
|
|
|
|
|
typedef struct HexagonGridState
|
|
{
|
|
GeometryShape cell_shape;
|
|
bool done;
|
|
GBOX bounds;
|
|
int32_t srid;
|
|
double size;
|
|
int32_t i, j;
|
|
int32_t column_min, column_max;
|
|
int32_t row_min_odd, row_max_odd;
|
|
int32_t row_min_even, row_max_even;
|
|
}
|
|
HexagonGridState;
|
|
|
|
static HexagonGridState *
|
|
hexagon_grid_state(double size, const GBOX *gbox, int32_t srid)
|
|
{
|
|
HexagonGridState *state = palloc0(sizeof(HexagonGridState));
|
|
double col_width = 1.5 * size;
|
|
double row_height = size * 2 * H;
|
|
|
|
/* fill in state */
|
|
state->cell_shape = SHAPE_HEXAGON;
|
|
state->size = size;
|
|
state->srid = srid;
|
|
state->done = false;
|
|
state->bounds = *gbox;
|
|
|
|
/* Column address is just width / column width, with an */
|
|
/* adjustment to account for partial overlaps */
|
|
state->column_min = floor(gbox->xmin / col_width);
|
|
if(gbox->xmin - state->column_min * col_width > size)
|
|
state->column_min++;
|
|
|
|
state->column_max = ceil(gbox->xmax / col_width);
|
|
if(state->column_max * col_width - gbox->xmax > size)
|
|
state->column_max--;
|
|
|
|
/* Row address range depends on the odd/even column we're on */
|
|
state->row_min_even = floor(gbox->ymin/row_height + 0.5);
|
|
state->row_max_even = floor(gbox->ymax/row_height + 0.5);
|
|
state->row_min_odd = floor(gbox->ymin/row_height);
|
|
state->row_max_odd = floor(gbox->ymax/row_height);
|
|
|
|
/* Set initial state */
|
|
state->i = state->column_min;
|
|
state->j = (state->i % 2) ? state->row_min_odd : state->row_min_even;
|
|
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
hexagon_state_next(HexagonGridState *state)
|
|
{
|
|
if (!state || state->done) return;
|
|
/* Move up one row */
|
|
state->j++;
|
|
/* Off the end, increment column counter, reset row counter back to (appropriate) minimum */
|
|
if (state->j > ((state->i % 2) ? state->row_max_odd : state->row_max_even))
|
|
{
|
|
state->i++;
|
|
state->j = ((state->i % 2) ? state->row_min_odd : state->row_min_even);
|
|
}
|
|
/* Column counter over max means we have used up all combinations */
|
|
if (state->i > state->column_max)
|
|
{
|
|
state->done = true;
|
|
}
|
|
}
|
|
|
|
/* ********* ********* ********* ********* ********* ********* ********* ********* */
|
|
|
|
static LWGEOM *
|
|
square(double origin_x, double origin_y, double size, int cell_i, int cell_j, int32_t srid)
|
|
{
|
|
double ll_x = origin_x + (size * cell_i);
|
|
double ll_y = origin_y + (size * cell_j);
|
|
double ur_x = origin_x + (size * (cell_i + 1));
|
|
double ur_y = origin_y + (size * (cell_j + 1));
|
|
return (LWGEOM*)lwpoly_construct_envelope(srid, ll_x, ll_y, ur_x, ur_y);
|
|
}
|
|
|
|
typedef struct SquareGridState
|
|
{
|
|
GeometryShape cell_shape;
|
|
bool done;
|
|
GBOX bounds;
|
|
int32_t srid;
|
|
double size;
|
|
int32_t i, j;
|
|
int32_t column_min, column_max;
|
|
int32_t row_min, row_max;
|
|
}
|
|
SquareGridState;
|
|
|
|
static SquareGridState *
|
|
square_grid_state(double size, const GBOX *gbox, int32_t srid)
|
|
{
|
|
SquareGridState *state = palloc0(sizeof(SquareGridState));
|
|
|
|
/* fill in state */
|
|
state->cell_shape = SHAPE_SQUARE;
|
|
state->size = size;
|
|
state->srid = srid;
|
|
state->done = false;
|
|
state->bounds = *gbox;
|
|
|
|
state->column_min = floor(gbox->xmin / size);
|
|
state->column_max = floor(gbox->xmax / size);
|
|
state->row_min = floor(gbox->ymin / size);
|
|
state->row_max = floor(gbox->ymax / size);
|
|
state->i = state->column_min;
|
|
state->j = state->row_min;
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
square_state_next(SquareGridState *state)
|
|
{
|
|
if (!state || state->done) return;
|
|
/* Move up one row */
|
|
state->j++;
|
|
/* Off the end, increment column counter, reset row counter back to (appropriate) minimum */
|
|
if (state->j > state->row_max)
|
|
{
|
|
state->i++;
|
|
state->j = state->row_min;
|
|
}
|
|
/* Column counter over max means we have used up all combinations */
|
|
if (state->i > state->column_max)
|
|
{
|
|
state->done = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ST_HexagonGrid(size float8, bounds geometry)
|
|
* ST_SquareGrid(size float8, bounds geometry)
|
|
*/
|
|
PG_FUNCTION_INFO_V1(ST_ShapeGrid);
|
|
Datum ST_ShapeGrid(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
|
|
GSERIALIZED *gbounds;
|
|
GeometryGridState *state;
|
|
|
|
LWGEOM *lwgeom;
|
|
bool isnull[3] = {0,0,0}; /* needed to say no value is null */
|
|
Datum tuple_arr[3]; /* used to construct the composite return value */
|
|
HeapTuple tuple;
|
|
Datum result; /* the actual composite return value */
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
MemoryContext oldcontext;
|
|
const char *func_name;
|
|
char gbounds_is_empty;
|
|
GBOX bounds;
|
|
double size;
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
|
|
/* Get a local copy of the bounds we are going to fill with shapes */
|
|
gbounds = PG_GETARG_GSERIALIZED_P(1);
|
|
size = PG_GETARG_FLOAT8(0);
|
|
|
|
gbounds_is_empty = (gserialized_get_gbox_p(gbounds, &bounds) == LW_FAILURE);
|
|
|
|
/* quick opt-out if we get nonsensical inputs */
|
|
if (size <= 0.0 || gbounds_is_empty)
|
|
{
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|
|
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
/*
|
|
* Support both hexagon and square grids with one function,
|
|
* by checking the calling signature up front.
|
|
*/
|
|
func_name = get_func_name(fcinfo->flinfo->fn_oid);
|
|
if (strcmp(func_name, "st_hexagongrid") == 0)
|
|
{
|
|
state = (GeometryGridState*)hexagon_grid_state(size, &bounds, gserialized_get_srid(gbounds));
|
|
}
|
|
else if (strcmp(func_name, "st_squaregrid") == 0)
|
|
{
|
|
state = (GeometryGridState*)square_grid_state(size, &bounds, gserialized_get_srid(gbounds));
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("%s called from unsupported functional context '%s'", __func__, func_name)));
|
|
}
|
|
|
|
funcctx->user_fctx = state;
|
|
|
|
/* get tuple description for return type */
|
|
if (get_call_result_type(fcinfo, 0, &funcctx->tuple_desc) != TYPEFUNC_COMPOSITE)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("set-valued function called in context that cannot accept a set")));
|
|
}
|
|
|
|
BlessTupleDesc(funcctx->tuple_desc);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
/* stuff done on every call of the function */
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
|
|
/* get state */
|
|
state = funcctx->user_fctx;
|
|
|
|
/* Stop when we've used up all the grid squares */
|
|
if (state->done)
|
|
{
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|
|
|
|
/* Store grid cell coordinates */
|
|
tuple_arr[1] = Int32GetDatum(state->i);
|
|
tuple_arr[2] = Int32GetDatum(state->j);
|
|
|
|
/* Generate geometry */
|
|
switch (state->cell_shape)
|
|
{
|
|
case SHAPE_HEXAGON:
|
|
lwgeom = hexagon(0.0, 0.0, state->size, state->i, state->j, state->srid);
|
|
/* Increment to next cell */
|
|
hexagon_state_next((HexagonGridState*)state);
|
|
break;
|
|
case SHAPE_SQUARE:
|
|
lwgeom = square(0.0, 0.0, state->size, state->i, state->j, state->srid);
|
|
/* Increment to next cell */
|
|
square_state_next((SquareGridState*)state);
|
|
break;
|
|
default:
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("%s called from with unsupported shape '%d'", __func__, state->cell_shape)));
|
|
}
|
|
|
|
tuple_arr[0] = PointerGetDatum(geometry_serialize(lwgeom));
|
|
lwfree(lwgeom);
|
|
|
|
/* Form tuple and return */
|
|
tuple = heap_form_tuple(funcctx->tuple_desc, tuple_arr, isnull);
|
|
result = HeapTupleGetDatum(tuple);
|
|
SRF_RETURN_NEXT(funcctx, result);
|
|
}
|
|
|
|
/**
|
|
* ST_Hexagon(size double, cell_i integer default 0, cell_j integer default 0, origin geometry default 'POINT(0 0)')
|
|
*/
|
|
PG_FUNCTION_INFO_V1(ST_Hexagon);
|
|
Datum ST_Hexagon(PG_FUNCTION_ARGS)
|
|
{
|
|
LWPOINT *lwpt;
|
|
double size = PG_GETARG_FLOAT8(0);
|
|
GSERIALIZED *ghex;
|
|
int cell_i = PG_GETARG_INT32(1);
|
|
int cell_j = PG_GETARG_INT32(2);
|
|
GSERIALIZED *gorigin = PG_GETARG_GSERIALIZED_P(3);
|
|
LWGEOM *lwhex;
|
|
LWGEOM *lworigin = lwgeom_from_gserialized(gorigin);
|
|
|
|
if (lwgeom_is_empty(lworigin))
|
|
{
|
|
elog(ERROR, "%s: origin point is empty", __func__);
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
lwpt = lwgeom_as_lwpoint(lworigin);
|
|
if (!lwpt)
|
|
{
|
|
elog(ERROR, "%s: origin argument is not a point", __func__);
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
lwhex = hexagon(lwpoint_get_x(lwpt), lwpoint_get_y(lwpt),
|
|
size, cell_i, cell_j,
|
|
lwgeom_get_srid(lworigin));
|
|
|
|
ghex = geometry_serialize(lwhex);
|
|
PG_FREE_IF_COPY(gorigin, 3);
|
|
PG_RETURN_POINTER(ghex);
|
|
}
|
|
|
|
|
|
/**
|
|
* ST_Square(size double, cell_i integer, cell_j integer, origin geometry default 'POINT(0 0)')
|
|
*/
|
|
PG_FUNCTION_INFO_V1(ST_Square);
|
|
Datum ST_Square(PG_FUNCTION_ARGS)
|
|
{
|
|
LWPOINT *lwpt;
|
|
double size = PG_GETARG_FLOAT8(0);
|
|
GSERIALIZED *gsqr;
|
|
int cell_i = PG_GETARG_INT32(1);
|
|
int cell_j = PG_GETARG_INT32(2);
|
|
GSERIALIZED *gorigin = PG_GETARG_GSERIALIZED_P(3);
|
|
LWGEOM *lwsqr;
|
|
LWGEOM *lworigin = lwgeom_from_gserialized(gorigin);
|
|
|
|
if (lwgeom_is_empty(lworigin))
|
|
{
|
|
elog(ERROR, "%s: origin point is empty", __func__);
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
lwpt = lwgeom_as_lwpoint(lworigin);
|
|
if (!lwpt)
|
|
{
|
|
elog(ERROR, "%s: origin argument is not a point", __func__);
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
lwsqr = square(lwpoint_get_x(lwpt), lwpoint_get_y(lwpt),
|
|
size, cell_i, cell_j,
|
|
lwgeom_get_srid(lworigin));
|
|
|
|
gsqr = geometry_serialize(lwsqr);
|
|
PG_FREE_IF_COPY(gorigin, 3);
|
|
PG_RETURN_POINTER(gsqr);
|
|
}
|