#include "polygon.h"
#include "point.h"

/* Polygon functions */


PG_FUNCTION_INFO_V1(spherepoly_in);
PG_FUNCTION_INFO_V1(spherepoly_deg);
PG_FUNCTION_INFO_V1(spherepoly_rad);
PG_FUNCTION_INFO_V1(spherepoly_equal);
PG_FUNCTION_INFO_V1(spherepoly_equal_neg);
PG_FUNCTION_INFO_V1(spherepoly_circ);
PG_FUNCTION_INFO_V1(spherepoly_npts);
PG_FUNCTION_INFO_V1(spherepoly_area);
PG_FUNCTION_INFO_V1(spherepoly_get_point);
PG_FUNCTION_INFO_V1(spherepoly_get_array);
PG_FUNCTION_INFO_V1(spherepoly_cont_point);
PG_FUNCTION_INFO_V1(spherepoly_cont_point_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_point_com);
PG_FUNCTION_INFO_V1(spherepoly_cont_point_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_circle);
PG_FUNCTION_INFO_V1(spherepoly_cont_circle_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_circle_com);
PG_FUNCTION_INFO_V1(spherepoly_cont_circle_com_neg);
PG_FUNCTION_INFO_V1(spherecircle_cont_poly);
PG_FUNCTION_INFO_V1(spherecircle_cont_poly_neg);
PG_FUNCTION_INFO_V1(spherecircle_cont_poly_com);
PG_FUNCTION_INFO_V1(spherecircle_cont_poly_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_circle);
PG_FUNCTION_INFO_V1(spherepoly_overlap_circle_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_circle_com);
PG_FUNCTION_INFO_V1(spherepoly_overlap_circle_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_line);
PG_FUNCTION_INFO_V1(spherepoly_cont_line_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_line_com);
PG_FUNCTION_INFO_V1(spherepoly_cont_line_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_line);
PG_FUNCTION_INFO_V1(spherepoly_overlap_line_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_line_com);
PG_FUNCTION_INFO_V1(spherepoly_overlap_line_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_poly);
PG_FUNCTION_INFO_V1(spherepoly_cont_poly_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_poly_com);
PG_FUNCTION_INFO_V1(spherepoly_cont_poly_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_poly);
PG_FUNCTION_INFO_V1(spherepoly_overlap_poly_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_ellipse);
PG_FUNCTION_INFO_V1(spherepoly_cont_ellipse_neg);
PG_FUNCTION_INFO_V1(spherepoly_cont_ellipse_com);
PG_FUNCTION_INFO_V1(spherepoly_cont_ellipse_com_neg);
PG_FUNCTION_INFO_V1(sphereellipse_cont_poly);
PG_FUNCTION_INFO_V1(sphereellipse_cont_poly_neg);
PG_FUNCTION_INFO_V1(sphereellipse_cont_poly_com);
PG_FUNCTION_INFO_V1(sphereellipse_cont_poly_com_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_ellipse);
PG_FUNCTION_INFO_V1(spherepoly_overlap_ellipse_neg);
PG_FUNCTION_INFO_V1(spherepoly_overlap_ellipse_com);
PG_FUNCTION_INFO_V1(spherepoly_overlap_ellipse_com_neg);
PG_FUNCTION_INFO_V1(spheretrans_poly);
PG_FUNCTION_INFO_V1(spheretrans_poly_inverse);
PG_FUNCTION_INFO_V1(spherepoly_add_point);
PG_FUNCTION_INFO_V1(spherepoly_add_points_finalize);
PG_FUNCTION_INFO_V1(spherepoly_is_convex);
PG_FUNCTION_INFO_V1(spherepoly_from_point_array);

 /*
  * Writes "center" of a polygon into 'v'.
  *
  * v    - pointer to the center of a polygon
  * poly - pointer to the polygon
  */
static void
spherepoly_center(Vector3D *v, const SPOLY *poly)
{
	int32		i;
	Vector3D	v1,
				v2;

	v1.x = 2.0;
	v1.y = 2.0;
	v1.z = 2.0;
	v2.x = -2.0;
	v2.y = -2.0;
	v2.z = -2.0;

	for (i = 0; i < poly->npts; i++)
	{
		spoint_vector3d(v, &poly->p[i]);
		v1.x = Min(v->x, v1.x);
		v1.y = Min(v->y, v1.y);
		v1.z = Min(v->z, v1.z);
		v2.x = Max(v->x, v2.x);
		v2.y = Max(v->y, v2.y);
		v2.z = Max(v->z, v2.z);
	}

	v->x = (v1.x + v2.x) / 2.0;
	v->y = (v1.y + v2.y) / 2.0;
	v->z = (v1.z + v2.z) / 2.0;
}


/*
 * Checks if line segments cross.
 */
