/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  MapServer-GEOS integration.
 * Author:   Steve Lime and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

#include "mapserver.h"
#include "mapgraph.h"

#ifdef USE_GEOS

// To avoid accidental use of non reentrant GEOS API.
// (check only effective in GEOS >= 3.5)
#define GEOS_USE_ONLY_R_API

#include <geos_c.h>

/*
** Error handling...
*/
static void msGEOSError(const char *format, ...)
{
  va_list args;

  va_start (args, format);
  msSetError(MS_GEOSERR, format, "msGEOSError()", args); /* just pass along to MapServer error handling */
  va_end(args);

  return;
}

static void msGEOSNotice(const char *fmt, ...)
{
  (void)fmt; /* do nothing with notices at this point */
}

#ifndef USE_THREAD
static GEOSContextHandle_t geos_handle;

static inline GEOSContextHandle_t msGetGeosContextHandle()
{
  return geos_handle;
}

#else
#include "mapthread.h"
typedef struct geos_thread_info {
  struct geos_thread_info *next;
  void*             thread_id;
  GEOSContextHandle_t        geos_handle;
} geos_thread_info_t;

static geos_thread_info_t *geos_list = NULL;

static GEOSContextHandle_t msGetGeosContextHandle()
{
  geos_thread_info_t *link;
  GEOSContextHandle_t ret_obj;
  void*        thread_id;

  msAcquireLock( TLOCK_GEOS );

  thread_id = msGetThreadId();

  /* find link for this thread */

  for( link = geos_list;
       link != NULL && link->thread_id != thread_id
       && link->next != NULL && link->next->thread_id != thread_id;
       link = link->next ) {}

  /* If the target thread link is already at the head of the list were ok */
  if( geos_list != NULL && geos_list->thread_id == thread_id ) {
  }

  /* We don't have one ... initialize one. */
  else if( link == NULL || link->next == NULL ) {
    geos_thread_info_t *new_link;
    new_link = (geos_thread_info_t *) malloc(sizeof(geos_thread_info_t));
    new_link->next = geos_list;
    new_link->thread_id = thread_id;
    new_link->geos_handle = initGEOS_r(msGEOSNotice, msGEOSError);

    geos_list = new_link;
  }

  /* If the link is not already at the head of the list, promote it */
  else {
    geos_thread_info_t *target = link->next;

    link->next = link->next->next;
    target->next = geos_list;
    geos_list = target;
  }

  ret_obj = geos_list->geos_handle;

  msReleaseLock( TLOCK_GEOS );

  return ret_obj;
}
#endif

/*
** Setup/Cleanup wrappers
*/
void msGEOSSetup()
{
#ifndef USE_THREAD
  geos_handle = initGEOS_r(msGEOSNotice, msGEOSError);
#endif
}

void msGEOSCleanup()
{
#ifndef USE_THREAD
  finishGEOS_r(geos_handle);
  geos_handle = NULL;
#else
  geos_thread_info_t *link;
  msAcquireLock( TLOCK_GEOS );
  for( link = geos_list; link != NULL;) {
    geos_thread_info_t *cur = link;
    link = link->next;
    finishGEOS_r(cur->geos_handle);
    free(cur);
  }
  geos_list = NULL;
  msReleaseLock( TLOCK_GEOS );
#endif
}

/*
** Translation functions
*/
static GEOSGeom msGEOSShape2Geometry_point(pointObj *point)
{
  GEOSCoordSeq coords;
  GEOSGeom g;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!point) return NULL;

  coords = GEOSCoordSeq_create_r(handle,1, 2); /* todo handle z's */
  if(!coords) return NULL;

  GEOSCoordSeq_setX_r(handle,coords, 0, point->x);
  GEOSCoordSeq_setY_r(handle,coords, 0, point->y);
  /* GEOSCoordSeq_setY(coords, 0, point->z); */

  g = GEOSGeom_createPoint_r(handle,coords); /* g owns the coordinate in coords */

  return g;
}

static GEOSGeom msGEOSShape2Geometry_multipoint(lineObj *multipoint)
{
  int i;
  GEOSGeom g;
  GEOSGeom *points;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!multipoint) return NULL;

  points = malloc(multipoint->numpoints*sizeof(GEOSGeom));
  if(!points) return NULL;

  for(i=0; i<multipoint->numpoints; i++)
    points[i] = msGEOSShape2Geometry_point(&(multipoint->point[i]));

  g = GEOSGeom_createCollection_r(handle,GEOS_MULTIPOINT, points, multipoint->numpoints);

  free(points);

  return g;
}

static GEOSGeom msGEOSShape2Geometry_line(lineObj *line)
{
  int i;
  GEOSGeom g;
  GEOSCoordSeq coords;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!line) return NULL;

  coords = GEOSCoordSeq_create_r(handle,line->numpoints, 2); /* todo handle z's */
  if(!coords) return NULL;

  for(i=0; i<line->numpoints; i++) {
    GEOSCoordSeq_setX_r(handle,coords, i, line->point[i].x);
    GEOSCoordSeq_setY_r(handle,coords, i, line->point[i].y);
    /* GEOSCoordSeq_setZ(coords, i, line->point[i].z); */
  }

  g = GEOSGeom_createLineString_r(handle,coords); /* g owns the coordinates in coords */

  return g;
}

