- #4388, AddRasterConstraints: Ignore NULLs when generating constraints (Raúl Marín)
- #4327, Avoid pfree'ing the result of getenv (Raúl Marín)
- #4406, Throw on invalid characters when decoding geohash (Raúl Marín)
- - #4372, Avoid resource leaks with PROJ6 (Raúl Marín)
+ - #4429, Avoid resource leaks with PROJ6 (Raúl Marín)
+ - #4372, PROJ6: Speed improvements (Raúl Marín)
PostGIS 3.0.0alpha2
2019/06/02
#define _LIBLWGEOM_H 1
#include <stdarg.h>
-#include <stdio.h>
#include <stdint.h>
+#include <stdio.h>
#include "../postgis_config.h"
projPJ pj_from;
projPJ pj_to;
} PJ;
+
+typedef PJ LWPROJ;
+
#else
#include "proj.h"
+
+/* For PROJ6 we cache several extra values to avoid calls to proj_get_source_crs
+ * or proj_get_target_crs since those are very costly
+ */
+typedef struct LWPROJ
+{
+ PJ* pj;
+ /* CRSs are swapped: Used in transformation calls */
+ uint8_t source_swapped;
+ uint8_t target_swapped;
+ /* Source crs is geographic: Used in geography calls (source srid == dst srid) */
+ uint8_t source_is_latlong;
+
+ /* Source ellipsoid parameters */
+ double source_semi_major_metre;
+ double source_semi_minor_metre;
+} LWPROJ;
#endif
#if POSTGIS_PROJ_VERSION < 49
*
* Eg: "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
*/
-projPJ lwproj_from_string(const char* txt);
+projPJ projpj_from_string(const char* txt);
#endif
/**
* @param geom the geometry to transform
* @param PJ the input and output
*/
-int lwgeom_transform(LWGEOM *geom, PJ* pj);
-int ptarray_transform(POINTARRAY *pa, PJ* pj);
+int lwgeom_transform(LWGEOM *geom, LWPROJ* pj);
+int ptarray_transform(POINTARRAY *pa, LWPROJ* pj);
+
+#if POSTGIS_PROJ_VERSION >= 60
+
+/**
+ * Allocate a new LWPROJ containing the reference to the PROJ's PJ
+ * If extra_geography_data is true, it will generate the following values for
+ * the source srs: is_latlong (geometric or not) and spheroid values
+ */
+LWPROJ *lwproj_from_PJ(PJ *pj, int8_t extra_geography_data);
+
+#endif
/*******************************************************************************
#if POSTGIS_PROJ_VERSION < 60
-
static int
-point4d_transform(POINT4D *pt, PJ* pj)
+point4d_transform(POINT4D *pt, LWPROJ *pj)
{
POINT3D orig_pt = {pt->x, pt->y, pt->z}; /* Copy for error report*/
* from inpj projection to outpj projection
*/
int
-ptarray_transform(POINTARRAY *pa, PJ* pj)
+ptarray_transform(POINTARRAY *pa, LWPROJ *pj)
{
uint32_t i;
POINT4D p;
{
char *pj_errstr;
int rv;
- PJ pj;
+ LWPROJ pj;
- pj.pj_from = lwproj_from_string(instr);
+ pj.pj_from = projpj_from_string(instr);
if (!pj.pj_from)
{
pj_errstr = pj_strerrno(*pj_get_errno_ref());
return LW_FAILURE;
}
- pj.pj_to = lwproj_from_string(outstr);
+ pj.pj_to = projpj_from_string(outstr);
if (!pj.pj_to)
{
pj_free(pj.pj_from);
* from inpj projection to outpj projection
*/
int
-lwgeom_transform(LWGEOM *geom, PJ* pj)
+lwgeom_transform(LWGEOM *geom, LWPROJ *pj)
{
uint32_t i;
}
projPJ
-lwproj_from_string(const char *str1)
+projpj_from_string(const char *str1)
{
if (!str1 || str1[0] == '\0')
{
#else /* POSTGIS_PROJ_VERION >= 60 */
+static uint8_t
+proj_crs_is_swapped(const PJ *pj_crs)
+{
+ PJ *pj_cs;
+ uint8_t rv = LW_FALSE;
+
+ if (proj_get_type(pj_crs) == PJ_TYPE_COMPOUND_CRS)
+ {
+ PJ *pj_horiz_crs = proj_crs_get_sub_crs(NULL, pj_crs, 0);
+ if (!pj_horiz_crs)
+ lwerror("%s: proj_crs_get_sub_crs returned NULL", __func__);
+ pj_cs = proj_crs_get_coordinate_system(NULL, pj_horiz_crs);
+ proj_destroy(pj_horiz_crs);
+ }
+ else if (proj_get_type(pj_crs) == PJ_TYPE_BOUND_CRS)
+ {
+ PJ *pj_src_crs = proj_get_source_crs(NULL, pj_crs);
+ if (!pj_src_crs)
+ lwerror("%s: proj_get_source_crs returned NULL", __func__);
+ pj_cs = proj_crs_get_coordinate_system(NULL, pj_src_crs);
+ proj_destroy(pj_src_crs);
+ }
+ else
+ {
+ pj_cs = proj_crs_get_coordinate_system(NULL, pj_crs);
+ }
+ if (!pj_cs)
+ lwerror("%s: proj_crs_get_coordinate_system returned NULL", __func__);
+ int axis_count = proj_cs_get_axis_count(NULL, pj_cs);
+ if (axis_count > 0)
+ {
+ const char *out_name, *out_abbrev, *out_direction;
+ double out_unit_conv_factor;
+ const char *out_unit_name, *out_unit_auth_name, *out_unit_code;
+ /* Read only first axis, see if it is degrees / north */
+ proj_cs_get_axis_info(NULL,
+ pj_cs,
+ 0,
+ &out_name,
+ &out_abbrev,
+ &out_direction,
+ &out_unit_conv_factor,
+ &out_unit_name,
+ &out_unit_auth_name,
+ &out_unit_code);
+ rv = (strcasecmp(out_direction, "north") == 0);
+ }
+ proj_destroy(pj_cs);
+ return rv;
+}
+
+LWPROJ *
+lwproj_from_PJ(PJ *pj, int8_t extra_geography_data)
+{
+ PJ *pj_source_crs = proj_get_source_crs(NULL, pj);
+ uint8_t source_is_latlong = LW_FALSE;
+ double out_semi_major_metre = DBL_MAX, out_semi_minor_metre = DBL_MAX;
+
+ if (!pj_source_crs)
+ {
+ lwerror("%s: unable to access source crs", __func__);
+ return NULL;
+ }
+ uint8_t source_swapped = proj_crs_is_swapped(pj_source_crs);
+
+ /* We only care about the extra values if there is no transformation */
+ if (!extra_geography_data)
+ {
+ proj_destroy(pj_source_crs);
+ }
+ else
+ {
+ PJ *pj_ellps;
+ double out_inv_flattening;
+ int out_is_semi_minor_computed;
+
+ PJ_TYPE pj_type = proj_get_type(pj_source_crs);
+ if (pj_type == PJ_TYPE_UNKNOWN)
+ {
+ proj_destroy(pj_source_crs);
+ lwerror("%s: unable to access source crs type", __func__);
+ return NULL;
+ }
+ source_is_latlong = (pj_type == PJ_TYPE_GEOGRAPHIC_2D_CRS) || (pj_type == PJ_TYPE_GEOGRAPHIC_3D_CRS);
+
+ pj_ellps = proj_get_ellipsoid(NULL, pj_source_crs);
+ proj_destroy(pj_source_crs);
+ if (!pj_ellps)
+ {
+ lwerror("%s: unable to access source crs ellipsoid", __func__);
+ return NULL;
+ }
+ if (!proj_ellipsoid_get_parameters(NULL,
+ pj_ellps,
+ &out_semi_major_metre,
+ &out_semi_minor_metre,
+ &out_is_semi_minor_computed,
+ &out_inv_flattening))
+ {
+ proj_destroy(pj_ellps);
+ lwerror("%s: unable to access source crs ellipsoid parameters", __func__);
+ return NULL;
+ }
+ proj_destroy(pj_ellps);
+ }
+
+ PJ *pj_target_crs = proj_get_target_crs(NULL, pj);
+ if (!pj_target_crs)
+ {
+ lwerror("%s: unable to access target crs", __func__);
+ return NULL;
+ }
+ uint8_t target_swapped = proj_crs_is_swapped(pj_target_crs);
+ proj_destroy(pj_target_crs);
+
+ LWPROJ *lp = malloc(sizeof(LWPROJ));
+ lp->pj = pj;
+ lp->source_swapped = source_swapped;
+ lp->target_swapped = target_swapped;
+ lp->source_is_latlong = source_is_latlong;
+ lp->source_semi_major_metre = out_semi_major_metre;
+ lp->source_semi_minor_metre = out_semi_minor_metre;
+
+ return lp;
+}
+
int
lwgeom_transform_from_str(LWGEOM *geom, const char* instr, const char* outstr)
{
return LW_FAILURE;
}
- int ret = lwgeom_transform(geom, pj);
+ LWPROJ *lp = lwproj_from_PJ(pj, LW_FALSE);
+
+ int ret = lwgeom_transform(geom, lp);
+
proj_destroy(pj);
+ free(lp);
return ret;
}
int
-lwgeom_transform(LWGEOM* geom, PJ* pj)
+lwgeom_transform(LWGEOM *geom, LWPROJ *pj)
{
uint32_t i;
return LW_SUCCESS;
}
-static int
-proj_crs_is_swapped(const PJ* pj_crs)
-{
- PJ *pj_cs;
- int rv = LW_FALSE;
-
- if (proj_get_type(pj_crs) == PJ_TYPE_COMPOUND_CRS)
- {
- PJ *pj_horiz_crs = proj_crs_get_sub_crs(NULL, pj_crs, 0);
- if (!pj_horiz_crs) lwerror("%s: proj_crs_get_sub_crs returned NULL", __func__);
- pj_cs = proj_crs_get_coordinate_system(NULL, pj_horiz_crs);
- proj_destroy(pj_horiz_crs);
- }
- else if (proj_get_type(pj_crs) == PJ_TYPE_BOUND_CRS)
- {
- PJ *pj_src_crs = proj_get_source_crs(NULL, pj_crs);
- if (!pj_src_crs) lwerror("%s: proj_get_source_crs returned NULL", __func__);
- pj_cs = proj_crs_get_coordinate_system(NULL, pj_src_crs);
- proj_destroy(pj_src_crs);
- }
- else
- {
- pj_cs = proj_crs_get_coordinate_system(NULL, pj_crs);
- }
- if (!pj_cs) lwerror("%s: proj_crs_get_coordinate_system returned NULL", __func__);
- int axis_count = proj_cs_get_axis_count(NULL, pj_cs);
- if (axis_count > 0)
- {
- const char *out_name, *out_abbrev, *out_direction;
- double out_unit_conv_factor;
- const char *out_unit_name, *out_unit_auth_name, *out_unit_code;
- /* Read only first axis, see if it is degrees / north */
- proj_cs_get_axis_info(NULL, pj_cs, 0,
- &out_name,
- &out_abbrev,
- &out_direction,
- &out_unit_conv_factor,
- &out_unit_name,
- &out_unit_auth_name,
- &out_unit_code
- );
- rv = (strcasecmp(out_direction, "north") == 0);
- }
- proj_destroy(pj_cs);
- return rv;
-}
-
-
int
-ptarray_transform(POINTARRAY* pa, PJ* pj)
+ptarray_transform(POINTARRAY *pa, LWPROJ *pj)
{
uint32_t i;
POINT4D p;
size_t point_size = ptarray_point_size(pa);
int has_z = ptarray_has_z(pa);
double *pa_double = (double*)(pa->serialized_pointlist);
- int input_swapped, output_swapped;
-
- PJ* pj_source_crs = proj_get_source_crs(NULL, pj);
- PJ* pj_target_crs = proj_get_target_crs(NULL, pj);
-
- if (!(pj_source_crs && pj_target_crs))
- {
- lwerror("ptarray_transform: unable to access source and target crs");
- return LW_FAILURE;
- }
-
- input_swapped = proj_crs_is_swapped(pj_source_crs);
- output_swapped = proj_crs_is_swapped(pj_target_crs);
- proj_destroy(pj_source_crs);
- proj_destroy(pj_target_crs);
/* Convert to radians if necessary */
- if (proj_angular_input(pj, PJ_FWD))
+ if (proj_angular_input(pj->pj, PJ_FWD)) /* CACHE THIS TOO */
{
for (i = 0; i < pa->npoints; i++)
{
}
}
- if (input_swapped)
+ if (pj->source_swapped)
ptarray_swap_ordinates(pa, LWORD_X, LWORD_Y);
/*
* double *t, size_t st, size_t nt)
*/
- n_converted = proj_trans_generic(
- pj, PJ_FWD,
- pa_double, point_size, n_points, /* X */
- pa_double + 1, point_size, n_points, /* Y */
- has_z ? pa_double + 2 : NULL,
- has_z ? point_size : 0,
- has_z ? n_points : 0, /* Z */
- NULL, 0, 0 /* M */
- );
+ n_converted = proj_trans_generic(pj->pj,
+ PJ_FWD,
+ pa_double,
+ point_size,
+ n_points, /* X */
+ pa_double + 1,
+ point_size,
+ n_points, /* Y */
+ has_z ? pa_double + 2 : NULL,
+ has_z ? point_size : 0,
+ has_z ? n_points : 0, /* Z */
+ NULL,
+ 0,
+ 0 /* M */
+ );
if (n_converted != n_points)
{
return LW_FAILURE;
}
- int pj_errno_val = proj_errno(pj);
+ int pj_errno_val = proj_errno(pj->pj);
if (pj_errno_val)
{
lwerror("transform: %s (%d)",
return LW_FAILURE;
}
- if (output_swapped)
+ if (pj->target_swapped)
ptarray_swap_ordinates(pa, LWORD_X, LWORD_Y);
/* Convert radians to degrees if necessary */
- if (proj_angular_output(pj, PJ_FWD))
+ if (proj_angular_output(pj->pj, PJ_FWD))
{
for (i = 0; i < pa->npoints; i++)
{
{
int32_t srid_from;
int32_t srid_to;
- PJ* projection;
+ LWPROJ *projection;
MemoryContext projection_mcxt;
}
PROJSRSCacheItem;
typedef struct struct_PJHashEntry
{
MemoryContext ProjectionContext;
- PJ* projection;
+ LWPROJ *projection;
}
PJHashEntry;
static HTAB *CreatePJHash(void);
static void DeletePJHashEntry(MemoryContext mcxt);
-static PJ* GetPJHashEntry(MemoryContext mcxt);
-static void AddPJHashEntry(MemoryContext mcxt, PJ* projection);
+static LWPROJ *GetPJHashEntry(MemoryContext mcxt);
+static void AddPJHashEntry(MemoryContext mcxt, LWPROJ *projection);
/* Internal Cache API */
/* static PROJPortalCache *GetPROJSRSCache(FunctionCallInfo fcinfo) ; */
}
static void
-PROJSRSDestroyPJ(PJ* pj)
+PROJSRSDestroyPJ(LWPROJ *pj)
{
#if POSTGIS_PROJ_VERSION < 60
/* Ape the Proj 6+ API for versions < 6 */
pj_free(pj->pj_to);
free(pj);
#else
- proj_destroy(pj);
+ proj_destroy(pj->pj);
+ free(pj);
#endif
}
#endif
/* Lookup the PJ pointer in the global hash table so we can free it */
- PJ* projection = GetPJHashEntry(context);
+ LWPROJ *projection = GetPJHashEntry(context);
if (!projection)
elog(ERROR, "PROJSRSCacheDelete: Trying to delete non-existant projection object with MemoryContext key (%p)", (void *)context);
return hash_create("PostGIS PROJ Backend MemoryContext Hash", PROJ_BACKEND_HASH_SIZE, &ctl, (HASH_ELEM | HASH_FUNCTION));
}
-static void AddPJHashEntry(MemoryContext mcxt, PJ* projection)
+static void
+AddPJHashEntry(MemoryContext mcxt, LWPROJ *projection)
{
bool found;
void **key;
}
}
-static PJ* GetPJHashEntry(MemoryContext mcxt)
+static LWPROJ *
+GetPJHashEntry(MemoryContext mcxt)
{
void **key;
PJHashEntry *he;
return false;
}
-static PJ *
+static LWPROJ *
GetProjectionFromPROJCache(PROJPortalCache *cache, int32_t srid_from, int32_t srid_to)
{
uint32_t i;
PJ* projection = malloc(sizeof(PJ));
pj_from_str = from_strs.proj4text;
pj_to_str = to_strs.proj4text;
- projection->pj_from = lwproj_from_string(pj_from_str);
- projection->pj_to = lwproj_from_string(pj_to_str);
+ projection->pj_from = projpj_from_string(pj_from_str);
+ projection->pj_to = projpj_from_string(pj_to_str);
if (!projection->pj_from)
elog(ERROR,
"could not form projection from 'srid=%d' to 'srid=%d'",
srid_from, srid_to);
#else
- PJ* projection = NULL;
+ PJ *projpj = NULL;
/* Try combinations of ESPG/SRTEXT/PROJ4TEXT until we find */
/* one that gives us a usable transform. Note that we prefer */
/* EPSG numbers over SRTEXT and SRTEXT over PROJ4TEXT */
pj_to_str = pgstrs_get_entry(&to_strs, i % 3);
if (!(pj_from_str && pj_to_str))
continue;
- projection = proj_create_crs_to_crs(NULL, pj_from_str, pj_to_str, NULL);
- if (projection && !proj_errno(projection))
+ projpj = proj_create_crs_to_crs(NULL, pj_from_str, pj_to_str, NULL);
+ if (projpj && !proj_errno(projpj))
break;
}
+ if (!projpj)
+ {
+ elog(ERROR, "could not form projection (PJ) from 'srid=%d' to 'srid=%d'", srid_from, srid_to);
+ return;
+ }
+ LWPROJ *projection = lwproj_from_PJ(projpj, srid_from == srid_to);
if (!projection)
{
- elog(ERROR,
- "could not form projection from 'srid=%d' to 'srid=%d'",
- srid_from, srid_to);
+ elog(ERROR, "could not form projection (LWPROJ) from 'srid=%d' to 'srid=%d'", srid_from, srid_to);
+ return;
}
#endif
}
int
-GetPJUsingFCInfo(FunctionCallInfo fcinfo, int32_t srid_from, int32_t srid_to, PJ **pj)
+GetPJUsingFCInfo(FunctionCallInfo fcinfo, int32_t srid_from, int32_t srid_to, LWPROJ **pj)
{
PROJPortalCache *proj_cache = NULL;
}
static int
-proj_pj_is_latlong(const PJ* pj)
+proj_pj_is_latlong(const LWPROJ *pj)
{
#if POSTGIS_PROJ_VERSION < 60
return pj_is_latlong(pj->pj_from);
#else
- PJ_TYPE pj_type;
- PJ *pj_src_crs = proj_get_source_crs(NULL, pj);
- if (!pj_src_crs)
- elog(ERROR, "%s: proj_get_source_crs returned NULL", __func__);
- pj_type = proj_get_type(pj_src_crs);
- proj_destroy(pj_src_crs);
- return (pj_type == PJ_TYPE_GEOGRAPHIC_2D_CRS) ||
- (pj_type == PJ_TYPE_GEOGRAPHIC_3D_CRS);
+ return pj->source_is_latlong;
#endif
}
static int
srid_is_latlong(FunctionCallInfo fcinfo, int32_t srid)
{
- PJ* pj;
+ LWPROJ *pj;
if ( GetPJUsingFCInfo(fcinfo, srid, srid, &pj) == LW_FAILURE)
return LW_FALSE;
return proj_pj_is_latlong(pj);
int
spheroid_init_from_srid(FunctionCallInfo fcinfo, int32_t srid, SPHEROID *s)
{
- PJ* pj;
-#if POSTGIS_PROJ_VERSION >= 60
- double out_semi_major_metre, out_semi_minor_metre, out_inv_flattening;
- int out_is_semi_minor_computed;
- PJ *pj_ellps, *pj_crs;
-#elif POSTGIS_PROJ_VERSION >= 48
+ LWPROJ *pj;
+#if POSTGIS_PROJ_VERSION >= 48 && POSTGIS_PROJ_VERSION < 60
double major_axis, minor_axis, eccentricity_squared;
#endif
return LW_FAILURE;
#if POSTGIS_PROJ_VERSION >= 60
- if (!proj_pj_is_latlong(pj))
+ if (!pj->source_is_latlong)
return LW_FAILURE;
- pj_crs = proj_get_source_crs(NULL, pj);
- if (!pj_crs)
- {
- lwerror("%s: proj_get_source_crs returned NULL", __func__);
- }
- pj_ellps = proj_get_ellipsoid(NULL, pj_crs);
- if (!pj_ellps)
- {
- proj_destroy(pj_crs);
- lwerror("%s: proj_get_ellipsoid returned NULL", __func__);
- }
- proj_ellipsoid_get_parameters(NULL, pj_ellps,
- &out_semi_major_metre, &out_semi_minor_metre,
- &out_is_semi_minor_computed, &out_inv_flattening);
- proj_destroy(pj_ellps);
- proj_destroy(pj_crs);
- spheroid_init(s, out_semi_major_metre, out_semi_minor_metre);
+ spheroid_init(s, pj->source_semi_major_metre, pj->source_semi_minor_metre);
#elif POSTGIS_PROJ_VERSION >= 48
if (!pj_is_latlong(pj->pj_from))
void SetPROJLibPath(void);
bool IsInPROJCache(ProjCache cache, int32_t srid_from, int32_t srid_to);
PJ *GetPJFromPROJCache(ProjCache cache, int32_t srid_from, int32_t srid_to);
-int GetPJUsingFCInfo(FunctionCallInfo fcinfo, int32_t srid_from, int32_t srid_to, PJ **pj);
+int GetPJUsingFCInfo(FunctionCallInfo fcinfo, int32_t srid_from, int32_t srid_to, LWPROJ **pj);
int spheroid_init_from_srid(FunctionCallInfo fcinfo, int32_t srid, SPHEROID *s);
void srid_check_latlong(FunctionCallInfo fcinfo, int32_t srid);
srs_precision srid_axis_precision(FunctionCallInfo fcinfo, int32_t srid, int precision);
text_in = GetProj4String(srid_in);
text_out = GetProj4String(srid_out);
- pj.pj_from = lwproj_from_string(text_in);
- pj.pj_to = lwproj_from_string(text_out);
+ pj.pj_from = projpj_from_string(text_in);
+ pj.pj_to = projpj_from_string(text_out);
lwfree(text_in);
lwfree(text_out);
gml_reproject_pa(POINTARRAY *pa, int32_t srid_in, int32_t srid_out)
{
PJ *pj;
+ LWPROJ *lwp;
char text_in[32];
char text_out[32];
- if (srid_in == SRID_UNKNOWN) return pa; /* nothing to do */
- if (srid_out == SRID_UNKNOWN) gml_lwpgerror("invalid GML representation", 3);
+ if (srid_in == SRID_UNKNOWN)
+ return pa; /* nothing to do */
+
+ if (srid_out == SRID_UNKNOWN)
+ {
+ gml_lwpgerror("invalid GML representation", 3);
+ return NULL;
+ }
snprintf(text_in, 32, "EPSG:%d", srid_in);
snprintf(text_out, 32, "EPSG:%d", srid_out);
pj = proj_create_crs_to_crs(NULL, text_in, text_out, NULL);
- if (ptarray_transform(pa, pj) == LW_FAILURE)
+ lwp = lwproj_from_PJ(pj, LW_FALSE);
+ if (!lwp)
{
+ proj_destroy(pj);
+ gml_lwpgerror("Could not create LWPROJ*", 57);
+ return NULL;
+ }
+
+ if (ptarray_transform(pa, lwp) == LW_FAILURE)
+ {
+ proj_destroy(pj);
elog(ERROR, "gml_reproject_pa: reprojection failed");
+ return NULL;
}
proj_destroy(pj);
+ free(lwp);
return pa;
}
GSERIALIZED* geom;
GSERIALIZED* result=NULL;
LWGEOM* lwgeom;
- PJ* pj;
+ LWPROJ *pj;
int32 srid_to, srid_from;
srid_to = PG_GETARG_INT32(1);
if (srid_from != srid_to)
{
- PJ* pj;
+ LWPROJ *pj;
if (GetPJUsingFCInfo(fcinfo, srid_from, srid_to, &pj) == LW_FAILURE)
{
PG_FREE_IF_COPY(geom, 0);