static bool
spherepoly_check(const SPOLY *poly)
{
	int32		i,
				k;
	SLine		sli,
				slk;
	Vector3D	v;
	SPoint		p;
	SEuler		se;
	int8		pos;

	spherepoly_center(&v, poly);
	/* If 0-vector */
	if (FPzero(v.x) && FPzero(v.y) && FPzero(v.z))
	{
		return false;
	}

	for (i = 0; i < poly->npts; i++)
	{
		spoly_segment(&sli, poly, i);
		for (k = (i + 1); k < poly->npts; k++)
		{
			spoly_segment(&slk, poly, k);
			pos = sline_sline_pos(&sli, &slk);
			if (!(pos == PGS_LINE_CONNECT || pos == PGS_LINE_AVOID))
			{
				return false;
			}
		}
	}

	vector3d_spoint(&p, &v);

	se.phi_a = EULER_AXIS_Z;
	se.theta_a = EULER_AXIS_X;
	se.psi_a = EULER_AXIS_Z;
	se.phi = -PIH - p.lng;
	se.theta = p.lat - PIH;
	se.psi = 0.0;

	for (i = 0; i < poly->npts; i++)
	{
		euler_spoint_trans(&p, &poly->p[i], &se);
		/* less _and_ equal are important !! */
		/* Do not change it! */
		if (FPle(p.lat, 0.0))
		{
			return false;
		}
	}
	return true;
}


/*
 * Converts an array of spherical points to SPOLY.
 *
 * arr   - pointer to the array of spherical points
 * nelem - number of the elements
 *
 * Returns the pointer to the created spherical polygon.
 */
static SPOLY *
spherepoly_from_array(SPoint *arr, int32 nelem)
{
	SPOLY *poly = NULL;

	if (nelem < 3)
	{
		elog(ERROR, "spherepoly_from_array: more than two points needed");
		return NULL;
	}
	else
	{
		int32	i;
		float8	scheck;
		int32	size;

		for (i = 0; i < nelem; i++)
		{
			spoint_check(&arr[i]);
		}

		/* check duplicate points */
		i = 0;
		while (i < (nelem - 1))
		{
			if (nelem < 3)
				break;
			if (spoint_eq(&arr[i], &arr[i + 1]))
			{
				if (i < (nelem - 2))
				{
					memmove((void *) &arr[i + 1],
							(void *) &arr[i + 2],
							(nelem - i - 2) * sizeof(SPoint));
				}
				nelem--;
				continue;
			}
			i++;
		}
		if (spoint_eq(&arr[0], &arr[nelem - 1]))
		{
			nelem--;
		}
		if (nelem < 3)
		{
			elog(ERROR, "spherepoly_from_array: more than two points needed");
			return NULL;
		}

		size = offsetof(SPOLY, p[0]) +sizeof(SPoint) * nelem;
		poly = (SPOLY *) palloc(size);
		SET_VARSIZE(poly, size);
		poly->npts = nelem;
		for (i = 0; i < nelem; i++)
		{
			if (i == 0)
			{
				scheck = spoint_dist(&arr[nelem - 1], &arr[0]);
			}
			else
			{
				scheck = spoint_dist(&arr[i - 1], &arr[i]);
			}
			if (FPeq(scheck, PI))
			{
				elog(ERROR, "spherepoly_from_array: a polygon segment length must be not equal 180 degrees.");
				return NULL;
			}
			memcpy((void *) &poly->p[i],
				   (void *) &arr[i],
				   sizeof(SPoint));
		}

	}

	if (!spherepoly_check(poly))
	{
		elog(ERROR, "spherepoly_from_array: a line segment overlaps or polygon too large");
		pfree(poly);
		return NULL;
	}

	return poly;

}


/*
 * Performs a transform of a polygon using an Euler transformation.
 *
 * se  - pointer to the Euler transformation
 * in  - pointer to the polygon
 * out - pointer to the transformed polygon
 */
static void
euler_spoly_trans(SPOLY *out, const SPOLY *in, const SEuler *se)
{
	int32		i;

	SET_VARSIZE(out, VARSIZE(in));
	out->npts = in->npts;
	for (i = 0; i < in->npts; i++)
		euler_spoint_trans(&out->p[i], &in->p[i], se);
}


/*
 * Returns the relationship between polygon and circle as
 * PGS_CIRCLE_POLY_REL int8 value.
 */
static int8
poly_circle_pos(const SPOLY *poly, const SCIRCLE *circ)
{
	int8		pos = 0;
	int32		i;
	SLine		sl;
	bool		pcp;
	const int8	sc_in = (1 << PGS_CIRCLE_CONT_LINE);
	const int8	sc_ov = (1 << PGS_CIRCLE_LINE_OVER);
	const int8	sc_os = (1 << PGS_CIRCLE_LINE_AVOID);

	pcp = spoly_contains_point(poly, &circ->center);

	if (FPzero(circ->radius))
	{
		if (pcp)
		{
			return PGS_POLY_CONT_CIRCLE;
		}
		else
		{
			return PGS_CIRCLE_POLY_AVOID;
		}
	}

	for (i = 0; i < poly->npts; i++)
	{

		spoly_segment(&sl, poly, i);
		pos |= (1 << sphereline_circle_pos(&sl, circ));
		if (((pos & sc_in) && (pos & sc_os)) || (pos & sc_ov))
		{
			return PGS_CIRCLE_POLY_OVER;
			/* overlaps */
		}

	}

	if ((pos & sc_in) == pos)
	{
		/* all lines inside circle */
		/* circle contains polygon */
		return PGS_CIRCLE_CONT_POLY;
	}
	else if ((pos & sc_os) == pos)
	{
		/* all lines outside circle */
		if (pcp)
		{
			/* polygon contains circle */
			return PGS_POLY_CONT_CIRCLE;
		}
		else
		{
			/* circle outside polygon */
			return PGS_CIRCLE_POLY_AVOID;
		}

	}
	/* circle and polygon overlap */
	return PGS_CIRCLE_POLY_OVER;
}