static GEOSGeom msGEOSShape2Geometry_multiline(shapeObj *multiline)
{
  int i;
  GEOSGeom g;
  GEOSGeom *lines;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!multiline) return NULL;

  lines = malloc(multiline->numlines*sizeof(GEOSGeom));
  if(!lines) return NULL;

  for(i=0; i<multiline->numlines; i++)
    lines[i] = msGEOSShape2Geometry_line(&(multiline->line[i]));

  g = GEOSGeom_createCollection_r(handle,GEOS_MULTILINESTRING, lines, multiline->numlines);

  free(lines);

  return g;
}

static GEOSGeom msGEOSShape2Geometry_simplepolygon(shapeObj *shape, int r, int *outerList)
{
  int i, j, k;
  GEOSCoordSeq coords;
  GEOSGeom g;
  GEOSGeom outerRing;
  GEOSGeom *innerRings=NULL;
  int numInnerRings=0, *innerList;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape || !outerList) return NULL;

  /* build the outer shell */
  coords = GEOSCoordSeq_create_r(handle,shape->line[r].numpoints, 2); /* todo handle z's */
  if(!coords) return NULL;

  for(i=0; i<shape->line[r].numpoints; i++) {
    GEOSCoordSeq_setX_r(handle,coords, i, shape->line[r].point[i].x);
    GEOSCoordSeq_setY_r(handle,coords, i, shape->line[r].point[i].y);
    /* GEOSCoordSeq_setZ(coords, i, shape->line[r].point[i].z); */
  }

  outerRing = GEOSGeom_createLinearRing_r(handle,coords); /* outerRing owns the coordinates in coords */

  /* build the holes */
  innerList = msGetInnerList(shape, r, outerList);
  for(j=0; j<shape->numlines; j++)
    if(innerList[j] == MS_TRUE) numInnerRings++;

  if(numInnerRings > 0) {
    k = 0; /* inner ring counter */

    innerRings = msSmallMalloc(numInnerRings*sizeof(GEOSGeom));

    for(j=0; j<shape->numlines; j++) {
      if(innerList[j] == MS_FALSE) continue;

      coords = GEOSCoordSeq_create_r(handle,shape->line[j].numpoints, 2); /* todo handle z's */
      if(!coords) {
        free(innerRings);
        free(innerList);
        return NULL; /* todo, this will leak memory (shell + allocated holes) */
      }

      for(i=0; i<shape->line[j].numpoints; i++) {
        GEOSCoordSeq_setX_r(handle,coords, i, shape->line[j].point[i].x);
        GEOSCoordSeq_setY_r(handle,coords, i, shape->line[j].point[i].y);
        /* GEOSCoordSeq_setZ(coords, i, shape->line[j].point[i].z); */
      }

      innerRings[k] = GEOSGeom_createLinearRing_r(handle,coords); /* innerRings[k] owns the coordinates in coords */
      k++;
    }
  }

  g = GEOSGeom_createPolygon_r(handle,outerRing, innerRings, numInnerRings);

  free(innerList); /* clean up */
  free(innerRings); /* clean up */

  return g;
}

static GEOSGeom msGEOSShape2Geometry_polygon(shapeObj *shape)
{
  int i, j;
  GEOSGeom *polygons;
  int *outerList, numOuterRings=0, lastOuterRing=0;
  GEOSGeom g;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  outerList = msGetOuterList(shape);
  for(i=0; i<shape->numlines; i++) {
    if(outerList[i] == MS_TRUE) {
      numOuterRings++;
      lastOuterRing = i; /* save for the simple case */
    }
  }

  if(numOuterRings == 1) {
    g = msGEOSShape2Geometry_simplepolygon(shape, lastOuterRing, outerList);
  } else { /* a true multipolygon */
    polygons = msSmallMalloc(numOuterRings*sizeof(GEOSGeom));

    j = 0; /* part counter */
    for(i=0; i<shape->numlines; i++) {
      if(outerList[i] == MS_FALSE) continue;
      polygons[j] = msGEOSShape2Geometry_simplepolygon(shape, i, outerList); /* TODO: account for NULL return values */
      j++;
    }

    g = GEOSGeom_createCollection_r(handle,GEOS_MULTIPOLYGON, polygons, numOuterRings);
    free(polygons);
  }

  free(outerList);
  return g;
}

GEOSGeom msGEOSShape2Geometry(shapeObj *shape)
{
  if(!shape)
    return NULL; /* a NULL shape generates a NULL geometry */

  switch(shape->type) {
    case MS_SHAPE_POINT:
      if(shape->numlines == 0 || shape->line[0].numpoints == 0) /* not enough info for a point */
        return NULL;

      if(shape->line[0].numpoints == 1) /* simple point */
        return msGEOSShape2Geometry_point(&(shape->line[0].point[0]));
      else /* multi-point */
        return msGEOSShape2Geometry_multipoint(&(shape->line[0]));
      break;
    case MS_SHAPE_LINE:
      if(shape->numlines == 0 || shape->line[0].numpoints < 2) /* not enough info for a line */
        return NULL;

      if(shape->numlines == 1) /* simple line */
        return msGEOSShape2Geometry_line(&(shape->line[0]));
      else /* multi-line */
        return msGEOSShape2Geometry_multiline(shape);
      break;
    case MS_SHAPE_POLYGON:
      if(shape->numlines == 0 || shape->line[0].numpoints < 4) /* not enough info for a polygon (first=last) */
        return NULL;

      return msGEOSShape2Geometry_polygon(shape); /* simple and multipolygon cases are addressed */
      break;
    default:
      break;
  }

  return NULL; /* should not get here */
}

static shapeObj *msGEOSGeometry2Shape_point(GEOSGeom g)
{
  GEOSCoordSeq coords;
  shapeObj *shape=NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g) return NULL;

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);

  shape->type = MS_SHAPE_POINT;
  shape->line = (lineObj *) malloc(sizeof(lineObj));
  shape->numlines = 1;
  shape->line[0].point = (pointObj *) malloc(sizeof(pointObj));
  shape->line[0].numpoints = 1;
  shape->geometry = (GEOSGeom) g;

  coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,g);

  GEOSCoordSeq_getX_r(handle,coords, 0, &(shape->line[0].point[0].x));
  GEOSCoordSeq_getY_r(handle,coords, 0, &(shape->line[0].point[0].y));
  /* GEOSCoordSeq_getZ(coords, 0, &(shape->line[0].point[0].z)); */

  shape->bounds.minx = shape->bounds.maxx = shape->line[0].point[0].x;
  shape->bounds.miny = shape->bounds.maxy = shape->line[0].point[0].y;

  return shape;
}

static shapeObj *msGEOSGeometry2Shape_multipoint(GEOSGeom g)
{
  int i;
  int numPoints;
  GEOSCoordSeq coords;
  GEOSGeom point;

  shapeObj *shape=NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g) return NULL;
  numPoints = GEOSGetNumGeometries_r(handle,g); /* each geometry has 1 point */

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);

  shape->type = MS_SHAPE_POINT;
  shape->line = (lineObj *) malloc(sizeof(lineObj));
  shape->numlines = 1;
  shape->line[0].point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
  shape->line[0].numpoints = numPoints;
  shape->geometry = (GEOSGeom) g;

  for(i=0; i<numPoints; i++) {
    point = (GEOSGeom) GEOSGetGeometryN_r(handle,g, i);
    coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,point);

    GEOSCoordSeq_getX_r(handle,coords, 0, &(shape->line[0].point[i].x));
    GEOSCoordSeq_getY_r(handle,coords, 0, &(shape->line[0].point[i].y));
    /* GEOSCoordSeq_getZ(coords, 0, &(shape->line[0].point[i].z)); */
  }

  msComputeBounds(shape);

  return shape;
}

static shapeObj *msGEOSGeometry2Shape_line(GEOSGeom g)
{
  shapeObj *shape=NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  int i;
  int numPoints;
  GEOSCoordSeq coords;

  if(!g) return NULL;
  numPoints = GEOSGetNumCoordinates_r(handle,g);
  coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,g);

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);

  shape->type = MS_SHAPE_LINE;
  shape->line = (lineObj *) malloc(sizeof(lineObj));
  shape->numlines = 1;
  shape->line[0].point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
  shape->line[0].numpoints = numPoints;
  shape->geometry = (GEOSGeom) g;

  for(i=0; i<numPoints; i++) {
    GEOSCoordSeq_getX_r(handle,coords, i, &(shape->line[0].point[i].x));
    GEOSCoordSeq_getY_r(handle,coords, i, &(shape->line[0].point[i].y));
    /* GEOSCoordSeq_getZ(coords, i, &(shape->line[0].point[i].z)); */
  }

  msComputeBounds(shape);

  return shape;
}

static void msGEOSGeometry2Shape_multiline_part(GEOSContextHandle_t handle, GEOSGeom part, shapeObj *shape) {
  int i;
  int type, numGeometries, numPoints;
  GEOSCoordSeq coords;
  lineObj line;
  
  type = GEOSGeomTypeId_r(handle,part); 
  if (type == GEOS_MULTILINESTRING) {
    numGeometries = GEOSGetNumGeometries_r(handle,part);

    for(i=0; i<numGeometries; i++) {
      GEOSGeom subPart = (GEOSGeom) GEOSGetGeometryN_r(handle, part, i);
      msGEOSGeometry2Shape_multiline_part(handle, subPart, shape);
    }
  } else {
    numPoints = GEOSGetNumCoordinates_r(handle,part);
    coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,part);
    line.point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
    line.numpoints = numPoints;

    for(i=0; i<numPoints; i++) {
      GEOSCoordSeq_getX_r(handle,coords, i, &(line.point[i].x));
      GEOSCoordSeq_getY_r(handle,coords, i, &(line.point[i].y));
      /* GEOSCoordSeq_getZ(coords, i, &(line.point[i].z)); */
    }

    msAddLineDirectly(shape, &line);
  }
}

static shapeObj *msGEOSGeometry2Shape_multiline(GEOSGeom g)
{
  int i;
  int numGeometries;
  GEOSGeom part;

  shapeObj *shape=NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g) return NULL;
  numGeometries = GEOSGetNumGeometries_r(handle,g);

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);

  shape->type = MS_SHAPE_LINE;
  shape->geometry = (GEOSGeom) g;

  for(i=0; i<numGeometries; i++) {
    part = (GEOSGeom) GEOSGetGeometryN_r(handle, g, i);
    msGEOSGeometry2Shape_multiline_part(handle, part, shape);
  }

  msComputeBounds(shape);

  return shape;
}

static shapeObj *msGEOSGeometry2Shape_polygon(GEOSGeom g)
{
  shapeObj *shape=NULL;
  lineObj line;
  int numPoints, numRings;
  int i, j;

  GEOSCoordSeq coords;
  GEOSGeom ring;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g) return NULL;

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);
  shape->type = MS_SHAPE_POLYGON;
  shape->geometry = (GEOSGeom) g;

  /* exterior ring */
  ring = (GEOSGeom) GEOSGetExteriorRing_r(handle,g);
  numPoints = GEOSGetNumCoordinates_r(handle,ring);
  coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,ring);

  line.point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
  line.numpoints = numPoints;

  for(i=0; i<numPoints; i++) {
    GEOSCoordSeq_getX_r(handle,coords, i, &(line.point[i].x));
    GEOSCoordSeq_getY_r(handle,coords, i, &(line.point[i].y));
    /* GEOSCoordSeq_getZ(coords, i, &(line.point[i].z)); */
  }
  msAddLineDirectly(shape, &line);

  /* interior rings */
  numRings = GEOSGetNumInteriorRings_r(handle,g);
  for(j=0; j<numRings; j++) {
    ring = (GEOSGeom) GEOSGetInteriorRingN_r(handle,g, j);
    if(GEOSisRing_r(handle,ring) != 1) continue; /* skip it */

    numPoints = GEOSGetNumCoordinates_r(handle,ring);
    coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,ring);

    line.point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
    line.numpoints = numPoints;

    for(i=0; i<numPoints; i++) {
      GEOSCoordSeq_getX_r(handle,coords, i, &(line.point[i].x));
      GEOSCoordSeq_getY_r(handle,coords, i, &(line.point[i].y));
      /* GEOSCoordSeq_getZ(coords, i, &(line.point[i].z)); */
    }
    msAddLineDirectly(shape, &line);
  }

  msComputeBounds(shape);

  return shape;
}

static shapeObj *msGEOSGeometry2Shape_multipolygon(GEOSGeom g)
{
  int i, j, k;
  shapeObj *shape=NULL;
  lineObj line;
  int numPoints, numRings, numPolygons;

  GEOSCoordSeq coords;
  GEOSGeom polygon, ring;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g) return NULL;
  numPolygons = GEOSGetNumGeometries_r(handle,g);

  shape = (shapeObj *) malloc(sizeof(shapeObj));
  msInitShape(shape);
  shape->type = MS_SHAPE_POLYGON;
  shape->geometry = (GEOSGeom) g;

  for(k=0; k<numPolygons; k++) { /* for each polygon */
    polygon = (GEOSGeom) GEOSGetGeometryN_r(handle,g, k);

    /* exterior ring */
    ring = (GEOSGeom) GEOSGetExteriorRing_r(handle,polygon);
    numPoints = GEOSGetNumCoordinates_r(handle,ring);
    coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,ring);

    line.point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
    line.numpoints = numPoints;

    for(i=0; i<numPoints; i++) {
      GEOSCoordSeq_getX_r(handle,coords, i, &(line.point[i].x));
      GEOSCoordSeq_getY_r(handle,coords, i, &(line.point[i].y));
      /* GEOSCoordSeq_getZ(coords, i, &(line.point[i].z)); */
    }
    msAddLineDirectly(shape, &line);

    /* interior rings */
    numRings = GEOSGetNumInteriorRings_r(handle,polygon);

    for(j=0; j<numRings; j++) {
      ring = (GEOSGeom) GEOSGetInteriorRingN_r(handle,polygon, j);
      if(GEOSisRing_r(handle,ring) != 1) continue; /* skip it */

      numPoints = GEOSGetNumCoordinates_r(handle,ring);
      coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,ring);

      line.point = (pointObj *) malloc(sizeof(pointObj)*numPoints);
      line.numpoints = numPoints;

      for(i=0; i<numPoints; i++) {
        GEOSCoordSeq_getX_r(handle,coords, i, &(line.point[i].x));
        GEOSCoordSeq_getY_r(handle,coords, i, &(line.point[i].y));
        /* GEOSCoordSeq_getZ(coords, i, &(line.point[i].z)); */
      }
      msAddLineDirectly(shape, &line);
    }
  } /* next polygon */

  msComputeBounds(shape);

  return shape;
}

shapeObj *msGEOSGeometry2Shape(GEOSGeom g)
{
  int type;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!g)
    return NULL; /* a NULL geometry generates a NULL shape */

  type = GEOSGeomTypeId_r(handle,g);
  switch(type) {
    case GEOS_POINT:
      return msGEOSGeometry2Shape_point(g);
      break;
    case GEOS_MULTIPOINT:
      return msGEOSGeometry2Shape_multipoint(g);
      break;
    case GEOS_LINESTRING:
      return msGEOSGeometry2Shape_line(g);
      break;
    case GEOS_MULTILINESTRING:
      return msGEOSGeometry2Shape_multiline(g);
      break;
    case GEOS_POLYGON:
      return msGEOSGeometry2Shape_polygon(g);
      break;
    case GEOS_MULTIPOLYGON:
      return msGEOSGeometry2Shape_multipolygon(g);
      break;
    case GEOS_GEOMETRYCOLLECTION:
      if (!GEOSisEmpty_r(handle,g))
      {
        shapeObj* shape = (shapeObj *) malloc(sizeof(shapeObj));
        msInitShape(shape);
        shape->type = MS_SHAPE_LINE;
        shape->geometry = (GEOSGeom) g;

        const int numGeoms = GEOSGetNumGeometries_r(handle,g);
        for(int i = 0; i < numGeoms; i++) { /* for each geometry */
           shapeObj* shape2 = msGEOSGeometry2Shape((GEOSGeom)GEOSGetGeometryN_r(handle,g, i));
           if (shape2) {
              for (int j = 0; j < shape2->numlines; j++)
                 msAddLineDirectly(shape, &shape2->line[j]);
              shape2->numlines = 0;
              shape2->geometry = NULL; /* not owned */
              msFreeShape(shape2);
           }
        }
        msComputeBounds(shape);
        return shape;
      }
      break;
    default:
      msSetError(MS_GEOSERR, "Unsupported GEOS geometry type (%d).", "msGEOSGeometry2Shape()", type);
  }
  return NULL;
}
#endif

/*
** Maintenence functions exposed to MapServer/MapScript.
*/

void msGEOSFreeGeometry(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g=NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape || !shape->geometry)
    return;

  g = (GEOSGeom) shape->geometry;
  GEOSGeom_destroy_r(handle,g);
  shape->geometry = NULL;
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSFreeGEOSGeom()");
  return;
#endif
}

/*
** WKT input and output functions
*/
shapeObj *msGEOSShapeFromWKT(const char *wkt)
{
#ifdef USE_GEOS
  GEOSGeom g;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!wkt)
    return NULL;

  g = GEOSGeomFromWKT_r(handle,wkt);
  if(!g) {
    msSetError(MS_GEOSERR, "Error reading WKT geometry \"%s\".", "msGEOSShapeFromWKT()", wkt);
    return NULL;
  } else {
    return msGEOSGeometry2Shape(g);
  }

#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSShapeFromWKT()");
  return NULL;
#endif
}

/* Return should be freed with msGEOSFreeWKT */
char *msGEOSShapeToWKT(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape)
    return NULL;

  /* if we have a geometry, we should update it*/
  msGEOSFreeGeometry(shape);

  shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g = (GEOSGeom) shape->geometry;
  if(!g) return NULL;

  return GEOSGeomToWKT_r(handle,g);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSShapeToWKT()");
  return NULL;
#endif
}

void msGEOSFreeWKT(char* pszGEOSWKT)
{
#ifdef USE_GEOS
  GEOSContextHandle_t handle = msGetGeosContextHandle();
#if GEOS_VERSION_MAJOR > 3 || (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 2)
  GEOSFree_r(handle,pszGEOSWKT);
#endif
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSFreeWKT()");
#endif
}

shapeObj *msGEOSOffsetCurve(shapeObj *p, double offset) {
#if defined USE_GEOS && (GEOS_VERSION_MAJOR > 3 || (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 3))
  int typeChanged = 0;
  GEOSGeom g1, g2 = NULL;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!p) 
    return NULL;

  /*
   * GEOSOffsetCurve_r() uses BufferBuilder.bufferLineSingleSided(), which
   * works with lines, naturally. In order to allow offsets for a MapServer
   * polygonObj, it has to be processed as line and afterwards reverted.
   */
  if(p->type == MS_SHAPE_POLYGON) {
    p->type = MS_SHAPE_LINE;
    typeChanged = 1;
    msGEOSFreeGeometry(p);
  }

  if(typeChanged || !p->geometry)
    p->geometry = (GEOSGeom) msGEOSShape2Geometry(p);

  g1 = (GEOSGeom) p->geometry;
  if(!g1) return NULL;
  
  if (GEOSGeomTypeId_r(handle,g1) == GEOS_MULTILINESTRING)
  {
    GEOSGeom *lines = malloc(p->numlines*sizeof(GEOSGeom));
    if (!lines) return NULL;
    for(int i=0; i<p->numlines; i++)
    {
      lines[i] = GEOSOffsetCurve_r(handle, GEOSGetGeometryN_r(handle,g1,i),
                                   offset, 4, GEOSBUF_JOIN_MITRE, fabs(offset*1.5));
    }
    g2 = GEOSGeom_createCollection_r(handle,GEOS_MULTILINESTRING, lines, p->numlines);
    free(lines);
  }
  else
  {
    g2 = GEOSOffsetCurve_r(handle,g1, offset, 4, GEOSBUF_JOIN_MITRE, fabs(offset*1.5));
  }

  /*
   * Undo change of geometry type. We won't re-create the geos gemotry here,
   * it's up to each geos function to create it.
   */
  if(typeChanged) {
    msGEOSFreeGeometry(p);
    p->type = MS_SHAPE_POLYGON;
  }

  if (g2)
    return msGEOSGeometry2Shape(g2);

  return NULL;
#else
  msSetError(MS_GEOSERR, "GEOS Offset Curve support is not available.", "msGEOSingleSidedBuffer()");
  return NULL;
#endif
}

/*
** Analytical functions exposed to MapServer/MapScript.
*/

shapeObj *msGEOSBuffer(shapeObj *shape, double width)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape)
    return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);

  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSBuffer_r(handle,g1, width, 30);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSBuffer()");
  return NULL;
#endif
}

shapeObj *msGEOSSimplify(shapeObj *shape, double tolerance)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape)
    return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);

  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSSimplify_r(handle,g1, tolerance);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS Simplify support is not available.", "msGEOSSimplify()");
  return NULL;
#endif
}

shapeObj *msGEOSTopologyPreservingSimplify(shapeObj *shape, double tolerance)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape)
    return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);

  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSTopologyPreserveSimplify_r(handle,g1, tolerance);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS Simplify support is not available.", "msGEOSTopologyPreservingSimplify()");
  return NULL;
#endif
}

shapeObj *msGEOSConvexHull(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSConvexHull_r(handle,g1);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSConvexHull()");
  return NULL;
#endif
}

shapeObj *msGEOSBoundary(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSBoundary_r(handle,g1);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSBoundary()");
  return NULL;
#endif
}

pointObj *msGEOSGetCentroid(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSCoordSeq coords;
  pointObj *point;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSGetCentroid_r(handle,g1);
  if (!g2) return NULL;

  point = (pointObj *) malloc(sizeof(pointObj));

  coords = (GEOSCoordSeq) GEOSGeom_getCoordSeq_r(handle,g2);

  GEOSCoordSeq_getX_r(handle,coords, 0, &(point->x));
  GEOSCoordSeq_getY_r(handle,coords, 0, &(point->y));
  /* GEOSCoordSeq_getZ(coords, 0, &(point->z)); */

  GEOSGeom_destroy_r(handle, g2);

  return point;
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSGetCentroid()");
  return NULL;
#endif
}

shapeObj *msGEOSUnion(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2, g3;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return NULL;

  if(!shape1->geometry) /* if no geometry for the shape then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return NULL;

  if(!shape2->geometry) /* if no geometry for the shape then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return NULL;

  g3 = GEOSUnion_r(handle,g1, g2);
  return msGEOSGeometry2Shape(g3);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSUnion()");
  return NULL;
#endif
}

shapeObj *msGEOSIntersection(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2, g3;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return NULL;

  if(!shape1->geometry) /* if no geometry for the shape then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return NULL;

  if(!shape2->geometry) /* if no geometry for the shape then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return NULL;

  g3 = GEOSIntersection_r(handle,g1, g2);
  return msGEOSGeometry2Shape(g3);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSIntersection()");
  return NULL;
#endif
}

shapeObj *msGEOSDifference(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2, g3;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return NULL;

  if(!shape1->geometry) /* if no geometry for the shape then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return NULL;

  if(!shape2->geometry) /* if no geometry for the shape then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return NULL;

  g3 = GEOSDifference_r(handle,g1, g2);
  return msGEOSGeometry2Shape(g3);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSDifference()");
  return NULL;
#endif
}

shapeObj *msGEOSSymDifference(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2, g3;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return NULL;

  if(!shape1->geometry) /* if no geometry for the shape then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return NULL;

  if(!shape2->geometry) /* if no geometry for the shape then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return NULL;

  g3 = GEOSSymDifference_r(handle,g1, g2);
  return msGEOSGeometry2Shape(g3);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSSymDifference()");
  return NULL;
#endif
}

shapeObj *msGEOSLineMerge(shapeObj *shape)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return NULL;
  if(shape->type != MS_SHAPE_LINE) return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSLineMerge_r(handle, g1);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSLineMerge()");
  return NULL;
#endif
}

shapeObj *msGEOSVoronoiDiagram(shapeObj *shape, double tolerance, int onlyEdges)
{
#if defined(USE_GEOS) && (GEOS_VERSION_MAJOR > 3 || (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 5))
  GEOSGeom g1, g2;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return NULL;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g1 = (GEOSGeom) shape->geometry;
  if(!g1) return NULL;

  g2 = GEOSVoronoiDiagram_r(handle, g1, NULL, tolerance, onlyEdges);
  return msGEOSGeometry2Shape(g2);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available or GEOS version is not 3.5 or higher.", "msGEOSVoronoiDiagram()");
  return NULL;
#endif
}

static int keepEdge(lineObj *segment, shapeObj *polygon)
{
  int i,j;

  if(segment->numpoints<2) return MS_FALSE;
  if(msIntersectPointPolygon(&segment->point[0], polygon) != MS_TRUE) return MS_FALSE;
  if(msIntersectPointPolygon(&segment->point[1], polygon) != MS_TRUE) return MS_FALSE;

  for(i=0; i<polygon->numlines; i++)
    for(j=1; j<polygon->line[i].numpoints; j++)
      if(msIntersectSegments(&(segment->point[0]), &(segment->point[1]), &(polygon->line[i].point[j-1]), &(polygon->line[i].point[j])) ==  MS_TRUE)
        return(MS_FALSE);

  return(MS_TRUE);
}

#define ARE_SAME_POINTS(a,b) (((a).x!=(b).x || (a).y!=(b).y)?MS_FALSE:MS_TRUE)

// returns the index of the node, we use z to store a count of points at the same coordinate
static int buildNodes(multipointObj *nodes, pointObj *point)
{
  int i;

  for(i=0; i<nodes->numpoints; i++) {
    if(ARE_SAME_POINTS(nodes->point[i], *point)) { // found it
      nodes->point[i].z++;
      return i;
    }
  }

  // not found, add it
  nodes->point[i].x = point->x;
  nodes->point[i].y = point->y;
  nodes->point[i].z = 1;
  nodes->numpoints++;

  return i;
}

shapeObj *msGEOSCenterline(shapeObj *shape)
{
#if defined(USE_GEOS) && (GEOS_VERSION_MAJOR > 3 || (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 5))
  int i;
  shapeObj *shape2=NULL;

  multipointObj nodes;
  graphObj *graph;

  int *path=NULL; // array of node indexes
  int path_size=0;
  double path_dist=-1, max_path_dist=-1;

  if(!shape) return NULL;
  if(shape->type != MS_SHAPE_POLYGON) {
    msSetError(MS_GEOSERR, "Centerlines can only be computed for polygon shapes.", "msGEOSCenterline()");
    return NULL;
  }

  shape2 = msGEOSVoronoiDiagram(shape, 0.0, MS_TRUE);
  if(!shape2) {
    msSetError(MS_GEOSERR, "Voronoi diagram generation failed.", "msGEOSCenterline()");
    return NULL;
  }

  // process the edges and build a graph representation
  nodes.point = (pointObj *) malloc(shape2->numlines*sizeof(pointObj)*2);
  nodes.numpoints = 0;
  if(!nodes.point) {
    msFreeShape(shape2);
    free(shape2);
    return NULL;
  }

  graph = msCreateGraph(shape2->numlines*2);
  if(!graph) {
    msFreeShape(shape2);
    free(shape2);
    free(nodes.point);
    return NULL;
  }

  for(i=0; i<shape2->numlines; i++) {
    if(keepEdge(&shape2->line[i], shape) == MS_TRUE) {
      int src = buildNodes(&nodes, &shape2->line[i].point[0]);
      int dest = buildNodes(&nodes, &shape2->line[i].point[1]);
      msGraphAddEdge(graph, src, dest, msDistancePointToPoint(&shape2->line[i].point[0], &shape2->line[i].point[1]));
    }
  }
  msFreeShape(shape2); // done with voronoi geometry, shape2 is still allocated though, just empty
  shape2->type = MS_SHAPE_LINE; // will fill with centerline

  if(nodes.numpoints == 0) {
    msSetError(MS_GEOSERR, "Centerline generation failed, try densifying the shapes.", "msGEOSCenterline()");
    free(shape2);
    msFreeGraph(graph);
    free(nodes.point);
    return NULL;
  }

  // step through edge nodes (z=1)
  for(i=0; i<nodes.numpoints; i++) {
    if(nodes.point[i].z != 1) continue; // skip

    if(!path) { // first one, keep this path
      path = msGraphGetLongestShortestPath(graph, i, &path_size, &path_dist);
      max_path_dist = path_dist;
    } else {
      int *tmp_path=NULL;
      int tmp_path_size=0;
      double tmp_path_dist=-1;

      if(i == path[path_size-1]) continue; // skip, graph is bi-directional so it can't be any longer
      tmp_path = msGraphGetLongestShortestPath(graph, i, &tmp_path_size, &tmp_path_dist);
      if(tmp_path_dist > max_path_dist) {
        free(path);
        path = tmp_path;
        path_size = tmp_path_size;
        path_dist = tmp_path_dist;
        max_path_dist = tmp_path_dist;
      } else { // skip path
        msFree(tmp_path);
      }
    }
  }
  msFreeGraph(graph); // done with graph

  // transform the path into a shape
  if(!path) {
    msSetError(MS_GEOSERR, "Centerline generation failed.", "msGEOSCenterline()");
    free(shape2);
    free(nodes.point);
    return NULL;
  } else {
    lineObj line;
    line.point = (pointObj *) malloc(path_size*sizeof(pointObj));
    if(!line.point) {
      free(shape2);
      free(path);
      free(nodes.point);
      return NULL;
    }
    line.numpoints = path_size;

    for(i=0; i<path_size; i++) {
      line.point[i].x = nodes.point[path[i]].x;
      line.point[i].y = nodes.point[path[i]].y;
    }
    msAddLineDirectly(shape2, &line);
  }

  free(path); // clean up
  free(nodes.point);

  return shape2;
#else
  msSetError(MS_GEOSERR, "GEOS support is not available or GEOS version is not 3.5 or higher.", "msGEOSCenterline()");
  return NULL;
#endif
}

/*
** Binary predicates exposed to MapServer/MapScript
*/

/*
** Does shape1 contain shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSContains(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = shape2->geometry;
  if(!g2) return -1;

  result = GEOSContains_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSContains()");
  return -1;
#endif
}

/*
** Does shape1 overlap shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSOverlaps(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = shape2->geometry;
  if(!g2) return -1;

  result = GEOSOverlaps_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSOverlaps()");
  return -1;
#endif
}

/*
** Is shape1 within shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSWithin(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = shape2->geometry;
  if(!g2) return -1;

  result = GEOSWithin_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSWithin()");
  return -1;
#endif
}

/*
** Does shape1 cross shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSCrosses(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = shape2->geometry;
  if(!g2) return -1;

  result = GEOSCrosses_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSCrosses()");
  return -1;
#endif
}

/*
** Does shape1 intersect shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSIntersects(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return -1;

  result = GEOSIntersects_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  if(!shape1 || !shape2)
    return -1;

  switch(shape1->type) { /* todo: deal with point shapes */
    case(MS_SHAPE_LINE):
      switch(shape2->type) {
        case(MS_SHAPE_LINE):
          return msIntersectPolylines(shape1, shape2);
        case(MS_SHAPE_POLYGON):
          return msIntersectPolylinePolygon(shape1, shape2);
      }
      break;
    case(MS_SHAPE_POLYGON):
      switch(shape2->type) {
        case(MS_SHAPE_LINE):
          return msIntersectPolylinePolygon(shape2, shape1);
        case(MS_SHAPE_POLYGON):
          return msIntersectPolygons(shape1, shape2);
      }
      break;
  }

  return -1;
