/**********************************************************************
 *
 * PostGIS - Spatial Types for PostgreSQL
 * http://postgis.net
 *
 * Copyright 2011 Sandro Santilli <strk@kbt.io>
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public Licence. See the COPYING file.
 *
 **********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CUnit/Basic.h"

#include "liblwgeom_internal.h"
#include "lwgeodetic.h"
#include "lwgeodetic_tree.h"
#include "intervaltree.h"
#include "cu_tester.h"


static void test_tree_circ_create(void)
{
	LWLINE *g;
	CIRC_NODE *c;
	/* Line with 4 edges */
	g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(0 88,0 89,0 90,180 89,180 88)", LW_PARSER_CHECK_NONE));
	c = circ_tree_new(g->points);
	//circ_tree_print(c, 0);

	if ( CIRC_NODE_SIZE > 4 )
	{
		CU_ASSERT(c->num_nodes == 4);
	}
	else
	{
		CU_ASSERT(c->num_nodes  == ( 4 % CIRC_NODE_SIZE ? 1 : 0 ) + 4 / CIRC_NODE_SIZE);
	}

	circ_tree_free(c);
	lwline_free(g);
}


static void test_tree_circ_pip(void)
{
	LWLINE *g;
	CIRC_NODE *c;
	POINT2D pt, pt_outside;
	int rv, on_boundary;

	pt.x = 0.0;
	pt.y = 0.0;
	pt_outside.x = -2.0;
	pt_outside.y = 0.0;

	/* Point in square */
	g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,1 -1,1 1,-1 1,-1 -1)", LW_PARSER_CHECK_NONE));
	c = circ_tree_new(g->points);
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv, 1);

	/* Point on other side of square */
	pt.x = 2.0;
	pt.y = 0.0;
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv, 0);

	/* Clean and do new shape */
	circ_tree_free(c);
	lwline_free(g);

	/* Point in square, stab passing through vertex */
	pt.x = 0.0;
	pt.y = 0.0;
	g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 1,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE));
	c = circ_tree_new(g->points);
	//circ_tree_print(c, 0);
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv, 1);

	/* Point on other side of square, stab passing through vertex */
	pt.x = 2.0;
	pt.y = 0.0;
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv, 0);

	/* Clean and do new shape */
	circ_tree_free(c);
	lwline_free(g);

	/* Point outside "w" thing, stab passing through vertices and grazing pointy thing */
	pt.x = 2.0;
	pt.y = 0.0;
	g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE));
	c = circ_tree_new(g->points);
	//circ_tree_print(c, 0);
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	//printf("rv %d\n", rv);
	CU_ASSERT_EQUAL(rv, 0);

	/* Point inside "w" thing, stab passing through vertices and grazing pointy thing */
	pt.x = 0.8;
	pt.y = 0.0;
	rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	//printf("rv %d\n", rv);
	CU_ASSERT_EQUAL(rv, 1);

	/* Clean and do new shape */
	circ_tree_free(c);
	lwline_free(g);

}