/*
 * Returns the relationship between a polygon and an ellipse
 * as PGS_ELLIPSE_POLY_REL int8 value.
 */
static int8
poly_ellipse_pos(const SPOLY *poly, const SELLIPSE *ell)
{
	int8		pos = 0;
	int32		i;
	SLine		sl;
	bool		pcp;
	SPoint		cen;
	const int8	sc_in = (1 << PGS_ELLIPSE_CONT_LINE);
	const int8	sc_ov = (1 << PGS_ELLIPSE_LINE_OVER);
	const int8	sc_os = (1 << PGS_ELLIPSE_LINE_AVOID);

	sellipse_center(&cen, ell);
	pcp = spoly_contains_point(poly, &cen);

	if (FPzero(ell->rad[0]))
	{
		if (pcp)
		{
			return PGS_POLY_CONT_ELLIPSE;
		}
		else
		{
			return PGS_ELLIPSE_POLY_AVOID;
		}
	}

	if (FPzero(ell->rad[1]))
	{
		SLine		l;
		int8		res;

		sellipse_line(&l, ell);
		res = poly_line_pos(poly, &l);
		if (res == PGS_LINE_POLY_AVOID)
		{
			return PGS_ELLIPSE_POLY_AVOID;
		}
		else if (res == PGS_POLY_CONT_LINE)
		{
			return PGS_POLY_CONT_ELLIPSE;
		}
		else
		{
			return PGS_ELLIPSE_POLY_OVER;
		}
	}

	for (i = 0; i < poly->npts; i++)
	{
		spoly_segment(&sl, poly, i);
		pos |= (1 << sellipse_line_pos(ell, &sl));
		if (((pos & sc_in) && (pos & sc_os)) || (pos & sc_ov))
		{
			return PGS_ELLIPSE_POLY_OVER;
			/* overlaps */
		}
	}

	if ((pos & sc_in) == pos)
	{
		/* all lines inside ellipse */
		/* ellipse contains polygon */
		return PGS_ELLIPSE_CONT_POLY;
	}
	else if ((pos & sc_os) == pos)
	{
		/* all lines outside ellipse */
		if (pcp)
		{
			/* polygon contains ellipse */
			return PGS_POLY_CONT_ELLIPSE;
		}
		else
		{
			/* ellipse outside polygon */
			return PGS_ELLIPSE_POLY_AVOID;
		}

	}

	/* ellipse and polygon overlap */
	return PGS_ELLIPSE_POLY_OVER;
}


/*
 * Returns the relationship between two polygons as
 * PGS_POLY_REL int8 value.
 *
 * p1 - pointer to the first polygon
 * p2 - pointer to the second polygon
 *
 * If 'recheck' is true, swaps 'p1' and 'p2'.
 */
static int8
poly_poly_pos(const SPOLY *p1, const SPOLY *p2, bool recheck)
{
	int32		i;
	SLine		sl;
	int8		pos = 0,
				res = 0;
	const int8	sp_os = (1 << PGS_LINE_POLY_AVOID);
	const int8	sp_ct = (1 << PGS_POLY_CONT_LINE);
	const int8	sp_ov = (1 << PGS_LINE_POLY_OVER);

	for (i = 0; i < p2->npts; i++)
	{

		spoly_segment(&sl, p2, i);
		pos = (1 << poly_line_pos(p1, &sl));
		if (pos == sp_ov)
		{
			return PGS_POLY_OVER;
			/* overlap */
		}
		res |= pos;
	}

	if (res == sp_os)
	{
		if (!recheck)
		{
			pos = poly_poly_pos(p2, p1, true);
		}
		if (pos == PGS_POLY_CONT)
		{
			return PGS_POLY_OVER;
		}
		else
		{
			return PGS_POLY_AVOID;
		}
	}
	if (res == sp_ct)
	{
		return PGS_POLY_CONT;
	}
	return PGS_POLY_OVER;
}

/*
 * Checks whether two polygons are equal.
 *
 * If 'dir' is true, check with reverse polygon of 'p2'.
 */
bool
spoly_eq(const SPOLY *p1, const SPOLY *p2, bool dir)
{
	bool ret = false;

	if (p1->npts == p2->npts)
	{

		int32	i,
				k,
				cntr,
				shift;

		for (shift = 0; shift < p1->npts; shift++)
		{
			cntr = 0;
			for (i = 0; i < p1->npts; i++)
			{
				k = (dir) ? (p1->npts - i - 1) : (i);
				k += shift;
				k = (k < p1->npts) ? (k) : (k - p1->npts);
				if (spoint_eq(&p1->p[i], &p2->p[k]))
				{
					cntr++;
				}
			}
			if (cntr == p1->npts)
			{
				ret = true;
				break;
			}
		}

		/* Try other direction if not equal */
		if (!dir && !ret)
		{
			ret = spoly_eq(p1, p2, true);
		}

	}

	return ret;
}

