PostGIS  2.5.1dev-r@@SVN_REVISION@@

◆ mvt_geom()

LWGEOM* mvt_geom ( LWGEOM lwgeom,
const GBOX gbox,
uint32_t  extent,
uint32_t  buffer,
bool  clip_geom 
)

Transform a geometry into vector tile coordinate space.

Makes best effort to keep validity. Might collapse geometry into lower dimension.

NOTE: modifies in place if possible (not currently possible for polygons)

Definition at line 788 of file mvt.c.

References AFFINE::afac, buffer(), COLLECTIONTYPE, AFFINE::efac, gbox_contains_2d(), gbox_expand(), gbox_overlaps_2d(), AFFINE::ifac, gridspec_t::ipx, gridspec_t::ipy, LW_TRUE, lwgeom_affine(), lwgeom_clip_by_rect(), lwgeom_force_clockwise(), lwgeom_free(), lwgeom_get_basic_type(), lwgeom_get_bbox(), lwgeom_grid_in_place(), lwgeom_is_empty(), lwgeom_make_valid(), lwgeom_remove_repeated_points_in_place(), lwgeom_reverse_in_place(), lwgeom_simplify_in_place(), lwgeom_to_basic_type(), POLYGONTYPE, window::res, LWGEOM::type, GBOX::xmax, GBOX::xmin, AFFINE::xoff, gridspec_t::xsize, GBOX::ymax, GBOX::ymin, AFFINE::yoff, and gridspec_t::ysize.

Referenced by ST_AsMVTGeom().