static void test_tree_circ_pip2(void)
{
	LWGEOM* g;
	LWPOLY* p;
	LWPOINT* lwpt;
	int rv_classic, rv_tree, on_boundary;
	POINT2D pt, pt_outside;
	GBOX gbox;
	CIRC_NODE *c;

	g = lwgeom_from_wkt("POLYGON((0 0,0 1,1 1,1 0,0 0))", LW_PARSER_CHECK_NONE);
	p = lwgeom_as_lwpoly(g);

	pt.x = 0.2;
	pt.y = 0.1;
	lwgeom_calculate_gbox_geodetic(g, &gbox);
	gbox_pt_outside(&gbox, &pt_outside);
	c = circ_tree_new(p->rings[0]);
	//circ_tree_print(c, 0);
	rv_classic = lwpoly_covers_point2d(p, &pt);
	rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv_tree, rv_classic);
	circ_tree_free(c);
	lwgeom_free(g);

	g = lwgeom_from_hexwkb("0103000020E6100000010000004700000000000000621119C000000040C70C4B40000000E0CC6C18C0000000A026FF4A4000000040438519C000000000E8F44A40000000000F5318C00000004024C84A4000000060F9E518C0000000A027AD4A40000000805E0D18C0000000C0F5784A4000000040539718C000000080815E4A40000000C026FE19C0000000C0502D4A4000000060127019C000000040EA164A40000000003BFD1BC0000000609E234A4000000080D9011CC000000060B9114A4000000040C8501EC0000000C0D50C4A40000000C05F2C20C000000040C9E749400000006008D820C000000080D6F0494000000080139F20C000000060F3DE4940000000A0B16421C0000000C059C94940000000808FA223C0000000C007B949400000000041E722C000000080C3DC4940000000808F4224C0000000405DCE494000000060752923C000000040A9EF4940000000005CAD24C0000000C036E4494000000040F88624C00000008078FE494000000060558523C00000006025134A40000000403AED24C00000000011174A40000000A05E7D23C0000000E0A41F4A4000000040F0AD23C0000000809A304A4000000040A64E23C000000040C9474A40000000C0FCA221C0000000C030554A40000000805EDD23C0000000E010474A4000000040BFF822C00000008078664A4000000080C98F22C000000040E2914A40000000C036E021C00000002024924A4000000080D9E121C000000000D0A14A4000000040533723C000000040B99D4A40000000204B1E23C0000000C0CCB04A4000000000625A24C0000000A071B44A40000000004A5F24C0000000806FC64A40000000E0DD6523C00000006088CC4A400000004012D023C0000000001AE14A40000000806E1F23C0000000400BEE4A40000000E0A2E123C0000000C017EF4A4000000060449423C00000004003F94A40000000C0DC0624C0000000A0ED1B4B40000000A0803F24C0000000005D0C4B4000000040753924C000000080701D4B400000002021F320C00000000001234B4000000000C65221C000000080792D4B40000000406D6020C0000000001A514B4000000040BF9821C0000000A00E594B400000000031B520C0000000C0726B4B400000002019EA20C000000020977F4B400000000002ED1FC0000000E0B49B4B400000000084CC1EC0000000602D8C4B4000000020BB2A1FC000000060239B4B4000000040AE871EC0000000A0FDA14B400000008077771EC0000000C0E5864B40000000C0AABA1EC000000000B7794B40000000C03EC91DC0000000E020874B4000000000A4301EC0000000C0C49B4B4000000000B5811DC0000000A0A3B04B400000004095BD1BC000000020869E4B400000004091021DC00000004009894B40000000409D361EC000000080A2614B40000000809FB41FC0000000A0AB594B40000000C046021FC0000000C0164C4B40000000C0EC5020C0000000E05A384B4000000040DF3C1EC0000000803F104B4000000000B4221DC0000000C0CD0F4B40000000C0261E1CC00000006067354B4000000080E17A1AC000000080C3044B4000000000621119C000000040C70C4B40", LW_PARSER_CHECK_NONE);
	p = lwgeom_as_lwpoly(g);
	lwpt = (LWPOINT*)lwgeom_from_hexwkb("0101000020E610000057B89C28FEB320C09C8102CB3B2B4A40", LW_PARSER_CHECK_NONE);
	lwpoint_getPoint2d_p(lwpt, &pt);
	lwgeom_calculate_gbox_geodetic(g, &gbox);
	gbox_pt_outside(&gbox, &pt_outside);
	//printf("OUTSIDE POINT(%g %g)\n", pt_outside.x, pt_outside.y);
	c = circ_tree_new(p->rings[0]);
	rv_classic = lwpoly_covers_point2d(p, &pt);
	rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv_tree, rv_classic);
	circ_tree_free(c);
	lwpoint_free(lwpt);
	lwgeom_free(g);

	g = lwgeom_from_hexwkb("[encoded data, 14850 characters]", LW_PARSER_CHECK_NONE);
	p = lwgeom_as_lwpoly(g);
	lwpt = (LWPOINT*)lwgeom_from_hexwkb("0101000020E610000031B1F9B836A046C03C889D2974124E40", LW_PARSER_CHECK_NONE);
	lwpoint_getPoint2d_p(lwpt, &pt);
	lwgeom_calculate_gbox_geodetic(g, &gbox);
	gbox_pt_outside(&gbox, &pt_outside);
	//printf("OUTSIDE POINT(%g %g)\n", pt_outside.x, pt_outside.y);
	c = circ_tree_new(p->rings[0]);
	rv_classic = lwpoly_covers_point2d(p, &pt);
	rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary);
	CU_ASSERT_EQUAL(rv_tree, rv_classic);
	circ_tree_free(c);
	lwpoint_free(lwpt);
	lwgeom_free(g);

}