/*
 * Returns the i-th line segment of a polygon.
 *
 * sl   - pointer to the line segment
 * poly - pointer to the polygon
 * i    - number of the segment
 */
bool
spoly_segment(SLine *sl, const SPOLY *poly, int32 i)
{
	if (i >= 0 && i < poly->npts)
	{
		if (i == (poly->npts - 1))
			sline_from_points(sl, &poly->p[i], &poly->p[0]);
		else
			sline_from_points(sl, &poly->p[i], &poly->p[i + 1]);
		return true;
	}
	else
	{
		return false;
	}
}

static bool
spoly_get_point(SPoint *sp, const SPOLY *poly, int32 i)
{
	if (i >= 0 && i < poly->npts)
	{
		memcpy((void *) sp, (void *) &poly->p[i], sizeof(SPoint));
		return true;
	}
	return false;
}

Datum
spherepoly_get_point(PG_FUNCTION_ARGS)
{
	int32		i;
	SPOLY		*poly = PG_GETARG_SPOLY(0);
	SPoint		*sp = (SPoint *) palloc(sizeof(SPoint));

	i = PG_GETARG_INT32(1);
	if (spoly_get_point(sp, poly, i - 1))
	{
		PG_RETURN_POINTER(sp);
	}
	pfree(sp);
	PG_RETURN_NULL();
}

Datum
spherepoly_get_array(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	Datum	   *datum_arr = (Datum *) palloc(sizeof(Datum) * poly->npts);
	ArrayType  *res;
	SPoint	   *p = (SPoint *) palloc(sizeof(SPoint) * poly->npts);

	for (int i = 0; i < poly->npts; i++)
	{
		if (!spoly_get_point(&p[i], poly, i))
		{
			pfree(p);
			pfree(datum_arr);
			PG_RETURN_NULL();
		}
		datum_arr[i] = PointerGetDatum(&p[i]);
	}

	res = construct_array(datum_arr, poly->npts, get_spoint_type_oid(), sizeof(SPoint), false, 'd');

	PG_RETURN_ARRAYTYPE_P(res);
}

/*
 * Checks whether a polygon contains a point.
 *
 * pg - pointer to the polygon
 * sp - pointer to the point
 */
bool
spoly_contains_point(const SPOLY *pg, const SPoint *sp)
{
	int32		i;
	SLine		sl;
	bool		res = false;
	float8		scp;
	Vector3D	vc,
				vp;

	/* First check if point is outside polygon (behind) */
	spherepoly_center(&vc, pg);
	spoint_vector3d(&vp, sp);
	scp = vector3d_scalar(&vp, &vc);
	if (FPle(scp, 0.0))
	{
		return false;
	}

	/* Check whether point is edge */
	for (i = 0; i < pg->npts; i++)
	{
		if (spoint_eq(&pg->p[i], sp))
		{
			return true;
		}
	}

	/* Check whether point is on a line segment */
	for (i = 0; i < pg->npts; i++)
	{
		spoly_segment(&sl, pg, i);
		if (spoint_at_sline(sp, &sl))
		{
			return true;
		}
	}

	do
	{
		SEuler		se,
					te;
		SPoint		p,
					lp[2];
		bool		a1,
					a2,
					eqa;
		int32		cntr = 0;
		SPOLY	   *tmp = (SPOLY *) palloc(VARSIZE(pg));

		/*
		 * Make a transformation, so the point is (0,0)
		 */

		se.phi_a = EULER_AXIS_Z;
		se.theta_a = EULER_AXIS_X;
		se.psi_a = EULER_AXIS_Z;
		se.phi = PIH - sp->lng;
		se.theta = -sp->lat;
		se.psi = -PIH;

		euler_spoly_trans(tmp, pg, &se);

		p.lng = 0.0;
		p.lat = 0.0;

		/* Check whether an edge is on equator. */
		/* If yes, rotate randomized around 0,0 */

		cntr = 0;
		do
		{
			eqa = false;
			for (i = 0; i < pg->npts; i++)
			{
				if (FPzero(tmp->p[i].lat))
				{
					if (FPeq(cos(tmp->p[i].lng), -1.0))
					{
						return false;
					}
					else
					{
						eqa = true;
						break;
					}
				}
			}
			if (eqa)
			{
				SPOLY	   *ttt = (SPOLY *) palloc(VARSIZE(pg));

				srand(cntr);
				se.phi_a = se.theta_a = se.psi_a = EULER_AXIS_X;
				se.phi = ((double) rand() / RAND_MAX) * PID;
				se.theta = 0.0;
				se.psi = 0.0;
				euler_spoly_trans(ttt, tmp, &se);
				memcpy((void *) tmp, (void *) ttt, VARSIZE(pg));
				pfree(ttt);
			}
			if (cntr > 10000)
			{
				elog(WARNING, "Bug found in spoly_contains_point");
				elog(ERROR, "Please report it to pg_sphere team!");
				return false;
			}
			cntr++;
		} while (eqa);

		/* Count line segment crossing "equator" */

		cntr = 0;
		for (i = 0; i < pg->npts; i++)
		{

			/* create a single line from segment */
			spoly_segment(&sl, tmp, i);

			sline_begin(&lp[0], &sl);
			sline_end(&lp[1], &sl);

			a1 = (FPgt(lp[0].lat, 0.0) && FPlt(lp[1].lat, 0.0));
			a2 = (FPlt(lp[0].lat, 0.0) && FPgt(lp[1].lat, 0.0));

			if (a1 || a2)
			{
				/* if crossing */
				sphereline_to_euler_inv(&te, &sl);
				if (a2)
				{
					/* crossing ascending */
					p.lng = PID - te.phi;
				}
				else
				{
					p.lng = PI - te.phi;
				}
				spoint_check(&p);
				if (p.lng < PI)
				{
					/* crossing between 0 and 180 deg */
					cntr++;
				}
			}
		}

		pfree(tmp);
		if (cntr % 2)
		{
			res = true;
		}

	} while (0);

	return res;
}

/*
 * Returns the relationship between a polygon and a line as
 * PGS_LINE_POLY_REL int8 value.
 *
 * poly - pointer to the polygon
 * line - pointer to the line
 */
int8
poly_line_pos(const SPOLY *poly, const SLine *line)
{
	int32		i;
	SLine		sl;
	SPoint		slbeg,
				slend;
	int8		p1,
				p2,
				pos,
				res;
	const int8	sl_os = (1 << PGS_LINE_AVOID);
	const int8	sl_cl = (1 << PGS_LINE_CONT_LINE);
	const int8	sl_cr = (1 << PGS_LINE_CROSS);
	const int8	sl_cn = (1 << PGS_LINE_CONNECT);
	const int8	sl_ov = (1 << PGS_LINE_OVER);
	const int8	sl_eq = (1 << PGS_LINE_EQUAL);

	pos = 0;
	res = 0;

	sline_begin(&slbeg, line);
	sline_end(&slend, line);

	p1 = spoly_contains_point(poly, &slbeg);
	p2 = spoly_contains_point(poly, &slend);

	for (i = 0; i < poly->npts; i++)
	{
		spoly_segment(&sl, poly, i);
		pos = (1 << sline_sline_pos(&sl, line));
		if (pos == sl_eq)
		{
			pos = sl_cl;
			/* is contain */
		}
		if (pos == sl_ov)
		{
			return PGS_LINE_POLY_OVER;
			/* overlap */
		}
		/* Recheck line crossing */
		if (pos == sl_cr)
		{
			bool	bal,
					eal;

			bal = spoint_at_sline(&slbeg, &sl);
			eal = spoint_at_sline(&slend, &sl);
			if (!bal && !eal)
			{
				return PGS_LINE_POLY_OVER;
				/* overlap */
			}
			if ((bal && p2) || (eal && p1))
			{
				pos = sl_cl;
				/* is contain */
			}
			else
			{
				return PGS_LINE_POLY_OVER;
				/* overlap */
			}
		}
		res |= pos;
	}
	if ((res & sl_cl) && ((res - sl_cl - sl_os - sl_cn - 1) < 0))
	{
		return PGS_POLY_CONT_LINE;
	}
	else if (p1 && p2 && ((res - sl_os - sl_cn - 1) < 0))
	{
		return PGS_POLY_CONT_LINE;
	}
	else if (!p1 && !p2 && ((res - sl_os - 1) < 0))
	{
		return PGS_LINE_POLY_AVOID;
	}
	return PGS_LINE_POLY_OVER;
}


Datum
spherepoly_in(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly;
	char	   *c = PG_GETARG_CSTRING(0);
	int32		i,
				nelem;

	void		sphere_yyparse(void);

	init_buffer(c);
	sphere_yyparse();

	nelem = get_path_count();
	if (nelem > MAX_POINTS)
	{
		reset_buffer();
		elog(ERROR, "spherepoly_in: too much points");
		PG_RETURN_NULL();

	}
	if (nelem > 2)
	{
		SPoint		arr[MAX_POINTS];

		for (i = 0; i < nelem; i++)
		{
			get_path_elem(i, &arr[i].lng, &arr[i].lat);
		}
		poly = spherepoly_from_array(&arr[0], nelem);
	}
	else
	{
		reset_buffer();
		elog(ERROR, "spherepoly_in: more than two points needed");
		PG_RETURN_NULL();
	}
	reset_buffer();

	PG_RETURN_POINTER(poly);
}

Datum
spherepoly_rad(PG_FUNCTION_ARGS)
{
	int			i,
				np;
	ArrayType  *float_vector = PG_GETARG_ARRAYTYPE_P(0);
	float8	   *array_data;
	SPoint	   *points;

	np = ArrayGetNItems(ARR_NDIM(float_vector), ARR_DIMS(float_vector));

	if (ARR_HASNULL(float_vector))
	{
		elog(ERROR,
		     "spherepoly_rad: input array is invalid because it has null values"
		    );
		PG_RETURN_NULL();
	}

	if (np < 6 || np % 2 != 0)
	{
		elog(ERROR,
		     "spherepoly_rad: invalid number of arguments (must be even and >= 6)"
		    );
		PG_RETURN_NULL();
	}

	np /= 2;

	points = (SPoint *) palloc(np * sizeof(SPoint));
	if (points == NULL)
	{
		elog(ERROR,
		     "spherepoly_rad: failed to allocate memory for points array"
		    );
		PG_RETURN_NULL();
	}

	array_data = (float8 *) ARR_DATA_PTR(float_vector);

	for (i = 0; i < np; i++)
	{
		create_spherepoint_from_long_lat(&points[i],
						 array_data[2 * i],
						 array_data[2 * i + 1]
						);
	}
	PG_RETURN_POINTER(spherepoly_from_array(points, np));
}

Datum
spherepoly_deg(PG_FUNCTION_ARGS)
{
	int			i,
				np;
	ArrayType  *float_vector = PG_GETARG_ARRAYTYPE_P(0);
	float8	   *array_data;
	SPoint	   *points;

	np = ArrayGetNItems(ARR_NDIM(float_vector), ARR_DIMS(float_vector));

	if (ARR_HASNULL(float_vector))
	{
		elog(ERROR,
		     "spherepoly_deg: input array is invalid because it has null values"
		    );
		PG_RETURN_NULL();
	}

	if (np < 6 || np % 2 != 0)
	{
		elog(ERROR,
		     "spherepoly_deg: invalid number of arguments (must be even and >= 6)"
		    );
		PG_RETURN_NULL();
	}

	np /= 2;

	points = (SPoint *) palloc(np * sizeof(SPoint));
	if (points == NULL)
	{
		elog(ERROR,
		     "spherepoly_deg: failed to allocate memory for points array"
		    );
		PG_RETURN_NULL();
	}

	array_data = (float8 *) ARR_DATA_PTR(float_vector);

	for (i = 0; i < np; i++)
	{
		create_spherepoint_from_long_lat(&points[i],
						 deg_to_rad(array_data[2 * i]),
						 deg_to_rad(array_data[2 * i + 1])
						);
	}
	PG_RETURN_POINTER(spherepoly_from_array(points, np));
}

Datum
spherepoly_from_point_array(PG_FUNCTION_ARGS)
{
	SPoint	   *points;
	ArrayType  *inarr = PG_GETARG_ARRAYTYPE_P(0);
	const int	np = ArrayGetNItems(ARR_NDIM(inarr), ARR_DIMS(inarr));

	if (np < 3)
	{
		elog(ERROR, "spherepoly_from_point_array: "
			 "invalid number of arguments (must be >= 3)");
		PG_RETURN_NULL();
	}

	if (ARR_HASNULL(inarr))
	{
		elog(ERROR, "spherepoly_from_point_array: "
			 "input array is invalid because it has null values");
		PG_RETURN_NULL();
	}

	points = (SPoint *) ARR_DATA_PTR(inarr);

	PG_RETURN_POINTER(spherepoly_from_array(points, np));
}

Datum
spherepoly_equal(PG_FUNCTION_ARGS)
{
	SPOLY	   *p1 = PG_GETARG_SPOLY(0);
	SPOLY	   *p2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(spoly_eq(p1, p2, false));
}

Datum
spherepoly_equal_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *p1 = PG_GETARG_SPOLY(0);
	SPOLY	   *p2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(!spoly_eq(p1, p2, false));
}

Datum
spherepoly_circ(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	int32		i;
	SLine		l;
	float8		sum = 0.0;

	for (i = 0; i < poly->npts; i++)
	{
		spoly_segment(&l, poly, i);
		sum += l.length;
	}
	PG_RETURN_FLOAT8(sum);
}

Datum
spherepoly_npts(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);

	PG_RETURN_INT32(poly->npts);
}



Datum
spherepoly_area(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	int32		i;
	SPoint		s[MAX_POINTS + 2];
	SPoint		stmp[2];
	SEuler		se;
	float8		sum = 0.0;

	memcpy((void *) &s[1],
		   (void *) &poly->p[0],
		   poly->npts * sizeof(SPoint));
	memcpy((void *) &s[0], (void *) &s[poly->npts], sizeof(SPoint));
	memcpy((void *) &s[poly->npts + 1], (void *) &s[1], sizeof(SPoint));

	se.psi = 0;
	se.phi_a = EULER_AXIS_Z;
	se.theta_a = EULER_AXIS_X;
	se.psi_a = EULER_AXIS_Z;

	for (i = 1; i <= poly->npts; i++)
	{

		se.phi = -PIH - s[i].lng;
		se.theta = s[i].lat - PIH;
		euler_spoint_trans(&stmp[0], &s[i - 1], &se);
		euler_spoint_trans(&stmp[1], &s[i + 1], &se);

		stmp[1].lng -= stmp[0].lng;
		if (FPlt(stmp[1].lng, 0.0))
		{
			stmp[1].lng += PID;
		}
		sum += stmp[1].lng;

	}

	sum -= (PI * (poly->npts - 2));
	if (FPge(sum, PID))
	{
		sum = 2 * PID - sum;
	}

	if (FPzero(sum))
	{
		sum = 0.0;
	}

	PG_RETURN_FLOAT8(sum);
}

Datum
spherepoly_cont_point(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SPoint	   *sp = (SPoint *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(spoly_contains_point(poly, sp));
}

Datum
spherepoly_cont_point_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SPoint	   *sp = (SPoint *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(!spoly_contains_point(poly, sp));
}

Datum
spherepoly_cont_point_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SPoint	   *sp = (SPoint *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(spoly_contains_point(poly, sp));
}

Datum
spherepoly_cont_point_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SPoint	   *sp = (SPoint *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(!spoly_contains_point(poly, sp));
}

Datum
spherepoly_cont_circle(PG_FUNCTION_ARGS)
{
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);
	SPOLY	   *poly = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_POLY_CONT_CIRCLE);
}

Datum
spherepoly_cont_circle_neg(PG_FUNCTION_ARGS)
{
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);
	SPOLY	   *poly = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) != PGS_POLY_CONT_CIRCLE);
}

Datum
spherepoly_cont_circle_com(PG_FUNCTION_ARGS)
{
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);
	SPOLY	   *poly = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_POLY_CONT_CIRCLE);
}

Datum
spherepoly_cont_circle_com_neg(PG_FUNCTION_ARGS)
{
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);
	SPOLY	   *poly = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) != PGS_POLY_CONT_CIRCLE);
}

Datum
spherecircle_cont_poly(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_CIRCLE_CONT_POLY);
}

Datum
spherecircle_cont_poly_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) != PGS_CIRCLE_CONT_POLY);
}

Datum
spherecircle_cont_poly_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_CIRCLE_CONT_POLY);
}

Datum
spherecircle_cont_poly_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) != PGS_CIRCLE_CONT_POLY);
}

Datum
spherepoly_overlap_circle(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) > PGS_CIRCLE_POLY_AVOID);
}

Datum
spherepoly_overlap_circle_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_CIRCLE_POLY_AVOID);
}

Datum
spherepoly_overlap_circle_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) > PGS_CIRCLE_POLY_AVOID);
}

Datum
spherepoly_overlap_circle_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SCIRCLE    *circ = (SCIRCLE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_circle_pos(poly, circ) == PGS_CIRCLE_POLY_AVOID);
}

Datum
spherepoly_cont_line(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_line_pos(poly, line) == PGS_POLY_CONT_LINE);
}

Datum
spherepoly_cont_line_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_line_pos(poly, line) != PGS_POLY_CONT_LINE);
}

Datum
spherepoly_cont_line_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_line_pos(poly, line) == PGS_POLY_CONT_LINE);
}

Datum
spherepoly_cont_line_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_line_pos(poly, line) != PGS_POLY_CONT_LINE);
}

Datum
spherepoly_overlap_line(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_line_pos(poly, line) > PGS_LINE_POLY_AVOID);
}

Datum
spherepoly_overlap_line_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_line_pos(poly, line) == PGS_LINE_POLY_AVOID);
}

Datum
spherepoly_overlap_line_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_line_pos(poly, line) > PGS_LINE_POLY_AVOID);
}

Datum
spherepoly_overlap_line_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SLine	   *line = (SLine *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_line_pos(poly, line) == PGS_LINE_POLY_AVOID);
}


Datum
spherepoly_cont_poly(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(0);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) == PGS_POLY_CONT);
}

Datum
spherepoly_cont_poly_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(0);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) != PGS_POLY_CONT);
}

Datum
spherepoly_cont_poly_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(1);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) == PGS_POLY_CONT);
}

Datum
spherepoly_cont_poly_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(1);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) != PGS_POLY_CONT);
}

Datum
spherepoly_overlap_poly(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(0);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) > PGS_POLY_AVOID);
}

Datum
spherepoly_overlap_poly_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly1 = PG_GETARG_SPOLY(0);
	SPOLY	   *poly2 = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_poly_pos(poly1, poly2, false) == PGS_POLY_AVOID);
}

Datum
spherepoly_cont_ellipse(PG_FUNCTION_ARGS)
{
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);
	SPOLY	   *poly = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_POLY_CONT_ELLIPSE);
}

Datum
spherepoly_cont_ellipse_neg(PG_FUNCTION_ARGS)
{
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);
	SPOLY	   *poly = PG_GETARG_SPOLY(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) != PGS_POLY_CONT_ELLIPSE);
}

Datum
spherepoly_cont_ellipse_com(PG_FUNCTION_ARGS)
{
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);
	SPOLY	   *poly = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_POLY_CONT_ELLIPSE);
}

Datum
spherepoly_cont_ellipse_com_neg(PG_FUNCTION_ARGS)
{
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);
	SPOLY	   *poly = PG_GETARG_SPOLY(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) != PGS_POLY_CONT_ELLIPSE);
}

Datum
sphereellipse_cont_poly(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_ELLIPSE_CONT_POLY);
}

Datum
sphereellipse_cont_poly_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) != PGS_ELLIPSE_CONT_POLY);
}

Datum
sphereellipse_cont_poly_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_ELLIPSE_CONT_POLY);
}

Datum
sphereellipse_cont_poly_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) != PGS_ELLIPSE_CONT_POLY);
}

Datum
spherepoly_overlap_ellipse(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) > PGS_ELLIPSE_POLY_AVOID);
}

Datum
spherepoly_overlap_ellipse_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(0);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(1);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_ELLIPSE_POLY_AVOID);
}

Datum
spherepoly_overlap_ellipse_com(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) > PGS_ELLIPSE_POLY_AVOID);
}

