postgis/postgis/geography_inout.c

589 lines
15 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 2009-2011 Paul Ramsey <pramsey@cleverelephant.ca>
*
**********************************************************************/
#include "postgres.h"
#include "../postgis_config.h"
#include <math.h>
#include <float.h>
#include <string.h>
#include <stdio.h>
#include "utils/elog.h"
#include "utils/array.h"
#include "utils/builtins.h" /* for text_to_cstring */
#include "lib/stringinfo.h" /* For binary input */
#include "catalog/pg_type.h" /* for CSTRINGOID, INT4OID */
#include "liblwgeom.h" /* For standard geometry types. */
#include "liblwgeom_internal.h"
#include "lwgeom_cache.h"
#include "lwgeom_pg.h" /* For debugging macros. */
#include "geography.h" /* For utility functions. */
#include "lwgeom_transform.h"
Datum geography_in(PG_FUNCTION_ARGS);
Datum geography_out(PG_FUNCTION_ARGS);
Datum geography_as_text(PG_FUNCTION_ARGS);
Datum geography_from_text(PG_FUNCTION_ARGS);
Datum geography_as_geojson(PG_FUNCTION_ARGS);
Datum geography_as_gml(PG_FUNCTION_ARGS);
Datum geography_as_kml(PG_FUNCTION_ARGS);
Datum geography_as_svg(PG_FUNCTION_ARGS);
Datum geography_from_binary(PG_FUNCTION_ARGS);
Datum geography_from_geometry(PG_FUNCTION_ARGS);
Datum geometry_from_geography(PG_FUNCTION_ARGS);
Datum geography_send(PG_FUNCTION_ARGS);
Datum geography_recv(PG_FUNCTION_ARGS);
GSERIALIZED* gserialized_geography_from_lwgeom(LWGEOM *lwgeom, int32 geog_typmod);
/**
* The geography type only support POINT, LINESTRING, POLYGON, MULTI* variants
* of same, and GEOMETRYCOLLECTION. If the input type is not one of those, shut
* down the query.
*/
void geography_valid_type(uint8_t type)
{
if ( ! (
type == POINTTYPE ||
type == LINETYPE ||
type == POLYGONTYPE ||
type == MULTIPOINTTYPE ||
type == MULTILINETYPE ||
type == MULTIPOLYGONTYPE ||
type == COLLECTIONTYPE
) )
{
ereport(ERROR, (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Geography type does not support %s", lwtype_name(type) )));
}
}
GSERIALIZED* gserialized_geography_from_lwgeom(LWGEOM *lwgeom, int32 geog_typmod)
{
GSERIALIZED *g_ser = NULL;
/* Set geodetic flag */
lwgeom_set_geodetic(lwgeom, true);
/* Check that this is a type we can handle */
geography_valid_type(lwgeom->type);
/* Force the geometry to have valid geodetic coordinate range. */
lwgeom_nudge_geodetic(lwgeom);
if ( lwgeom_force_geodetic(lwgeom) == LW_TRUE )
{
ereport(NOTICE, (
errmsg_internal("Coordinate values were coerced into range [-180 -90, 180 90] for GEOGRAPHY" ))
);
}
/* Force default SRID to the default */
if ( (int)lwgeom->srid <= 0 )
lwgeom->srid = SRID_DEFAULT;
/*
** Serialize our lwgeom and set the geodetic flag so subsequent
** functions do the right thing.
*/
g_ser = geography_serialize(lwgeom);
/* Check for typmod agreement */
if ( geog_typmod >= 0 )
{
g_ser = postgis_valid_typmod(g_ser, geog_typmod);
POSTGIS_DEBUG(3, "typmod and geometry were consistent");
}
else
{
POSTGIS_DEBUG(3, "typmod was -1");
}
return g_ser;
}
/*
** geography_in(cstring) returns *GSERIALIZED
*/
PG_FUNCTION_INFO_V1(geography_in);
Datum geography_in(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
/* Datum geog_oid = PG_GETARG_OID(1); Not needed. */
int32 geog_typmod = -1;
LWGEOM_PARSER_RESULT lwg_parser_result;
LWGEOM *lwgeom = NULL;
GSERIALIZED *g_ser = NULL;
if ( (PG_NARGS()>2) && (!PG_ARGISNULL(2)) ) {
geog_typmod = PG_GETARG_INT32(2);
}
lwgeom_parser_result_init(&lwg_parser_result);
/* Empty string. */
if ( str[0] == '\0' )
ereport(ERROR,(errmsg("parse error - invalid geometry")));
/* WKB? Let's find out. */
if ( str[0] == '0' )
{
/* TODO: 20101206: No parser checks! This is inline with current 1.5 behavior, but needs discussion */
lwgeom = lwgeom_from_hexwkb(str, LW_PARSER_CHECK_NONE);
/* Error out if something went sideways */
if ( ! lwgeom )
ereport(ERROR,(errmsg("parse error - invalid geometry")));
}
/* WKT then. */
else
{
if ( lwgeom_parse_wkt(&lwg_parser_result, str, LW_PARSER_CHECK_ALL) == LW_FAILURE )
PG_PARSER_ERROR(lwg_parser_result);
lwgeom = lwg_parser_result.geom;
}
/* Error on any SRID != default */
srid_check_latlong(lwgeom->srid);
/* Convert to gserialized */
g_ser = gserialized_geography_from_lwgeom(lwgeom, geog_typmod);
/* Clean up temporary object */
lwgeom_free(lwgeom);
PG_RETURN_POINTER(g_ser);
}
/*
** geography_out(*GSERIALIZED) returns cstring
*/
PG_FUNCTION_INFO_V1(geography_out);
Datum geography_out(PG_FUNCTION_ARGS)
{
GSERIALIZED *g = PG_GETARG_GSERIALIZED_P(0);
LWGEOM *lwgeom = lwgeom_from_gserialized(g);
PG_RETURN_CSTRING(lwgeom_to_hexwkb_buffer(lwgeom, WKB_EXTENDED));
}
/*
** geography_as_gml(*GSERIALIZED) returns text
*/
PG_FUNCTION_INFO_V1(geography_as_gml);
Datum geography_as_gml(PG_FUNCTION_ARGS)
{
LWGEOM *lwgeom = NULL;
GSERIALIZED *g = NULL;
lwvarlena_t *v;
int version;
const char *srs;
int32_t srid = SRID_DEFAULT;
int precision = -1;
int option = 0;
int lwopts = LW_GML_IS_DIMS;
static const char *default_prefix = "gml:";
const char *prefix = default_prefix;
char *prefix_buf = "";
text *prefix_text, *id_text = NULL;
const char *id = NULL;
char *id_buf;
/*
* Two potential callers, one starts with GML version,
* one starts with geography, and we check for initial
* argument type and then dynamically change what args
* we read based on presence/absence
*/
Oid first_type = get_fn_expr_argtype(fcinfo->flinfo, 0);
int argnum = 0;
if (first_type != INT4OID)
{
version = 2;
}
else
{
/* Get the version */
version = PG_GETARG_INT32(argnum++);
if (version != 2 && version != 3)
{
elog(ERROR, "Only GML 2 and GML 3 are supported");
PG_RETURN_NULL();
}
}
/* Get the parameters, both callers have same order */
g = PG_GETARG_GSERIALIZED_P(argnum++);
precision = PG_GETARG_INT32(argnum++);
option = PG_GETARG_INT32(argnum++);
prefix_text = PG_GETARG_TEXT_P(argnum++);
id_text = PG_GETARG_TEXT_P(argnum++);
/* Convert to lwgeom so we can run the old functions */
lwgeom = lwgeom_from_gserialized(g);
/* Condition the prefix argument */
if (VARSIZE_ANY_EXHDR(prefix_text) > 0)
{
/* +2 is one for the ':' and one for term null */
prefix_buf = palloc(VARSIZE_ANY_EXHDR(prefix_text)+2);
memcpy(prefix_buf, VARDATA_ANY(prefix_text),
VARSIZE_ANY_EXHDR(prefix_text));
/* add colon and null terminate */
prefix_buf[VARSIZE_ANY_EXHDR(prefix_text)] = ':';
prefix_buf[VARSIZE_ANY_EXHDR(prefix_text)+1] = '\0';
prefix = prefix_buf;
}
else
{
prefix = "";
}
if (VARSIZE_ANY_EXHDR(id_text) > 0)
{
id_buf = palloc(VARSIZE_ANY_EXHDR(id_text)+1);
memcpy(id_buf, VARDATA(id_text), VARSIZE_ANY_EXHDR(id_text));
prefix_buf[VARSIZE_ANY_EXHDR(id_text)+1] = '\0';
id = id_buf;
}
if (option & 1)
srs = GetSRSCacheBySRID(fcinfo, srid, false);
else
srs = GetSRSCacheBySRID(fcinfo, srid, true);
if (!srs)
{
elog(ERROR, "SRID %d unknown in spatial_ref_sys table", SRID_DEFAULT);
PG_RETURN_NULL();
}
/* Revert lat/lon only with long SRS */
if (option & 1) lwopts |= LW_GML_IS_DEGREE;
if (option & 2) lwopts &= ~LW_GML_IS_DIMS;
if (option & 8)
{
elog(ERROR,
"Options %d passed to ST_AsGML(geography) sets "
"unsupported value 8",
option);
PG_RETURN_NULL();
}
if ((option & 4) || (option & 16) || (option & 32))
{
elog(ERROR,
"Options %d passed to ST_AsGML(geography) but are only "
"applicable to ST_AsGML(geometry)",
option);
PG_RETURN_NULL();
}
if (version == 2)
v = lwgeom_to_gml2(lwgeom, srs, precision, prefix);
else
v = lwgeom_to_gml3(lwgeom, srs, precision, lwopts, prefix, id);
if (!v)
PG_RETURN_NULL();
else
PG_RETURN_TEXT_P(v);
}
/*
** geography_as_kml(*GSERIALIZED) returns text
*/
PG_FUNCTION_INFO_V1(geography_as_kml);
Datum geography_as_kml(PG_FUNCTION_ARGS)
{
lwvarlena_t *kml;
static const char *default_prefix = "";
char *prefixbuf;
const char *prefix = default_prefix;
GSERIALIZED *g = PG_GETARG_GSERIALIZED_P(0);
int precision = PG_GETARG_INT32(1);
text *prefix_text = PG_GETARG_TEXT_P(2);
LWGEOM *lwgeom = lwgeom_from_gserialized(g);
/* Condition the precision */
if (precision < 0)
precision = 0;
if (VARSIZE_ANY_EXHDR(prefix_text) > 0)
{
/* +2 is one for the ':' and one for term null */
prefixbuf = palloc(VARSIZE_ANY_EXHDR(prefix_text)+2);
memcpy(prefixbuf, VARDATA(prefix_text),
VARSIZE_ANY_EXHDR(prefix_text));
/* add colon and null terminate */
prefixbuf[VARSIZE_ANY_EXHDR(prefix_text)] = ':';
prefixbuf[VARSIZE_ANY_EXHDR(prefix_text)+1] = '\0';
prefix = prefixbuf;
}
else
{
prefix = "";
}
kml = lwgeom_to_kml2(lwgeom, precision, prefix);
if (kml)
PG_RETURN_TEXT_P(kml);
PG_RETURN_NULL();
}
/*
** geography_as_svg(*GSERIALIZED) returns text
*/
PG_FUNCTION_INFO_V1(geography_as_svg);
Datum geography_as_svg(PG_FUNCTION_ARGS)
{
GSERIALIZED *g = PG_GETARG_GSERIALIZED_P(0);
int relative = PG_GETARG_INT32(1) ? 1 : 0;
int precision = PG_GETARG_INT32(2);
LWGEOM *lwgeom = lwgeom_from_gserialized(g);
if (precision < 0)
precision = 0;
PG_RETURN_TEXT_P(lwgeom_to_svg(lwgeom, precision, relative));
}
/*
** geography_as_geojson(*GSERIALIZED) returns text
*/
PG_FUNCTION_INFO_V1(geography_as_geojson);
Datum geography_as_geojson(PG_FUNCTION_ARGS)
{
lwvarlena_t *geojson;
int has_bbox = 0;
const char *srs = NULL;
GSERIALIZED *g = PG_GETARG_GSERIALIZED_P(0);
int precision = PG_GETARG_INT32(1);
int option = PG_GETARG_INT32(2);
LWGEOM *lwgeom = lwgeom_from_gserialized(g);
if (precision < 0)
precision = 0;
/* Retrieve output option
* 0 = without option (default)
* 1 = bbox
* 2 = short crs
* 4 = long crs
*/
if (option & 2 || option & 4)
{
/* Geography only handle srid SRID_DEFAULT */
if (option & 2)
srs = GetSRSCacheBySRID(fcinfo, SRID_DEFAULT, true);
if (option & 4)
srs = GetSRSCacheBySRID(fcinfo, SRID_DEFAULT, false);
if (!srs)
{
elog(ERROR, "SRID SRID_DEFAULT unknown in spatial_ref_sys table");
PG_RETURN_NULL();
}
}
if (option & 1) has_bbox = 1;
geojson = lwgeom_to_geojson(lwgeom, srs, precision, has_bbox);
lwgeom_free(lwgeom);
PG_FREE_IF_COPY(g, 0);
PG_RETURN_TEXT_P(geojson);
}
/*
** geography_from_text(*char) returns *GSERIALIZED
**
** Convert text (varlena) to cstring and then call geography_in().
*/
PG_FUNCTION_INFO_V1(geography_from_text);
Datum geography_from_text(PG_FUNCTION_ARGS)
{
LWGEOM_PARSER_RESULT lwg_parser_result;
GSERIALIZED *g_ser = NULL;
text *wkt_text = PG_GETARG_TEXT_P(0);
/* Extract the cstring from the varlena */
char *wkt = text_to_cstring(wkt_text);
/* Pass the cstring to the input parser, and magic occurs! */
if ( lwgeom_parse_wkt(&lwg_parser_result, wkt, LW_PARSER_CHECK_ALL) == LW_FAILURE )
PG_PARSER_ERROR(lwg_parser_result);
/* Error on any SRID != default */
srid_check_latlong(lwg_parser_result.geom->srid);
/* Clean up string */
pfree(wkt);
g_ser = gserialized_geography_from_lwgeom(lwg_parser_result.geom, -1);
/* Clean up temporary object */
lwgeom_free(lwg_parser_result.geom);
PG_RETURN_POINTER(g_ser);
}
/*
** geography_from_binary(*char) returns *GSERIALIZED
*/
PG_FUNCTION_INFO_V1(geography_from_binary);
Datum geography_from_binary(PG_FUNCTION_ARGS)
{
char *wkb_bytea = (char*)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
GSERIALIZED *gser = NULL;
size_t wkb_size = VARSIZE(wkb_bytea);
uint8_t *wkb = (uint8_t*)VARDATA(wkb_bytea);
LWGEOM *lwgeom = lwgeom_from_wkb(wkb, wkb_size, LW_PARSER_CHECK_NONE);
if ( ! lwgeom )
lwpgerror("Unable to parse WKB");
/* Error on any SRID != default */
srid_check_latlong(lwgeom->srid);
gser = gserialized_geography_from_lwgeom(lwgeom, -1);
lwgeom_free(lwgeom);
PG_RETURN_POINTER(gser);
}
PG_FUNCTION_INFO_V1(geography_from_geometry);
Datum geography_from_geometry(PG_FUNCTION_ARGS)
{
GSERIALIZED *g_ser = NULL;
GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P_COPY(0);
LWGEOM *lwgeom = lwgeom_from_gserialized(geom);
geography_valid_type(lwgeom_get_type(lwgeom));
/* Force default SRID */
if ( (int)lwgeom->srid <= 0 )
{
lwgeom->srid = SRID_DEFAULT;
}
/* Error on any SRID != default */
srid_check_latlong(lwgeom->srid);
/* Force the geometry to have valid geodetic coordinate range. */
lwgeom_nudge_geodetic(lwgeom);
if ( lwgeom_force_geodetic(lwgeom) == LW_TRUE )
{
ereport(NOTICE, (
errmsg_internal("Coordinate values were coerced into range [-180 -90, 180 90] for GEOGRAPHY" ))
);
}
/* force recalculate of box by dropping */
lwgeom_drop_bbox(lwgeom);
lwgeom_set_geodetic(lwgeom, true);
/* We are trusting geography_serialize will add a box if needed */
g_ser = geography_serialize(lwgeom);
lwgeom_free(lwgeom);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_POINTER(g_ser);
}
PG_FUNCTION_INFO_V1(geometry_from_geography);
Datum geometry_from_geography(PG_FUNCTION_ARGS)
{
LWGEOM *lwgeom = NULL;
GSERIALIZED *ret = NULL;
GSERIALIZED *g_ser = PG_GETARG_GSERIALIZED_P(0);
lwgeom = lwgeom_from_gserialized(g_ser);
/* Recalculate the boxes after re-setting the geodetic bit */
lwgeom_set_geodetic(lwgeom, false);
lwgeom_refresh_bbox(lwgeom);
/* We want "geometry" to think all our "geography" has an SRID, and the
implied SRID is the default, so we fill that in if our SRID is actually unknown. */
if ( (int)lwgeom->srid <= 0 )
lwgeom->srid = SRID_DEFAULT;
ret = geometry_serialize(lwgeom);
lwgeom_free(lwgeom);
PG_RETURN_POINTER(ret);
}
PG_FUNCTION_INFO_V1(geography_recv);
Datum geography_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
int32 geog_typmod = -1;
LWGEOM *lwgeom = NULL;
GSERIALIZED *g_ser = NULL;
if ( (PG_NARGS()>2) && (!PG_ARGISNULL(2)) ) {
geog_typmod = PG_GETARG_INT32(2);
}
lwgeom = lwgeom_from_wkb((uint8_t*)buf->data, buf->len, LW_PARSER_CHECK_ALL);
/* Error on any SRID != default */
srid_check_latlong(lwgeom->srid);
g_ser = gserialized_geography_from_lwgeom(lwgeom, geog_typmod);
/* Clean up temporary object */
lwgeom_free(lwgeom);
/* Set cursor to the end of buffer (so the backend is happy) */
buf->cursor = buf->len;
PG_RETURN_POINTER(g_ser);
}
PG_FUNCTION_INFO_V1(geography_send);
Datum geography_send(PG_FUNCTION_ARGS)
{
GSERIALIZED *g = PG_GETARG_GSERIALIZED_P(0);
LWGEOM *lwgeom = lwgeom_from_gserialized(g);
PG_RETURN_POINTER(lwgeom_to_wkb_varlena(lwgeom, WKB_EXTENDED));
}