static void test_tree_circ_distance(void)
{
	LWGEOM *lwg1, *lwg2;
	CIRC_NODE *c1, *c2;
	SPHEROID s;
	double d1, d2, d3, d4, e1, e2;
	double threshold = 0.0;

	spheroid_init(&s, 1.0, 1.0);

	/* Ticket 5765 */
	/* Two very close segments (1.64m) with mid-line points */
	lwg1 = lwgeom_from_wkt("LINESTRING (-1.7485638876537 52.477970467863,-1.7485727216213 52.477970486617)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("LINESTRING (-1.7486648265496 52.477955848635,-1.7485639721184 52.477955634571)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	e1 = d1 * WGS84_RADIUS;
	e2 = d2 * WGS84_RADIUS;

	// printf("\nRadians (tree and brute force)\n");
	// printf("  d1 = %g r   d2 = %g r\n", d1, d2);
	// printf("\nMeters (tree and brute force)\n");
	// printf("  e1 = %g m   e2 = %g m\n\n", e1, e2);
	// printf("linestring a\n");
	// circ_tree_print(c1, 0);
	// printf("linestring b\n");
	// circ_tree_print(c2, 0);

	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(e1, e2, 0.0001);

	return;

	/* Ticket #4223 */
	/* tall skinny rectangle */
	lwg1 = lwgeom_from_wkt("POLYGON((58.5112113206308 0,58.5112113200772 0.000901937525203378,58.511300910044 0.000901937636668872,58.5113009105976 0,58.5112113206308 0))", LW_PARSER_CHECK_NONE);
	/* square box 5m to left */
	lwg2 = lwgeom_from_wkt("POLYGON((58.5111665256017 0.000270581240841207,58.5111665255629 0.000360774987788249,58.5110769356128 0.000360774943200728,58.5110769356515 0.000270581207400566,58.5111665256017 0.000270581240841207))", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	e1 = d1 * WGS84_RADIUS;
	e2 = d2 * WGS84_RADIUS;
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(e1, e2, 0.0001);


	/* Ticket #1958 */
	lwg1 = lwgeom_from_wkt("LINESTRING(22.88333 41.96667,21.32667 42.13667)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POLYGON((22.94472 41.34667,22.87528 41.99028,22.87389 41.98472,22.87472 41.98333,22.94472 41.34667))", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001);

	/* Ticket #1951 */
	lwg1 = lwgeom_from_wkt("LINESTRING(0 0, 0 0)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(0.1 0.1)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001);

	lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(-2 0)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001);

	lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(2 2)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001);

	lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(1 1)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001);

	lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(1 0.5)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
//	printf("distance_tree %g\n", distance_tree);
//	printf("distance_geom %g\n", distance_geom);
//	circ_tree_print(cline, 0);
//	circ_tree_print(cpoint, 0);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001);


	/* Ticket #2351 */
	lwg1 = lwgeom_from_wkt("LINESTRING(149.386990599235 -26.3567415843982,149.386990599247 -26.3567415843965)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT(149.386990599235 -26.3567415843982)", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
//	printf("d1 = %g   d2 = %g\n", d1 * WGS84_RADIUS, d2 * WGS84_RADIUS);
//	printf("line\n");
//	circ_tree_print(c1, 0);
//	printf("point\n");
//	circ_tree_print(c2, 0);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001);

	/* Ticket #2634 */
	lwg1 = lwgeom_from_wkt("MULTIPOINT (-10 40,-10 65,10 40,10 65,30 40,30 65,50 40,50 65)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POLYGON((-9.1111111 40,-9.14954053919354 39.6098193559677,-9.26335203497743 39.2346331352698,-9.44817187539491 38.8888595339608,-9.6968975376269 38.5857864376269,-9.99997063396079 38.3370607753949,-10.3457442352698 38.1522409349774,-10.7209304559677 38.0384294391935,-11.1111111 38,-11.5012917440323 38.0384294391935,-11.8764779647302 38.1522409349774,-12.2222515660392 38.3370607753949,-12.5253246623731 38.5857864376269,-12.7740503246051 38.8888595339608,-12.9588701650226 39.2346331352698,-13.0726816608065 39.6098193559677,-13.1111111 40,-13.0726816608065 40.3901806440322,-12.9588701650226 40.7653668647302,-12.7740503246051 41.1111404660392,-12.5253246623731 41.4142135623731,-12.2222515660392 41.6629392246051,-11.8764779647302 41.8477590650226,-11.5012917440323 41.9615705608065,-11.1111111 42,-10.7209304559678 41.9615705608065,-10.3457442352698 41.8477590650226,-9.9999706339608 41.6629392246051,-9.69689753762691 41.4142135623731,-9.44817187539491 41.1111404660392,-9.26335203497743 40.7653668647302,-9.14954053919354 40.3901806440323,-9.1111111 40))", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
//	printf("d1 = %g   d2 = %g\n", d1 * WGS84_RADIUS, d2 * WGS84_RADIUS);
//	printf("multipoint\n");
//	circ_tree_print(c1, 0);
//	printf("polygon\n");
//	circ_tree_print(c2, 0);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001);

	/* Ticket #2634 */
	lwg1 = lwgeom_from_wkt("MULTIPOINT Z (-10 40 1,-10 65 1,10 40 1,10 65 1,30 40 1,30 65 1,50 40 1,50 65 1,-10 40 2,-10 65 2,10 40 2,10 65 2,30 40 2,30 65 2,50 40 2,50 65 2,-10 40 3,-10 65 3,10 40 3,10 65 3,30 40 3,30 65 3,50 40 3,50 65 3)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("MULTIPOLYGON(((-9.1111111 40,-9.14954053919354 39.6098193559677,-9.26335203497743 39.2346331352698,-9.44817187539491 38.8888595339608,-9.6968975376269 38.5857864376269,-9.99997063396079 38.3370607753949,-10.3457442352698 38.1522409349774,-10.7209304559677 38.0384294391935,-11.1111111 38,-11.5012917440323 38.0384294391935,-11.8764779647302 38.1522409349774,-12.2222515660392 38.3370607753949,-12.5253246623731 38.5857864376269,-12.7740503246051 38.8888595339608,-12.9588701650226 39.2346331352698,-13.0726816608065 39.6098193559677,-13.1111111 40,-13.0726816608065 40.3901806440322,-12.9588701650226 40.7653668647302,-12.7740503246051 41.1111404660392,-12.5253246623731 41.4142135623731,-12.2222515660392 41.6629392246051,-11.8764779647302 41.8477590650226,-11.5012917440323 41.9615705608065,-11.1111111 42,-10.7209304559678 41.9615705608065,-10.3457442352698 41.8477590650226,-9.9999706339608 41.6629392246051,-9.69689753762691 41.4142135623731,-9.44817187539491 41.1111404660392,-9.26335203497743 40.7653668647302,-9.14954053919354 40.3901806440323,-9.1111111 40)))", LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
//	printf("\n");
//	circ_tree_print(c1, 0);
//	printf("\n");
//	circ_tree_print(c2, 0);
//	printf("\n");
	d1 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	d2 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d3 = circ_tree_distance_tree(c1, c2, &s, threshold);
	d4 = circ_tree_distance_tree(c1, c2, &s, threshold);
//	printf("\n d1-no-tree %20.20g\n d2 %20.20g\n d3 %20.20g\n d4 %20.20g\n", d1, d2, d3, d4);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00000001);
	CU_ASSERT_DOUBLE_EQUAL(d1, d3, 0.00000001);
	CU_ASSERT_DOUBLE_EQUAL(d1, d4, 0.00000001);


	/* Ticket #4290 */
	const char *wkb1 = "[encoded data, 15746 characters]";
	const char *wkb2 = "0103000020E610000001000000080000002CAFE119EF2F53C02D6D4097DFBE42400050FBE7F32F53C05A08B32BD9BE42402ED0C3E9E52F53C0132FFFAE9DBE424048885052FE2F53C0DE5D06256BBE42409534CFEF163053C07D170F98ADBE42402E72095F303053C025ECD50CEABE424059CE3A7C103053C0235393C114BF42402CAFE119EF2F53C02D6D4097DFBE4240";
	spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS);
	lwg1 = lwgeom_from_hexwkb(wkb1, LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_hexwkb(wkb2, LW_PARSER_CHECK_NONE);
	c1 = lwgeom_calculate_circ_tree(lwg1);
	c2 = lwgeom_calculate_circ_tree(lwg2);
	d1 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
	d2 = circ_tree_distance_tree(c1, c2, &s, threshold);
	circ_tree_free(c1);
	circ_tree_free(c2);
	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	CU_ASSERT_DOUBLE_EQUAL(d1, 18358.866, 0.01);
	CU_ASSERT_DOUBLE_EQUAL(d2, 18358.866, 0.01);
}

static void test_tree_circ_distance_threshold(void)
{
	SPHEROID s;
	int i, j;
	int step = 10;

	const char *txt_poly1 = "0103000020E6100000010000000B0000000AA2F068F47651C0F7893DEB70B8454007ABD4C6D57651C000FB650799B84540C21AA2645A7651C011C24BA84AB8454089A9A325E87751C03314EB5453B74540AF9ED96BF57751C0BF9818F889B74540E936A498B47751C0690C87D1C5B74540F5386204DC7751C02FCA658F1AB8454077B65F7B657751C012C586EE37B845408C1862C5977751C00F17E41674B84540D4012F57357751C0AD3BC67E99B845400AA2F068F47651C0F7893DEB70B84540";
	const char *txt_poly2 = "[encoded data, 6354 characters]";
	const char *polys[2];
	static int npolys = 2;

	polys[0] = txt_poly1;
	polys[1] = txt_poly2;

	// spheroid_init(&s, WGS84_RADIUS, WGS84_RADIUS);
	spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS);

	for ( j = 0; j < npolys; j++ )
	{
		LWGEOM *lwg1 = lwgeom_from_hexwkb(polys[j], LW_PARSER_CHECK_NONE);
		LWGEOM *lwg2 = lwgeom_from_wkt("POINT(-69.83262 43.43636)", LW_PARSER_CHECK_NONE);

		CIRC_NODE *c1 = lwgeom_calculate_circ_tree(lwg1);
		CIRC_NODE *c2 = lwgeom_calculate_circ_tree(lwg2);

		for ( i = 50; i < 1500 / step; i++ )
		{
			double d1, d2;
			double d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0);
			double threshold = step * i;
			d1 = circ_tree_distance_tree(c1, c2, &s, threshold);
			d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold);
			if (threshold > d && (d1 > threshold || d2 > threshold))
			{
				printf("polygon #%d\n"
		       "threshold = %g\n"
					"true distance = %g\n"
		       "circ_tree_distance = %g\n"
		       "lwgeom_distance_spheroid = %g\n", j, threshold, d, d1, d2);
				CU_FAIL_FATAL();
			}
		}

		circ_tree_free(c1);
		circ_tree_free(c2);
		lwgeom_free(lwg1);
		lwgeom_free(lwg2);
	}

}