Datum
spherepoly_overlap_ellipse_com_neg(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = PG_GETARG_SPOLY(1);
	SELLIPSE   *ell = (SELLIPSE *) PG_GETARG_POINTER(0);

	PG_RETURN_BOOL(poly_ellipse_pos(poly, ell) == PGS_ELLIPSE_POLY_AVOID);
}

Datum
spheretrans_poly(PG_FUNCTION_ARGS)
{
	SPOLY	   *sp = PG_GETARG_SPOLY(0);
	SEuler	   *se = (SEuler *) PG_GETARG_POINTER(1);
	SPOLY	   *out = (SPOLY *) palloc(VARSIZE(sp));

	euler_spoly_trans(out, sp, se);
	PG_RETURN_POINTER(out);
}

Datum
spheretrans_poly_inverse(PG_FUNCTION_ARGS)
{
	Datum		sp = PG_GETARG_DATUM(0);
	SEuler	   *se = (SEuler *) PG_GETARG_POINTER(1);
	SEuler		tmp;
	Datum		ret;

	spheretrans_inverse(&tmp, se);
	ret = DirectFunctionCall2(spheretrans_poly,
							  sp, PointerGetDatum(&tmp));
	PG_RETURN_DATUM(ret);
}

Datum
spherepoly_add_point(PG_FUNCTION_ARGS)
{
	SPOLY	   *poly = (SPOLY *) PG_GETARG_POINTER(0);
	SPoint	   *p = (SPoint *) PG_GETARG_POINTER(1);
	int32		size = 0;
	SPOLY	   *poly_new = NULL;

	if (p == NULL)
	{
		PG_RETURN_POINTER(poly);
	}
	if (poly == NULL)
	{
		size = offsetof(SPOLY, p[0]) +sizeof(SPoint);
		poly = (SPOLY *) palloc(size);
		memcpy((void *) &poly->p[0], (void *) p, sizeof(SPoint));
		SET_VARSIZE(poly, size);
		poly->npts = 1;
		PG_RETURN_POINTER(poly);
	}

	poly = PG_GETARG_SPOLY(0);

	/* skip if equal */
	if (spoint_eq(p, &poly->p[poly->npts - 1]))
	{
		PG_RETURN_POINTER(poly);
	}

	/* Skip if distance is equal 180deg */
	if (FPeq(spoint_dist(p, &poly->p[poly->npts - 1]), PI))
	{
		elog(NOTICE, "spoly(spoint): Skip point, distance of previous point is 180deg");
	}

	size = offsetof(SPOLY, p[0]) +sizeof(SPoint) * (poly->npts + 1);
	poly_new = palloc(size);
	memcpy((void *) poly_new, (void *) poly, VARSIZE(poly));
	poly_new->npts++;
	SET_VARSIZE(poly_new, size);

	memcpy((void *) &poly_new->p[poly->npts], (void *) p, sizeof(SPoint));
	PG_RETURN_POINTER(poly_new);
}


Datum
spherepoly_add_points_finalize(PG_FUNCTION_ARGS)
{
	SPOLY *poly = (SPOLY *) PG_GETARG_POINTER(0);

	if (poly == NULL)
	{
		PG_RETURN_NULL();
	}

	poly = PG_GETARG_SPOLY(0);

	if (poly->npts < 3)
	{
		elog(NOTICE, "spoly(spoint): At least 3 points required");
		pfree(poly);
		PG_RETURN_NULL();
	}
	/* Skip if distance is equal to 180deg */
	if (FPeq(spoint_dist(&poly->p[0], &poly->p[poly->npts - 1]), PI))
	{
		elog(NOTICE, "spoly(spoint): Cannot close polygon. Distance between first and last point is 180deg");
		pfree(poly);
		PG_RETURN_NULL();
	}

	if (!spherepoly_check(poly))
	{
		elog(NOTICE, "spoly(spoint): a line segment overlaps or polygon too large");
		pfree(poly);
		PG_RETURN_NULL();
	}
	PG_RETURN_POINTER(poly);
}


Datum
spherepoly_is_convex(PG_FUNCTION_ARGS)
{
	Vector3D	u,
				v,
				vsu,
				wsv,
				crs;
	int32 		i;
	float8		cur = 0.0,
				prev = 0.0;
	SPOLY		*poly = (SPOLY *) PG_GETARG_POINTER(0);

	if (poly == NULL)
	{
		PG_RETURN_BOOL(false);
	}

	poly = PG_GETARG_SPOLY(0);
	if (poly->npts == 3)
	{
		PG_RETURN_BOOL(true);
	}

	for (i = 0; i < poly->npts; i++)
	{
		const int j = (i - 1 + poly->npts) % poly->npts;
		const int k = (i + 1) % poly->npts;

		spoint_vector3d(&u, &poly->p[i]);
		spoint_vector3d(&v, &poly->p[j]);
		spoint_vector3d(&vsu, &poly->p[j]);
		spoint_vector3d(&wsv, &poly->p[k]);

		vector3d_addwithscalar(&vsu, -1, &u);
		vector3d_addwithscalar(&wsv, -1, &v);

		vector3d_cross(&crs, &vsu, &wsv);

		cur = vector3d_scalar(&crs, &v);
		if (cur * prev < 0)
		{
			PG_RETURN_BOOL(false);
		}
		prev = cur;
	}

	PG_RETURN_BOOL(true);
}
