blob: 5239de5ae09c466d51733878a3da1e186f28f522 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "geom_buf.h"
#include <string.h>
#include "geomserde.h"
#include "geos_c_dyn.h"
static CoordinateType coordinate_type_of(int has_z, int has_m) {
if (has_z && has_m) {
return XYZM;
} else if (has_z) {
return XYZ;
} else if (has_m) {
return XYM;
} else {
return XY;
}
}
static unsigned int get_bytes_per_coordinate(CoordinateType coord_type) {
switch (coord_type) {
case XY:
return 16;
case XYZ:
case XYM:
return 24;
case XYZM:
default:
return 32;
}
}
SedonaErrorCode get_coord_seq_info_from_geom(
GEOSContextHandle_t handle, const GEOSGeometry *geom,
CoordinateSequenceInfo *coord_seq_info) {
int dims = dyn_GEOSGeom_getCoordinateDimension_r(handle, geom);
if (dims == 0) {
return SEDONA_GEOS_ERROR;
}
int has_z = (dims >= 3);
int has_m = 0; /* libgeos does not support M dimension for now */
CoordinateType coord_type = coordinate_type_of(has_z, has_m);
unsigned int bytes_per_coord = get_bytes_per_coordinate(coord_type);
int num_coords = dyn_GEOSGetNumCoordinates_r(handle, geom);
if (num_coords == -1) {
return SEDONA_GEOS_ERROR;
}
coord_seq_info->dims = dims;
coord_seq_info->has_z = has_z;
coord_seq_info->has_m = has_m;
coord_seq_info->coord_type = coord_type;
coord_seq_info->bytes_per_coord = bytes_per_coord;
coord_seq_info->num_coords = num_coords;
coord_seq_info->total_bytes = bytes_per_coord * num_coords;
return SEDONA_SUCCESS;
}
void *alloc_buffer_for_geom(GeometryTypeId geom_type_id,
CoordinateType coord_type, int srid, int buf_size,
int num_coords) {
unsigned char *buf = malloc(buf_size);
int *buf_int = (int *)buf;
if (buf == NULL) {
return buf;
}
int has_srid = (srid != 0) ? 1 : 0;
unsigned char preamble_byte =
(geom_type_id << 4) | (coord_type << 1) | has_srid;
buf[0] = preamble_byte;
buf[1] = srid >> 16;
buf[2] = srid >> 8;
buf[3] = srid;
buf_int[1] = num_coords;
return buf;
}
static SedonaErrorCode copy_coord_seq_to_buffer(
GEOSContextHandle_t handle, const GEOSCoordSequence *coord_seq, double *buf,
int num_coords, int has_z, int has_m) {
if (dyn_GEOSCoordSeq_copyToBuffer_r != NULL) {
/* fast path for libgeos >= 3.10.0 */
if (dyn_GEOSCoordSeq_copyToBuffer_r(handle, coord_seq, buf, has_z, has_m) ==
0) {
return SEDONA_GEOS_ERROR;
}
return SEDONA_SUCCESS;
}
/* slow path for old libgeos */
for (int k = 0; k < num_coords; k++) {
if (has_z) {
double x, y, z;
if (dyn_GEOSCoordSeq_getXYZ_r(handle, coord_seq, k, &x, &y, &z) == 0) {
return SEDONA_GEOS_ERROR;
}
*buf++ = x;
*buf++ = y;
*buf++ = z;
} else {
double x, y;
if (dyn_GEOSCoordSeq_getXY_r(handle, coord_seq, k, &x, &y) == 0) {
return SEDONA_GEOS_ERROR;
}
*buf++ = x;
*buf++ = y;
}
if (has_m) {
/* XYZ/XYZM is not supported for now, just fill in 0 for M ordinate as a
* fallback value. */
*buf++ = 0;
}
}
return SEDONA_SUCCESS;
}
static SedonaErrorCode copy_buffer_to_coord_seq(
GEOSContextHandle_t handle, double *buf, int num_coords, int has_z,
int has_m, GEOSCoordSequence **p_coord_seq) {
if (dyn_GEOSCoordSeq_copyFromBuffer_r != NULL) {
/* fast path for libgeos >= 3.10.0 */
GEOSCoordSequence *coord_seq = dyn_GEOSCoordSeq_copyFromBuffer_r(
handle, buf, num_coords, has_z, has_m);
if (coord_seq == NULL) {
return SEDONA_GEOS_ERROR;
}
*p_coord_seq = coord_seq;
return SEDONA_SUCCESS;
}
/* slow path for old libgeos */
GEOSCoordSequence *coord_seq =
dyn_GEOSCoordSeq_create_r(handle, num_coords, 2 + has_z + has_m);
if (coord_seq == NULL) {
return SEDONA_GEOS_ERROR;
}
for (int k = 0; k < num_coords; k++) {
double x = *buf++;
double y = *buf++;
double z;
if (has_z) {
z = *buf++;
}
if (has_m) {
/* M ordinate is not supported for now, just skip it. */
buf++;
}
if (has_z) {
if (dyn_GEOSCoordSeq_setXYZ_r(handle, coord_seq, k, x, y, z) == 0) {
dyn_GEOSCoordSeq_destroy_r(handle, coord_seq);
return SEDONA_GEOS_ERROR;
}
} else {
if (dyn_GEOSCoordSeq_setXY_r(handle, coord_seq, k, x, y) == 0) {
dyn_GEOSCoordSeq_destroy_r(handle, coord_seq);
return SEDONA_GEOS_ERROR;
}
}
}
*p_coord_seq = coord_seq;
return SEDONA_SUCCESS;
}
static void geom_buf_init(GeomBuffer *geom_buf, void *buf,
const CoordinateSequenceInfo *cs_info, int num_coords,
int num_ints) {
geom_buf->buf = buf;
geom_buf->buf_size = 8 + num_coords * cs_info->bytes_per_coord + 4 * num_ints;
geom_buf->buf_coord = (double *)buf + 1;
geom_buf->buf_coord_end = geom_buf->buf_coord + num_coords * cs_info->dims;
geom_buf->buf_int = (int *)geom_buf->buf_coord_end;
geom_buf->buf_int_end = geom_buf->buf_int + num_ints;
}
SedonaErrorCode geom_buf_alloc(GeomBuffer *geom_buf,
GeometryTypeId geom_type_id, int srid,
const CoordinateSequenceInfo *cs_info,
int num_ints) {
int num_coords = cs_info->num_coords;
int bytes_per_coord = cs_info->bytes_per_coord;
int buf_size = 8 + num_coords * bytes_per_coord + 4 * num_ints;
void *buf = alloc_buffer_for_geom(geom_type_id, cs_info->coord_type, srid,
buf_size, num_coords);
if (buf == NULL) {
return SEDONA_ALLOC_ERROR;
}
geom_buf_init(geom_buf, buf, cs_info, num_coords, num_ints);
return SEDONA_SUCCESS;
}
SedonaErrorCode read_geom_buf_header(const char *buf, int buf_size,
GeomBuffer *geom_buf,
CoordinateSequenceInfo *cs_info,
GeometryTypeId *p_geom_type_id,
int *p_srid) {
if (buf_size < 8) {
return SEDONA_INCOMPLETE_BUFFER;
}
unsigned int preamble = (unsigned int)buf[0];
int srid = 0;
int geom_type_id = preamble >> 4;
int coord_type = (preamble & 0x0F) >> 1;
if ((preamble & 0x01) != 0) {
srid = (((unsigned int)buf[1] & 0xFF) << 16) |
(((unsigned int)buf[2] & 0xFF) << 8) | ((unsigned int)buf[3] & 0xFF);
}
int num_coords = ((int *)buf)[1];
if (geom_type_id < 0 || geom_type_id > GEOMETRYCOLLECTION) {
return SEDONA_UNKNOWN_GEOM_TYPE;
}
if (coord_type < 0 || coord_type > XYZM) {
return SEDONA_UNKNOWN_COORD_TYPE;
}
if (num_coords < 0 || num_coords > buf_size) {
return SEDONA_BAD_GEOM_BUFFER;
}
int bytes_per_coord = get_bytes_per_coordinate(coord_type);
if (geom_type_id != GEOMETRYCOLLECTION) {
if (8 + num_coords * bytes_per_coord > buf_size) {
return SEDONA_INCOMPLETE_BUFFER;
}
int dims = bytes_per_coord / 8;
int has_z = ((coord_type == XYZ || coord_type == XYZM) ? 1 : 0);
int has_m = ((coord_type == XYM || coord_type == XYZM) ? 1 : 0);
cs_info->bytes_per_coord = bytes_per_coord;
cs_info->coord_type = coord_type;
cs_info->num_coords = num_coords;
cs_info->dims = dims;
cs_info->has_z = has_z;
cs_info->has_m = has_m;
geom_buf->buf = (void *)buf;
geom_buf->buf_coord = (double *)(buf + 8);
geom_buf->buf_coord_end = geom_buf->buf_coord + num_coords * dims;
geom_buf->buf_int = (int *)geom_buf->buf_coord_end;
geom_buf->buf_int_end = (int *)(buf + buf_size);
geom_buf->buf_size = buf_size;
} else {
/* num_coords for GeometryCollection is number of geometries in the
* collection, other fields in cs_info are unused. */
cs_info->num_coords = num_coords;
/* geom_buf contains a series of serialized geometries. buf_coord is the
* beginning of its first child geometry, and buf_int is unused. */
const void *buf_coord = buf + 8;
geom_buf->buf = (void *)buf;
geom_buf->buf_coord = (double *)(buf_coord);
geom_buf->buf_coord_end = (double *)buf_coord;
geom_buf->buf_int = (int *)buf_coord;
geom_buf->buf_int_end = (int *)buf_coord;
geom_buf->buf_size = buf_size;
}
*p_geom_type_id = geom_type_id;
*p_srid = srid;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_write_int(GeomBuffer *geom_buf, int value) {
if (geom_buf->buf_int >= geom_buf->buf_int_end) {
return SEDONA_INTERNAL_ERROR;
}
*geom_buf->buf_int++ = value;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_read_bounded_int(GeomBuffer *geom_buf, int *p_value) {
if (geom_buf->buf_int >= geom_buf->buf_int_end) {
return SEDONA_INCOMPLETE_BUFFER;
}
int value = *geom_buf->buf_int++;
if (value < 0 || value > geom_buf->buf_size) {
return SEDONA_BAD_GEOM_BUFFER;
}
*p_value = value;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_write_coords(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
const GEOSCoordSequence *coord_seq,
const CoordinateSequenceInfo *cs_info) {
int num_coords = cs_info->num_coords;
if (num_coords == 0) {
return SEDONA_SUCCESS;
}
int num_doubles = num_coords * cs_info->dims;
if (geom_buf->buf_coord + num_doubles > geom_buf->buf_coord_end) {
return SEDONA_INTERNAL_ERROR;
}
SedonaErrorCode err = copy_coord_seq_to_buffer(
handle, coord_seq, geom_buf->buf_coord, cs_info->num_coords,
cs_info->has_z, cs_info->has_m);
if (err != SEDONA_SUCCESS) {
return err;
}
geom_buf->buf_coord += num_doubles;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_read_coords(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
const CoordinateSequenceInfo *cs_info,
GEOSCoordSequence **p_coord_seq) {
int num_coords = cs_info->num_coords;
int dims = cs_info->dims;
int num_ordinates = num_coords * dims;
if (geom_buf->buf_coord + num_ordinates > geom_buf->buf_coord_end) {
return SEDONA_INCOMPLETE_BUFFER;
}
int err =
copy_buffer_to_coord_seq(handle, geom_buf->buf_coord, num_coords,
cs_info->has_z, cs_info->has_m, p_coord_seq);
if (err != SEDONA_SUCCESS) {
return err;
}
geom_buf->buf_coord += num_ordinates;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_write_linear_segment(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
const GEOSGeometry *geom,
CoordinateSequenceInfo *cs_info) {
const GEOSCoordSequence *coord_seq = dyn_GEOSGeom_getCoordSeq_r(handle, geom);
if (coord_seq == NULL) {
return SEDONA_GEOS_ERROR;
}
unsigned int num_coords = 0;
if (dyn_GEOSCoordSeq_getSize_r(handle, coord_seq, &num_coords) == 0) {
return SEDONA_GEOS_ERROR;
}
cs_info->num_coords = num_coords;
SedonaErrorCode err =
geom_buf_write_coords(geom_buf, handle, coord_seq, cs_info);
if (err != SEDONA_SUCCESS) {
return err;
}
err = geom_buf_write_int(geom_buf, num_coords);
if (err != SEDONA_SUCCESS) {
return err;
}
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_read_linear_segment(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
CoordinateSequenceInfo *cs_info,
int type, GEOSGeometry **p_geom) {
SedonaErrorCode err =
geom_buf_read_bounded_int(geom_buf, (int *)&cs_info->num_coords);
if (err != SEDONA_SUCCESS) {
return err;
}
GEOSCoordSequence *coord_seq = NULL;
err = geom_buf_read_coords(geom_buf, handle, cs_info, &coord_seq);
if (err != SEDONA_SUCCESS) {
return err;
}
GEOSGeometry *segment = NULL;
switch (type) {
case GEOS_LINESTRING:
segment = dyn_GEOSGeom_createLineString_r(handle, coord_seq);
break;
case GEOS_LINEARRING:
segment = dyn_GEOSGeom_createLinearRing_r(handle, coord_seq);
break;
default:
return SEDONA_INTERNAL_ERROR;
}
if (segment == NULL) {
dyn_GEOSCoordSeq_destroy_r(handle, coord_seq);
return SEDONA_GEOS_ERROR;
}
*p_geom = segment;
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_write_polygon(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
const GEOSGeometry *geom,
CoordinateSequenceInfo *cs_info) {
const GEOSGeometry *exterior_ring = dyn_GEOSGetExteriorRing_r(handle, geom);
if (exterior_ring == NULL) {
return SEDONA_GEOS_ERROR;
}
/* if exterior ring is empty, the serialized polygon is an empty polygon */
char is_empty = dyn_GEOSisEmpty_r(handle, exterior_ring);
if (is_empty == 2) {
return SEDONA_GEOS_ERROR;
}
if (is_empty == 1) {
return geom_buf_write_int(geom_buf, 0);
}
int num_interior_rings = dyn_GEOSGetNumInteriorRings_r(handle, geom);
if (num_interior_rings == -1) {
return SEDONA_GEOS_ERROR;
}
int num_rings = num_interior_rings + 1;
SedonaErrorCode err = geom_buf_write_int(geom_buf, num_rings);
if (err != SEDONA_SUCCESS) {
return err;
}
/* Write exterior ring */
err = geom_buf_write_linear_segment(geom_buf, handle, exterior_ring, cs_info);
if (err != SEDONA_SUCCESS) {
return err;
}
/* Write interior rings */
for (int k = 0; k < num_interior_rings; k++) {
const GEOSGeometry *interior_ring =
dyn_GEOSGetInteriorRingN_r(handle, geom, k);
if (interior_ring == NULL) {
return SEDONA_GEOS_ERROR;
}
err =
geom_buf_write_linear_segment(geom_buf, handle, interior_ring, cs_info);
if (err != SEDONA_SUCCESS) {
return err;
}
}
return SEDONA_SUCCESS;
}
SedonaErrorCode geom_buf_read_polygon(GeomBuffer *geom_buf,
GEOSContextHandle_t handle,
CoordinateSequenceInfo *cs_info,
GEOSGeometry **p_geom) {
int num_rings = 0;
SedonaErrorCode err = geom_buf_read_bounded_int(geom_buf, &num_rings);
if (err != SEDONA_SUCCESS) {
return err;
}
if (num_rings == 0) {
GEOSGeometry *geom = dyn_GEOSGeom_createEmptyPolygon_r(handle);
if (geom == NULL) {
return SEDONA_GEOS_ERROR;
}
*p_geom = geom;
return SEDONA_SUCCESS;
}
GEOSGeometry **rings = calloc(num_rings, sizeof(GEOSGeometry *));
if (rings == NULL) {
return SEDONA_ALLOC_ERROR;
}
for (int k = 0; k < num_rings; k++) {
GEOSGeometry *ring = NULL;
err = geom_buf_read_linear_segment(geom_buf, handle, cs_info,
GEOS_LINEARRING, &ring);
if (err != SEDONA_SUCCESS) {
goto handle_error;
}
rings[k] = ring;
}
GEOSGeometry *geom =
dyn_GEOSGeom_createPolygon_r(handle, rings[0], &rings[1], num_rings - 1);
if (geom == NULL) {
err = SEDONA_GEOS_ERROR;
goto handle_error;
}
free(rings);
*p_geom = geom;
return SEDONA_SUCCESS;
handle_error:
destroy_geometry_array(handle, rings, num_rings);
return err;
}
void destroy_geometry_array(GEOSContextHandle_t handle, GEOSGeometry **geoms,
int num_geoms) {
for (int k = 0; k < num_geoms; k++) {
if (geoms[k] != NULL) {
dyn_GEOSGeom_destroy_r(handle, geoms[k]);
}
}
free(geoms);
}