790 {
791  AFFINE affine;
792  gridspec grid;
793  double width = gbox->xmax - gbox->xmin;
794  double height = gbox->ymax - gbox->ymin;
795  double resx, resy, res, fx, fy;
796  int preserve_collapsed = LW_TRUE;
797  const uint8_t basic_type = lwgeom_get_basic_type(lwgeom);
798  POSTGIS_DEBUG(2, "mvt_geom called");
799 
800  /* Short circuit out on EMPTY */
801  if (lwgeom_is_empty(lwgeom))
802  return NULL;
803 
804  if (width == 0 || height == 0)
805  elog(ERROR, "mvt_geom: bounds width or height cannot be 0");
806 
807  if (extent == 0)
808  elog(ERROR, "mvt_geom: extent cannot be 0");
809 
810  resx = width / extent;
811  resy = height / extent;
812  res = (resx < resy ? resx : resy)/2;
813  fx = extent / width;
814  fy = -(extent / height);
815 
816  /* Remove all non-essential points (under the output resolution) */
818  lwgeom_simplify_in_place(lwgeom, res, preserve_collapsed);
819 
820  /* If geometry has disappeared, you're done */
821  if (lwgeom_is_empty(lwgeom))
822  return NULL;
823 
824  if (clip_geom)
825  {
826  // We need to add an extra half pixel to include the points that
827  // fall into the bbox only after the coordinate transformation
828  double buffer_map_xunits = nextafterf(res, 0.0) + resx * buffer;
829  GBOX bgbox;
830  const GBOX *lwgeom_gbox = lwgeom_get_bbox(lwgeom);
831  bgbox = *gbox;
832  gbox_expand(&bgbox, buffer_map_xunits);
833  if (!gbox_overlaps_2d(lwgeom_gbox, &bgbox))
834  {
835  POSTGIS_DEBUG(3, "mvt_geom: geometry outside clip box");
836  return NULL;
837  }
838  if (!gbox_contains_2d(&bgbox, lwgeom_gbox))
839  {
840  double x0 = bgbox.xmin;
841  double y0 = bgbox.ymin;
842  double x1 = bgbox.xmax;
843  double y1 = bgbox.ymax;
844  const GBOX pre_clip_box = *lwgeom_get_bbox(lwgeom);
845  LWGEOM *clipped_geom = lwgeom_clip_by_rect(lwgeom, x0, y0, x1, y1);
846  if (clipped_geom == NULL || lwgeom_is_empty(clipped_geom))
847  {
848  POSTGIS_DEBUG(3, "mvt_geom: no geometry after clip");
849  return NULL;
850  }
851  /* For some polygons, the simplify step might have left them
852  * as invalid, which can cause clipping to return the complementary
853  * geometry of what it should */
854  if ((basic_type == POLYGONTYPE) &&
855  !gbox_contains_2d(&pre_clip_box, lwgeom_get_bbox(clipped_geom)))
856  {
857  /* TODO: Adapt this when and if Exception Policies are introduced.
858  * Other options would be to fix the geometry and retry
859  * or to calculate the difference between the 2 boxes.
860  */
861  POSTGIS_DEBUG(3, "mvt_geom: Invalid geometry after clipping");
862  lwgeom_free(clipped_geom);
863  return NULL;
864  }
865  lwgeom = clipped_geom;
866  }
867  }
868 
869  /* transform to tile coordinate space */
870  memset(&affine, 0, sizeof(affine));
871  affine.afac = fx;
872  affine.efac = fy;
873  affine.ifac = 1;
874  affine.xoff = -gbox->xmin * fx;
875  affine.yoff = -gbox->ymax * fy;
876  lwgeom_affine(lwgeom, &affine);
877 
878  /* snap to integer precision, removing duplicate points */
879  memset(&grid, 0, sizeof(gridspec));
880  grid.ipx = 0;
881  grid.ipy = 0;
882  grid.xsize = 1;
883  grid.ysize = 1;
884  lwgeom_grid_in_place(lwgeom, &grid);
885 
886  if (lwgeom == NULL || lwgeom_is_empty(lwgeom))
887  return NULL;
888 
889 
890  if (basic_type == POLYGONTYPE)
891  {
892  /* Force validation as per MVT spec */
893  lwgeom = lwgeom_make_valid(lwgeom);
894 
895  /* In image coordinates CW actually comes out a CCW, so we reverse */
896  lwgeom_force_clockwise(lwgeom);
897  lwgeom_reverse_in_place(lwgeom);
898  }
899 
900  /* if geometry collection extract highest dimensional geometry type */
901  if (lwgeom->type == COLLECTIONTYPE)
902  lwgeom_to_basic_type(lwgeom, basic_type);
903 
904  if (basic_type != lwgeom_get_basic_type(lwgeom))
905  {
906  /* Drop type changes to play nice with MVT renderers */
907  POSTGIS_DEBUG(3, "mvt_geom: Dropping geometry after type change");
908  return NULL;
909  }
910 
911  if (lwgeom == NULL || lwgeom_is_empty(lwgeom))
912  return NULL;
913 
914  return lwgeom;
915 }
void lwgeom_remove_repeated_points_in_place(LWGEOM *in, double tolerance)
Definition: lwgeom.c:1603
tuple res
Definition: window.py:78
void gbox_expand(GBOX *g, double d)
Move the box minimums down and the maximums up by the distance provided.
Definition: g_box.c:104
void lwgeom_reverse_in_place(LWGEOM *lwgeom)
Reverse vertex order of LWGEOM.
Definition: lwgeom.c:102
#define POLYGONTYPE
Definition: liblwgeom.h:86
double xmax
Definition: liblwgeom.h:295
void lwgeom_free(LWGEOM *geom)
Definition: lwgeom.c:1144
LWGEOM * lwgeom_clip_by_rect(const LWGEOM *geom1, double x0, double y0, double x1, double y1)
int gbox_contains_2d(const GBOX *g1, const GBOX *g2)
Return LW_TRUE if the first GBOX contains the second on the 2d plane, LW_FALSE otherwise.
Definition: g_box.c:346
double ifac
Definition: liblwgeom.h:272
double xoff
Definition: liblwgeom.h:272
double afac
Definition: liblwgeom.h:272
void lwgeom_simplify_in_place(LWGEOM *igeom, double dist, int preserve_collapsed)
Definition: lwgeom.c:1748
void lwgeom_force_clockwise(LWGEOM *lwgeom)
Force Right-hand-rule on LWGEOM polygons.
Definition: lwgeom.c:37
Datum buffer(PG_FUNCTION_ARGS)
double ymin
Definition: liblwgeom.h:296
double xmin
Definition: liblwgeom.h:294
LWGEOM * lwgeom_make_valid(LWGEOM *geom)
Attempts to make an invalid geometries valid w/out losing points.
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:75
const GBOX * lwgeom_get_bbox(const LWGEOM *lwgeom)
Get a non-empty geometry bounding box, computing and caching it if not already there.
Definition: lwgeom.c:734
double ymax
Definition: liblwgeom.h:297
int gbox_overlaps_2d(const GBOX *g1, const GBOX *g2)
Return LW_TRUE if the GBOX overlaps on the 2d plane, LW_FALSE otherwise.
Definition: g_box.c:330
static uint8 lwgeom_get_basic_type(LWGEOM *geom)
Definition: mvt.c:733
double efac
Definition: liblwgeom.h:272
void lwgeom_grid_in_place(LWGEOM *lwgeom, const gridspec *grid)
Definition: lwgeom.c:2145
uint8_t type
Definition: liblwgeom.h:398
double yoff
Definition: liblwgeom.h:272
int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members) ...
Definition: lwgeom.c:1393
unsigned char uint8_t
Definition: uthash.h:79
static void lwgeom_to_basic_type(LWGEOM *geom, uint8 original_type)
In place process a collection to find a concrete geometry object and expose that as the actual object...
Definition: mvt.c:770
#define COLLECTIONTYPE
Definition: liblwgeom.h:90
void lwgeom_affine(LWGEOM *geom, const AFFINE *affine)
Definition: lwgeom.c:1969
Snap to grid.
Here is the call graph for this function:
Here is the caller graph for this function: