
/****************************************************************
 *
 * MODULE:       v.in.ogr
 * 
 * AUTHOR(S):    Radim Blazek
 *               
 * PURPOSE:      Import OGR vectors
 *               
 * COPYRIGHT:    (C) 2001 by the GRASS Development Team
 *
 *               This program is free software under the 
 *               GNU General Public License (>=v2). 
 *               Read the file COPYING that comes with GRASS
 *               for details.
 *
 **************************************************************/

#include <stdlib.h>
#include <string.h>
#include <grass/gis.h>
#include <grass/dbmi.h>
#include <grass/vector.h>
#include <grass/glocale.h>
#include "ogr_api.h"
#include "global.h"

int split_line(struct Map_info *Map, int otype, struct line_pnts *Points,
	       struct line_cats *Cats);

/* Add categories to centroids inside polygon */
int
centroid(OGRGeometryH hGeomAny, CENTR * Centr, struct spatial_index *Sindex,
	 int field, int cat, double min_area, int type)
{
    int i, valid_isles, j, np, nr, ret;
    static int first = 1;
    static struct line_pnts *Points;
    struct line_pnts **IPoints;
    static struct line_cats *BCats, *Cats;
    OGRGeometryH hGeom;
    OGRwkbGeometryType eType;
    OGRGeometryH hRing;
    double size;
    static struct ilist *List;
    struct bound_box box;

    G_debug(3, "centroid() cat = %d", cat);

    if (first) {
	Points = Vect_new_line_struct();
	BCats = Vect_new_cats_struct();
	Cats = Vect_new_cats_struct();
	List = Vect_new_list();
	first = 0;
    }
    else {
	Vect_reset_line(Points);
	Vect_reset_cats(Cats);
	Vect_reset_cats(BCats);
	Vect_cat_set(Cats, field, cat);
    }

    hGeom = hGeomAny;

#if GDAL_VERSION_NUM >= 2000000
    if (OGR_G_HasCurveGeometry(hGeom, 0)) {
	G_debug(2, "Approximating curves in a '%s'",
		OGR_G_GetGeometryName(hGeom));

	/* The ownership of the returned geometry belongs to the caller. */
	hGeom = OGR_G_GetLinearGeometry(hGeom, 0, NULL);
    }
#endif

    eType = wkbFlatten(OGR_G_GetGeometryType(hGeom));

    if (eType == wkbPolygon) {
	nr = OGR_G_GetGeometryCount(hGeom);

	G_debug(3, "polygon: %d rings", nr);

	/* SFS: 1 exterior boundary and 0 or more interior boundaries.
	 *  So I hope that exterior is the first one, even if it is not explicitly told  */

	/* Area */
	hRing = OGR_G_GetGeometryRef(hGeom, 0);
        if (hRing == NULL) {
            G_warning(_("Skipping empty geometry feature %d"), cat);
	    return 0;
        }
	np = OGR_G_GetPointCount(hRing);
	Vect_reset_line(Points);
	for (j = 0; j < np; j++) {
	    Vect_append_point(Points, OGR_G_GetX(hRing, j),
			      OGR_G_GetY(hRing, j), OGR_G_GetZ(hRing, j));
	}

	/* Degenerate is ignored */
	if (Points->n_points < 4)
	    return 0;

	/* Small areas ignored because boundaries are not imported */
	size = G_area_of_polygon(Points->x, Points->y, Points->n_points);
	if (size < min_area)
	    return 0;

	/* Isles */
	IPoints =
	    (struct line_pnts **)G_malloc((nr - 1) *
					  sizeof(struct line_pnts *));
	valid_isles = 0;
	for (i = 1; i < nr; i++) {

	    hRing = OGR_G_GetGeometryRef(hGeom, i);
	    if ((np = OGR_G_GetPointCount(hRing)) > 0) {
		IPoints[valid_isles] = Vect_new_line_struct();
		for (j = 0; j < np; j++) {
		    Vect_append_point(IPoints[valid_isles],
				      OGR_G_GetX(hRing, j),
				      OGR_G_GetY(hRing, j),
				      OGR_G_GetZ(hRing, j));
		}
		size =
		    G_area_of_polygon(IPoints[valid_isles]->x,
				      IPoints[valid_isles]->y,
				      IPoints[valid_isles]->n_points);
		if (size < min_area)
		    Vect_destroy_line_struct(IPoints[valid_isles]);
		else
		    valid_isles++;
	    }
	}

	/* Find centroids */
	if (Points->n_points >= 4) {
	    int centr, in;
	    double x, y;

	    Vect_line_box(Points, &box);
	    /* centroid's z is set to zero */
	    box.T = box.B = 0;
	    Vect_spatial_index_select(Sindex, &box, List);

	    for (i = 0; i < List->n_values; i++) {
		centr = List->value[i];
		x = Centr[centr].x;
		y = Centr[centr].y;
		ret = Vect_point_in_poly(x, y, Points);
		if (ret == 0)
		    continue;	/* outside */

		in = 1;
		for (j = 0; j < valid_isles; j++) {
		    ret = Vect_point_in_poly(x, y, IPoints[j]);
		    if (ret > 0) {	/* centroid in inner ring */
			in = 0;
			break;	/* inside isle */
		    }
		}
		if (!in)
		    continue;

		G_debug(3, "Centroid %d : layer %d cat %d", centr, field,
			cat);
		Vect_cat_set(Centr[centr].cats, field, cat);
	    }
	}

	for (i = 0; i < valid_isles; i++) {
	    Vect_destroy_line_struct(IPoints[i]);
	}
	G_free(IPoints);
    }

    /* I did not test this because I did not have files of these types */
    else if (eType == wkbGeometryCollection || eType == wkbMultiPolygon) {
	G_debug(3, "GeometryCollection or MultiPolygon/LineString/Point");
	nr = OGR_G_GetGeometryCount(hGeom);
	for (i = 0; i < nr; i++) {
	    hRing = OGR_G_GetGeometryRef(hGeom, i);

	    ret = centroid(hRing, Centr, Sindex, field, cat, min_area, type);
	}
    }

    /* destroy non-curve version of a curve geometry */
    if (hGeom != hGeomAny)
	OGR_G_DestroyGeometry(hGeom);

    return 0;
}

/* count polygons and isles */
int poly_count(OGRGeometryH hGeomAny, int line2boundary)
{
    int i, nr, ret;
    OGRGeometryH hGeom;
    OGRwkbGeometryType eType;
    OGRGeometryH hRing;

    hGeom = hGeomAny;

#if GDAL_VERSION_NUM >= 2000000
    if (OGR_G_HasCurveGeometry(hGeom, 0)) {
	G_debug(2, "Approximating curves in a '%s'",
		OGR_G_GetGeometryName(hGeom));

	/* The ownership of the returned geometry belongs to the caller. */
	hGeom = OGR_G_GetLinearGeometry(hGeom, 0, NULL);
    }
#endif

    eType = wkbFlatten(OGR_G_GetGeometryType(hGeom));

    if (eType == wkbPolygon) {
	G_debug(5, "Polygon");
	nr = OGR_G_GetGeometryCount(hGeom);
	n_polygon_boundaries += nr;

    }
    else if (eType == wkbGeometryCollection || eType == wkbMultiPolygon) {
	G_debug(5, "GeometryCollection or MultiPolygon");
	nr = OGR_G_GetGeometryCount(hGeom);
	for (i = 0; i < nr; i++) {
	    hRing = OGR_G_GetGeometryRef(hGeom, i);

	    ret = poly_count(hRing, line2boundary);
	    if (ret == -1) {
		G_warning(_("Unable to read part of geometry"));
	    }
	}
    }

    if (!line2boundary) {
	/* destroy non-curve version of a curve geometry */
	if (hGeom != hGeomAny)
	    OGR_G_DestroyGeometry(hGeom);

	return 0;
    }

    if (eType == wkbLineString) {
	G_debug(5, "Polygon");
	n_polygon_boundaries++;

    }
    else if (eType == wkbGeometryCollection || eType == wkbMultiLineString) {
	G_debug(5, "GeometryCollection or MultiPolygon");
	nr = OGR_G_GetGeometryCount(hGeom);
	for (i = 0; i < nr; i++) {
	    hRing = OGR_G_GetGeometryRef(hGeom, i);

	    ret = poly_count(hRing, line2boundary);
	    if (ret == -1) {
		G_warning(_("Unable to read part of geometry"));
	    }
	}
    }

    G_debug(1, "poly_count(): n_poly_boundaries=%d", n_polygon_boundaries);

    /* destroy non-curve version of a curve geometry */
    if (hGeom != hGeomAny)
	OGR_G_DestroyGeometry(hGeom);

    return 0;
}

/* Write geometry to output map */
int
geom(OGRGeometryH hGeomAny, struct Map_info *Map, int field, int cat,
     double min_area, int type, int mk_centr)
{
    int i, valid_isles, j, np, nr, ret, otype;
    static int first = 1;
    static struct line_pnts *Points;
    struct line_pnts **IPoints;
    static struct line_cats *BCats, *Cats;
    OGRGeometryH hGeom;
    OGRwkbGeometryType eType;
    OGRGeometryH hRing;
    double x, y;
    double size;

    G_debug(3, "geom() cat = %d", cat);

    if (first) {
	Points = Vect_new_line_struct();
	BCats = Vect_new_cats_struct();
	Cats = Vect_new_cats_struct();
	first = 0;
    }
    Vect_reset_line(Points);
    Vect_reset_cats(Cats);
    Vect_reset_cats(BCats);
    Vect_cat_set(Cats, field, cat);

    hGeom = hGeomAny;

#if GDAL_VERSION_NUM >= 2000000
    if (OGR_G_HasCurveGeometry(hGeom, 0)) {
	G_debug(2, "Approximating curves in a '%s'",
		OGR_G_GetGeometryName(hGeom));

	/* The ownership of the returned geometry belongs to the caller. */
	hGeom = OGR_G_GetLinearGeometry(hGeom, 0, NULL);
    }
#endif

    eType = wkbFlatten(OGR_G_GetGeometryType(hGeom));

    if (eType == wkbPoint) {
	if ((np = OGR_G_GetPointCount(hGeom)) == 0) {
	    G_warning(_("Skipping empty geometry feature %d"), cat);
	    return 0;
	}

	Vect_append_point(Points, OGR_G_GetX(hGeom, 0), OGR_G_GetY(hGeom, 0),
			  OGR_G_GetZ(hGeom, 0));
	if (type & GV_CENTROID)
	    otype = GV_CENTROID;
	else
	    otype = GV_POINT;
	Vect_write_line(Map, otype, Points, Cats);
    }
    else if (eType == wkbLineString) {
	if ((np = OGR_G_GetPointCount(hGeom)) == 0) {
	    G_warning(_("Skipping empty geometry feature %d"), cat);
	    return 0;
	}

	for (i = 0; i < np; i++) {
	    Vect_append_point(Points, OGR_G_GetX(hGeom, i),
			      OGR_G_GetY(hGeom, i), OGR_G_GetZ(hGeom, i));
	}
	Vect_line_prune(Points);
	if (type & GV_BOUNDARY)
	    otype = GV_BOUNDARY;
	else
	    otype = GV_LINE;

	if (split_distance > 0 && otype == GV_BOUNDARY)
	    split_line(Map, otype, Points, Cats);
	else
	    Vect_write_line(Map, otype, Points, Cats);
    }

    else if (eType == wkbPolygon) {
	G_debug(4, "\tPolygon");

	/* SFS: 1 exterior boundary and 0 or more interior boundaries.
	 *  So I hope that exterior is the first one, even if it is not explicitly told  */

	/* Area */
	hRing = OGR_G_GetGeometryRef(hGeom, 0);
	if (hRing == NULL || (np = OGR_G_GetPointCount(hRing)) == 0) {
            G_warning(_("Skipping empty geometry feature %d"), cat);
	    return 0;
	}

	nr = OGR_G_GetGeometryCount(hGeom);

	Vect_reset_line(Points);
	for (j = 0; j < np; j++) {
	    Vect_append_point(Points, OGR_G_GetX(hRing, j),
			      OGR_G_GetY(hRing, j), OGR_G_GetZ(hRing, j));
	}
	Vect_line_prune(Points);

	/* Degenerate is not ignored because it may be useful to see where it is,
	 * but may be eliminated by min_area option */
	if (Points->n_points < 4)
	    G_warning(_("Feature (cat %d): degenerated polygon (%d vertices)"),
		      cat, Points->n_points);

	size = G_area_of_polygon(Points->x, Points->y, Points->n_points);
	if (size < min_area) {
	    G_debug(2, "\tArea size %.1e, area not imported", size);
	    return 0;
	}

	n_polygons++;

	if (type & GV_LINE)
	    otype = GV_LINE;
	else
	    otype = GV_BOUNDARY;

	if (split_distance > 0 && otype == GV_BOUNDARY)
	    split_line(Map, otype, Points, BCats);
	else
	    Vect_write_line(Map, otype, Points, BCats);

	/* Isles */
	IPoints =
	    (struct line_pnts **)G_malloc((nr - 1) *
					  sizeof(struct line_pnts *));
	valid_isles = 0;
	for (i = 1; i < nr; i++) {
	    G_debug(3, "\tInner ring %d", i);

	    hRing = OGR_G_GetGeometryRef(hGeom, i);

	    if ((np = OGR_G_GetPointCount(hRing)) == 0) {
		G_warning(_("Skipping empty geometry feature %d"), cat);
	    }
	    else {
		IPoints[valid_isles] = Vect_new_line_struct();

		for (j = 0; j < np; j++) {
		    Vect_append_point(IPoints[valid_isles],
				      OGR_G_GetX(hRing, j),
				      OGR_G_GetY(hRing, j),
				      OGR_G_GetZ(hRing, j));
		}
		Vect_line_prune(IPoints[valid_isles]);

		if (IPoints[valid_isles]->n_points < 4)
		    G_warning(_("Degenerate island (%d vertices)"),
			      IPoints[i - 1]->n_points);

		size =
		    G_area_of_polygon(IPoints[valid_isles]->x,
				      IPoints[valid_isles]->y,
				      IPoints[valid_isles]->n_points);
		if (size < min_area) {
		    G_debug(2, "\tIsland size %.1e, island not imported",
			      size);
		}
		else {
		    if (type & GV_LINE)
			otype = GV_LINE;
		    else
			otype = GV_BOUNDARY;
		    if (split_distance > 0 && otype == GV_BOUNDARY)
			split_line(Map, otype, IPoints[valid_isles], BCats);
		    else
			Vect_write_line(Map, otype, IPoints[valid_isles],
					BCats);
		}
		valid_isles++;
	    }
	}			/* inner rings done */

	/* Centroid */
	/* Vect_get_point_in_poly_isl() would fail for degenerate polygon */
	if (mk_centr) {
	    if (Points->n_points >= 4) {
		ret =
		    Vect_get_point_in_poly_isl(Points, (const struct line_pnts **)IPoints,
					       valid_isles, &x, &y);
		if (ret == -1) {
		    G_warning(_("Unable calculate centroid"));
		}
		else {
		    Vect_reset_line(Points);
		    Vect_append_point(Points, x, y, 0.0);
		    if (type & GV_POINT)
			otype = GV_POINT;
		    else
			otype = GV_CENTROID;
		    Vect_write_line(Map, otype, Points, Cats);
		}
	    }
	    else if (Points->n_points > 0) {
		if (Points->n_points >= 2) {
		    /* center of 1. segment ( 2. point is not best for 3 vertices as 3. may be the same as 1.) */
		    x = (Points->x[0] + Points->x[1]) / 2;
		    y = (Points->y[0] + Points->y[1]) / 2;
		}
		else {		/* one point */
		    x = Points->x[0];
		    y = Points->y[0];
		}
		Vect_reset_line(Points);
		Vect_append_point(Points, x, y, 0.0);
		if (type & GV_POINT)
		    otype = GV_POINT;
		else
		    otype = GV_CENTROID;
		Vect_write_line(Map, otype, Points, Cats);
	    }
	    else {		/* 0 points */
		G_warning(_("No centroid written for polygon with 0 vertices"));
	    }
	}

	for (i = 0; i < valid_isles; i++) {
	    Vect_destroy_line_struct(IPoints[i]);
	}
	G_free(IPoints);
    }

    /* I did not test this because I did not have files of these types */
    else if (eType == wkbGeometryCollection
	     || eType == wkbMultiPolygon
	     || eType == wkbMultiLineString || eType == wkbMultiPoint) {
	G_debug(4, "\tGeometryCollection or MultiPolygon/LineString/Point");
	nr = OGR_G_GetGeometryCount(hGeom);
	for (i = 0; i < nr; i++) {
	    hRing = OGR_G_GetGeometryRef(hGeom, i);

	    ret = geom(hRing, Map, field, cat, min_area, type, mk_centr);
	    if (ret == -1) {
		G_warning(_("Unable to write part of geometry"));
	    }
	}
    }

    else {
	G_warning(_("Skipping unsupported geometry type '%s'"), OGR_G_GetGeometryName(hGeom));
    }

    /* destroy non-curve version of a curve geometry */
    if (hGeom != hGeomAny)
	OGR_G_DestroyGeometry(hGeom);

    return 0;
}

int split_line(struct Map_info *Map, int otype, struct line_pnts *Points,
	       struct line_cats *Cats)
{
    int i;
    double dist = 0., seg_dist, dx, dy;
    struct line_pnts *OutPoints;

    /* don't write zero length boundaries */
    Vect_line_prune(Points);
    if (Points->n_points < 2)
	return 0;

    /* can't split boundaries with only 2 vertices */
    if (Points->n_points == 2) {
	Vect_line_prune(Points);
	
	if (Points->n_points < 2)
	    return 0;
	Vect_write_line(Map, otype, Points, Cats);
	return 0;
    }

    OutPoints = Vect_new_line_struct();
    Vect_append_point(OutPoints, Points->x[0], Points->y[0], Points->z[0]);
    Vect_append_point(OutPoints, Points->x[1], Points->y[1], Points->z[1]);
    dx = Points->x[1] - Points->x[0];
    dy = Points->y[1] - Points->y[0];
    dist = sqrt(dx * dx + dy * dy);

    /* trying to keep line length smaller than split_distance
     * alternative, less code: write line as soon as split_distance is exceeded */
    for (i = 2; i < Points->n_points; i++) {
	dx = Points->x[i] - Points->x[i - 1];
	dy = Points->y[i] - Points->y[i - 1];
	seg_dist = sqrt(dx * dx + dy * dy);
	dist += seg_dist;
	if (dist > split_distance) {
	    Vect_write_line(Map, otype, OutPoints, Cats);
	    Vect_reset_line(OutPoints);
	    dist = seg_dist;
	    Vect_append_point(OutPoints, Points->x[i - 1], Points->y[i - 1],
			      Points->z[i - 1]);
	}
	Vect_append_point(OutPoints, Points->x[i], Points->y[i],
			  Points->z[i]);
    }
    Vect_line_prune(OutPoints);
    
    if (OutPoints->n_points > 1)
	Vect_write_line(Map, otype, OutPoints, Cats);

    Vect_destroy_line_struct(OutPoints);

    return 0;
}