/***** Tests for IntervalTree *****/

static void test_itree_once(
	const char *polyWkt, double x, double y,
	IntervalTreeResult iexp)
{
	LWPOINT *pt = lwpoint_make2d(SRID_DEFAULT, x, y);
	LWGEOM *poly = lwgeom_from_wkt(polyWkt, LW_PARSER_CHECK_NONE);
	if(!poly)
		CU_FAIL_FATAL("unable to parse WKT");
	IntervalTree *itree = itree_from_lwgeom(poly);
	IntervalTreeResult irslt = itree_point_in_multipolygon(itree, pt);
	itree_free(itree);
	lwgeom_free(poly);
	lwpoint_free(pt);
	CU_ASSERT_EQUAL(irslt, iexp);
}

static void test_itree_square(void)
{
	const char *wktPoly =
		"MULTIPOLYGON(((-1 -1, 0 -1, 1 -1, 1 0, 1 1, -1 1, -1 -1)))";

	/* inside square */
	test_itree_once(wktPoly, 0.5, 0.5, ITREE_INSIDE);
	/* inside square at vertex */
	test_itree_once(wktPoly, 0.0, 0.0, ITREE_INSIDE);
	/* left of square */
	test_itree_once(wktPoly, -3.0, 0.0, ITREE_OUTSIDE);
	/* right of square */
	test_itree_once(wktPoly, 3.0, 0.0, ITREE_OUTSIDE);
	/* above square */
	test_itree_once(wktPoly, 0.0, 3.0, ITREE_OUTSIDE);
	/* on horizontal boundary */
	test_itree_once(wktPoly, 0.0, 1.0, ITREE_BOUNDARY);
	/* on vertical boundary */
	test_itree_once(wktPoly, -1.0, 0.0, ITREE_BOUNDARY);
}

static void test_itree_hole(void)
{
	const char *wktPoly =
		"POLYGON("
		"(-10 -10, 0 -10, 10 -10, 10 0, 10 10, 0 10, -10 10, -10 -10),"
		"(-5 -5, -5 0, -5 5, 5 5, 5 0, 5 -5, -5 -5))";

	/* inside hole */
	test_itree_once(wktPoly, 1.0, 1.0, ITREE_OUTSIDE);
	/* inside hole at vertical vertex */
	test_itree_once(wktPoly, 0.0, 0.0, ITREE_OUTSIDE);
	/* left of hole */
	test_itree_once(wktPoly, -7.0, 0.0, ITREE_INSIDE);
	/* right of hole */
	test_itree_once(wktPoly, 7.0, 0.0, ITREE_INSIDE);
	/* left of hole at top edge */
	test_itree_once(wktPoly, -7.0, 5.0, ITREE_INSIDE);
	/* right of hole at bottom edge */
	test_itree_once(wktPoly, 7.0, -5.0, ITREE_INSIDE);
	/* above hole */
	test_itree_once(wktPoly, 0.0, 7.0, ITREE_INSIDE);
	/* on hole boundary */
	test_itree_once(wktPoly, 0.0, 5.0, ITREE_BOUNDARY);
	/* on hole corner */
	test_itree_once(wktPoly, 5.0, 5.0, ITREE_BOUNDARY);
}