#endif
}

/*
** Does shape1 touch shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSTouches(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return -1;

  result = GEOSTouches_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSTouches()");
  return -1;
#endif
}

/*
** Does shape1 equal shape2, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSEquals(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return -1;

  result = GEOSEquals_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSEquals()");
  return -1;
#endif
}

/*
** Are shape1 and shape2 disjoint, returns MS_TRUE/MS_FALSE or -1 for an error.
*/
int msGEOSDisjoint(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return -1;

  result = GEOSDisjoint_r(handle,g1, g2);
  return ((result==2) ? -1 : result);
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSDisjoint()");
  return -1;
#endif
}

/*
** Useful misc. functions that return -1 on failure.
*/
double msGEOSArea(shapeObj *shape)
{
#if defined(USE_GEOS) && defined(GEOS_CAPI_VERSION_MAJOR) && defined(GEOS_CAPI_VERSION_MINOR) && (GEOS_CAPI_VERSION_MAJOR > 1 || GEOS_CAPI_VERSION_MINOR >= 1)
  GEOSGeom g;
  double area;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return -1;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g = (GEOSGeom) shape->geometry;
  if(!g) return -1;

  result = GEOSArea_r(handle,g, &area);
  return  ((result==0) ? -1 : area);
#elif defined(USE_GEOS)
  msSetError(MS_GEOSERR, "GEOS support enabled, but old version lacks GEOSArea().", "msGEOSArea()");
  return -1;
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSArea()");
  return -1;
#endif
}

double msGEOSLength(shapeObj *shape)
{
#if defined(USE_GEOS) && defined(GEOS_CAPI_VERSION_MAJOR) && defined(GEOS_CAPI_VERSION_MINOR) && (GEOS_CAPI_VERSION_MAJOR > 1 || GEOS_CAPI_VERSION_MINOR >= 1)

  GEOSGeom g;
  double length;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape) return -1;

  if(!shape->geometry) /* if no geometry for the shape then build one */
    shape->geometry = (GEOSGeom) msGEOSShape2Geometry(shape);
  g = (GEOSGeom) shape->geometry;
  if(!g) return -1;

  result = GEOSLength_r(handle,g, &length);
  return  ((result==0) ? -1 : length);
#elif defined(USE_GEOS)
  msSetError(MS_GEOSERR, "GEOS support enabled, but old version lacks GEOSLength().", "msGEOSLength()");
  return -1;
#else
  msSetError(MS_GEOSERR, "GEOS support is not available.", "msGEOSLength()");
  return -1;
#endif
}

double msGEOSDistance(shapeObj *shape1, shapeObj *shape2)
{
#ifdef USE_GEOS
  GEOSGeom g1, g2;
  double distance;
  int result;
  GEOSContextHandle_t handle = msGetGeosContextHandle();

  if(!shape1 || !shape2)
    return -1;

  if(!shape1->geometry) /* if no geometry for shape1 then build one */
    shape1->geometry = (GEOSGeom) msGEOSShape2Geometry(shape1);
  g1 = (GEOSGeom) shape1->geometry;
  if(!g1) return -1;

  if(!shape2->geometry) /* if no geometry for shape2 then build one */
    shape2->geometry = (GEOSGeom) msGEOSShape2Geometry(shape2);
  g2 = (GEOSGeom) shape2->geometry;
  if(!g2) return -1;

  result = GEOSDistance_r(handle,g1, g2, &distance);
  return ((result==0) ? -1 : distance);
#else
  return msDistanceShapeToShape(shape1, shape2); /* fall back on brute force method (for MapScript) */
#endif
}