static void test_itree_hole_spike(void)
{
	const char *wktPoly =
		"POLYGON("
		"(-10 -10, 6 -10, 7 -10, 7.5 2, 8 -10, 9 -10, 10 -10, 10 10, -10 10, -10 2, -10 2, -10 -10),"
		"(-5 -5, -5 5, 5 5, 5 -5, -5 -5))";

	/* inside hole */
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);
	/* inside hole */
	test_itree_once(wktPoly, -2.0, -2.0, ITREE_OUTSIDE);
	/* left of spike */
	test_itree_once(wktPoly, 6.0, -2.0, ITREE_INSIDE);
	/* right of spike */
	test_itree_once(wktPoly, 9.0, -2.0, ITREE_INSIDE);
	/* left of tip of spike */
	test_itree_once(wktPoly, 6.0, 2.0, ITREE_INSIDE);
	/* right of tip of spike */
	test_itree_once(wktPoly, 9.0, 2.0, ITREE_INSIDE);
	/* on spike tip */
	test_itree_once(wktPoly, 7.5, 2.0, ITREE_BOUNDARY);
	/* left of dupe vertex */
	test_itree_once(wktPoly, -11, 2.0, ITREE_OUTSIDE);
	/* right of dupe vertex */
	test_itree_once(wktPoly, 11, 2.0, ITREE_OUTSIDE);
}

static void test_itree_multipoly_empty(void)
{

	/* outside empty */
	const char *wktPoly = "POLYGON EMPTY";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);

	/* outside empty */
	wktPoly = "MULTIPOLYGON EMPTY";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);

	/* outside collection of empty */
	wktPoly = "MULTIPOLYGON(EMPTY, EMPTY, EMPTY)";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);

	/* mixed collection of empty and not */
	wktPoly =
		"MULTIPOLYGON(EMPTY,"
		"((-10 -10, 6 -10, 7 -10, 7.5 2, 8 -10, 9 -10, 10 -10, 10 10, -10 10, -10 2, -10 2, -10 -10),"
		"(-5 -5, -5 5, 5 5, 5 -5, -5 -5)))";

	/* inside hole */
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);
	/* inside hole */
	test_itree_once(wktPoly, -2.0, -2.0, ITREE_OUTSIDE);
	/* left of spike */
	test_itree_once(wktPoly, 6.0, -2.0, ITREE_INSIDE);
	/* right of spike */
	test_itree_once(wktPoly, 9.0, -2.0, ITREE_INSIDE);
	/* left of tip of spike */
	test_itree_once(wktPoly, 6.0, 2.0, ITREE_INSIDE);
	/* right of tip of spike */
	test_itree_once(wktPoly, 9.0, 2.0, ITREE_INSIDE);
	/* on spike tip */
	test_itree_once(wktPoly, 7.5, 2.0, ITREE_BOUNDARY);
	/* left of dupe vertex */
	test_itree_once(wktPoly, -11, 2.0, ITREE_OUTSIDE);
	/* right of dupe vertex */
	test_itree_once(wktPoly, 11, 2.0, ITREE_OUTSIDE);

	/* mixed collection of empty, empty in middle */
	wktPoly =
		"MULTIPOLYGON("
		"((-10 -10, 6 -10, 7 -10, 7.5 2, 8 -10, 9 -10, 10 -10, 10 10, -10 10, -10 2, -10 2, -10 -10),"
		"(-5 -5, -5 5, 5 5, 5 -5, -5 -5)),"
		"EMPTY, ((-1 -1, 1 -1, 1 1, -1 1, -1 -1)))";

	/* inside poly in hole */
	test_itree_once(wktPoly, 0.0, 0.0, ITREE_INSIDE);
	/* inside hole */
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);
	/* inside hole */
	test_itree_once(wktPoly, -2.0, -2.0, ITREE_OUTSIDE);
	/* left of spike */
	test_itree_once(wktPoly, 6.0, -2.0, ITREE_INSIDE);
	/* right of spike */
	test_itree_once(wktPoly, 9.0, -2.0, ITREE_INSIDE);
	/* left of tip of spike */
	test_itree_once(wktPoly, 6.0, 2.0, ITREE_INSIDE);
	/* right of tip of spike */
	test_itree_once(wktPoly, 9.0, 2.0, ITREE_INSIDE);
	/* on spike tip */
	test_itree_once(wktPoly, 7.5, 2.0, ITREE_BOUNDARY);
	/* left of dupe vertex */
	test_itree_once(wktPoly, -11, 2.0, ITREE_OUTSIDE);
	/* right of dupe vertex */
	test_itree_once(wktPoly, 11, 2.0, ITREE_OUTSIDE);
}

static void test_itree_degenerate_poly(void)
{
	/* collapsed polygon */
	const char *wktPoly = "POLYGON((0 0, 0 0, 0 0, 0 0))";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);

	/* zero area polygon */
	wktPoly = "POLYGON((0 0, 0 1, 0 1, 0 0))";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);
	test_itree_once(wktPoly, 0, 0.5, ITREE_BOUNDARY);
	test_itree_once(wktPoly, 0, 1, ITREE_BOUNDARY);

	/* zero area polygon */
	wktPoly = "POLYGON((0 0, 0 1, 1 1, 2 2, 1 1, 0 1, 0 0))";
	test_itree_once(wktPoly, 0.5, 0.5, ITREE_OUTSIDE);
	test_itree_once(wktPoly, 1, 1, ITREE_BOUNDARY);

	/* non finite coordinates */
	wktPoly = "POLYGON((0 0, 0 NaN, 0 NaN, 0 0, 0 1, 0 0))";
	test_itree_once(wktPoly, 2.0, 2.0, ITREE_OUTSIDE);
	test_itree_once(wktPoly, 0, 0, ITREE_BOUNDARY);
}


static void test_geography_tree_closestpoint(void)
{
	LWGEOM *lwg1, *lwg2, *lwg3;
	LWPOINT *lwpt;
	POINT2D pt;

	/* Simple case */
	lwg1 = lwgeom_from_wkt("LINESTRING (18 9, 18 1)", LW_PARSER_CHECK_NONE);
	lwg2 = lwgeom_from_wkt("POINT (16 4)", LW_PARSER_CHECK_NONE);
	lwg3 = geography_tree_closestpoint(lwg1, lwg2, 0.1);
	lwpt = (LWPOINT *)lwg3;

	lwpoint_getPoint2d_p(lwpt, &pt);
	CU_ASSERT_DOUBLE_EQUAL(pt.x, 18, 0.0001);
	CU_ASSERT_DOUBLE_EQUAL(pt.y, 4.0024302, 0.0001);

	lwgeom_free(lwg1);
	lwgeom_free(lwg2);
	lwgeom_free(lwg3);
}

/*
** Used by test harness to register the tests in this file.
*/
void tree_suite_setup(void);
void tree_suite_setup(void)
{
	CU_pSuite suite = CU_add_suite("spatial_trees", NULL, NULL);
	PG_ADD_TEST(suite, test_itree_square);
	PG_ADD_TEST(suite, test_itree_hole);
	PG_ADD_TEST(suite, test_itree_hole_spike);
	PG_ADD_TEST(suite, test_itree_multipoly_empty);
	PG_ADD_TEST(suite, test_itree_degenerate_poly);
	PG_ADD_TEST(suite, test_tree_circ_create);
	PG_ADD_TEST(suite, test_tree_circ_pip);
	PG_ADD_TEST(suite, test_tree_circ_pip2);
	PG_ADD_TEST(suite, test_tree_circ_distance);
	PG_ADD_TEST(suite, test_tree_circ_distance_threshold);
	PG_ADD_TEST(suite, test_geography_tree_closestpoint);
}
