]> granicus.if.org Git - postgis/commitdiff
Add ST_ClosestPointOfApproach (#3128)
authorSandro Santilli <strk@keybit.net>
Tue, 26 May 2015 15:07:58 +0000 (15:07 +0000)
committerSandro Santilli <strk@keybit.net>
Tue, 26 May 2015 15:07:58 +0000 (15:07 +0000)
Based on new lwgeom_tcpa liblwgeom function.
Includes unit and regress tests.
Includes documentation.

git-svn-id: http://svn.osgeo.org/postgis/trunk@13560 b70326c6-7e19-0410-871a-916f4a2858ee

NEWS
doc/reference_lrs.xml
liblwgeom/cunit/cu_measures.c
liblwgeom/cunit/cu_tester.c
liblwgeom/cunit/cu_tester.h
liblwgeom/liblwgeom.h.in
liblwgeom/lwlinearreferencing.c
postgis/lwgeom_functions_lrs.c
postgis/postgis.sql.in
regress/regress_lrs.sql
regress/regress_lrs_expected

diff --git a/NEWS b/NEWS
index 140922454c6b251b4e7a536f6efbca23aafbb2c1..cd1318d5147c8d131703116fabfb2640b85512ef 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,7 @@ PostGIS 2.2.0
 
  * New Features *
 
+  - #3128, ST_ClosestPointOfApproach (Sandro Santilli / Boundless)
   - Canonical output for index key types
   - ST_SwapOrdinates (Sandro Santilli / Boundless)
   - #2918, Use GeographicLib functions for geodetics (Mike Toews)
index 759f5703c3bf81b02bcb2034c02c53ff06480acb..99741da1fc910b0130b6adb0c16bc201a3ef2092 100644 (file)
@@ -542,7 +542,7 @@ LINESTRING(6.1 7.1 6,7 8 9)
          <refnamediv>
                <refname>ST_AddMeasure</refname>
 
-               <refpurpose>Return a derived geometry with measure elements linearly interpolated between the start and end points. If the geometry has no measure dimension, one is added. If the geometry has a measure dimension, it is over-written with new values. Only LINESTRINGS and MULTILINESTRINGS are supported.</refpurpose>
+               <refpurpose>Return a derived geometry with measure elements linearly interpolated between the start and end points.</refpurpose>
          </refnamediv>
 
          <refsynopsisdiv>
@@ -597,6 +597,84 @@ ST_GeomFromEWKT('MULTILINESTRINGM((1 0 4, 2 0 4, 4 0 4),(1 0 4, 2 0 4, 4 0 4))')
          </refsection>
 
        </refentry>
-
        
+               <refentry id="ST_ClosestPointOfApproach">
+
+                 <refnamediv>
+                       <refname>ST_ClosestPointOfApproach</refname>
+                       <refpurpose>
+Returns the measure at which points interpolated along two lines are closest.
+      </refpurpose>
+                 </refnamediv>
+
+                 <refsynopsisdiv>
+                       <funcsynopsis>
+                         <funcprototype>
+                               <funcdef>float8 <function>ST_ClosestPointOfApproach</function></funcdef>
+                               <paramdef><type>geometry </type> <parameter>track1</parameter></paramdef>
+                               <paramdef><type>geometry </type> <parameter>track2</parameter></paramdef>
+                         </funcprototype>
+                       </funcsynopsis>
+                 </refsynopsisdiv>
+
+                 <refsection>
+                       <title>Description</title>
+
+                       <para>
+Returns the smallest measure at which point interpolated along the given
+lines are at the smallest distance. Inputs must be LINESTRINGM geometries
+and the M value in their vertices are expected to be growing from first
+to last vertex.
+                       </para>
+      
+                       <para>
+See <xref linkend="ST_LocateAlong" /> for getting the actual points at
+the given measure.
+                       </para>
+                       
+                       <para>Availability: 2.2.0</para>
+                       <para>&Z_support;</para>
+                 </refsection>
+
+
+                 <refsection>
+                       <title>Examples</title>
+<programlisting>
+-- Return the time in which two objects moving between 10:00 and 11:00
+-- are closest to each other and their distance at that point
+WITH inp AS ( SELECT
+  ST_AddMeasure('LINESTRING Z (0 0 0, 10 0 5)'::geometry,
+    extract(epoch from '2015-05-26 10:00'::timestamptz),
+    extract(epoch from '2015-05-26 11:00'::timestamptz)
+  ) a,
+  ST_AddMeasure('LINESTRING Z (0 2 10, 12 1 2)'::geometry,
+    extract(epoch from '2015-05-26 10:00'::timestamptz),
+    extract(epoch from '2015-05-26 11:00'::timestamptz)
+  ) b
+), cpa AS (
+  SELECT ST_ClosestPointOfApproach(a,b) m FROM inp
+), points AS (
+  SELECT ST_Force3DZ(ST_GeometryN(ST_LocateAlong(a,m),1)) pa,
+         ST_Force3DZ(ST_GeometryN(ST_LocateAlong(b,m),1)) pb
+  FROM inp, cpa
+)
+SELECT to_timestamp(m) t,
+       ST_Distance(pa,pb) distance
+FROM points, cpa;
+
+               t               |     distance
+-------------------------------+------------------
+ 2015-05-26 10:45:31.034483+02 | 1.96036833151395
+</programlisting>
+                 </refsection>
+
+                 <!-- Optionally add a "See Also" section -->
+                 <refsection>
+                       <title>See Also</title>
+                       <para>
+<xref linkend="ST_LocateAlong" />,
+<xref linkend="ST_AddMeasure" />
+                       </para>
+                 </refsection>
+               </refentry>
   </sect1>
index b424f81dcd40ff7ec11e38d9ef7364a4ac7f1d80..466732e0a67d43d3670322fa5e9e6b3003076638 100644 (file)
@@ -2,7 +2,9 @@
  *
  * PostGIS - Spatial Types for PostgreSQL
  * http://postgis.net
- * Copyright 2009 Paul Ramsey <pramsey@cleverelephant.ca>
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ * Copyright (C) 2009 Paul Ramsey <pramsey@cleverelephant.ca>
  *
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU General Public Licence. See the COPYING file.
@@ -869,6 +871,226 @@ test_lw_dist2d_ptarray_ptarrayarc(void)
        lwline_free(lwline1);
 }
 
+static void
+test_lwgeom_tcpa(void)
+{
+       LWGEOM *g1, *g2;
+       double m, dist;
+
+  /* Invalid input, lack of dimensions */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING (0 0 2, 0 0 5)", LW_PARSER_CHECK_NONE);
+       m = lwgeom_tcpa(g1, g2, NULL);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, -1.0);
+       CU_ASSERT_STRING_EQUAL(
+    "Both input geometries must have a measure dimension",
+    cu_error_msg
+  );
+
+  /* Invalid input, not linestrings */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("POINT M (0 0 2)", LW_PARSER_CHECK_NONE);
+       m = lwgeom_tcpa(g1, g2, NULL);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, -1.0);
+       CU_ASSERT_STRING_EQUAL(
+    "Both input geometries must be linestrings",
+    cu_error_msg
+  );
+
+  /* Invalid input, too short linestring */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(2 0 1)", LW_PARSER_CHECK_NONE);
+  dist = -77;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(dist, -77.0); /* not touched */
+       ASSERT_DOUBLE_EQUAL(m, -1.0);
+       CU_ASSERT_STRING_EQUAL(
+    "Both input lines must have at least 2 points",
+    cu_error_msg
+  );
+
+  /* Invalid input, empty linestring */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M EMPTY", LW_PARSER_CHECK_NONE);
+       m = lwgeom_tcpa(g1, g2, NULL);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, -1.0);
+       CU_ASSERT_STRING_EQUAL(
+    "Both input lines must have at least 2 points",
+    cu_error_msg
+  );
+
+  /* Invalid input, timeranges do not overlap */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(0 0 2, 0 0 5)", LW_PARSER_CHECK_NONE);
+       m = lwgeom_tcpa(g1, g2, NULL);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, -1.0);
+       CU_ASSERT_STRING_EQUAL(
+    "Inputs never exist at the same time",
+    cu_error_msg
+  );
+
+  /* One of the tracks is still, the other passes to that point */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 5)", LW_PARSER_CHECK_NONE);
+  dist  = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 1.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /* One of the tracks is still, the other passes at 10 meters from point */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 5)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(-10 10 1, 10 10 5)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 3.0);
+       ASSERT_DOUBLE_EQUAL(dist, 10.0);
+
+  /* Equal tracks, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 10.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /* Reversed tracks, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(10 0 10, 0 0 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 15.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /*  Parallel tracks, same speed, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(2 0 10, 12 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(13 0 10, 23 0 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 10.0);
+       ASSERT_DOUBLE_EQUAL(dist, 11.0);
+
+  /*  Parallel tracks, different speed (g2 gets closer as time passes), 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(4 0 10, 10 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(2 0 10,  9 0 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 20.0);
+       ASSERT_DOUBLE_EQUAL(dist, 1.0);
+
+  /*  Parallel tracks, different speed (g2 left behind as time passes), 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(4 0 10, 10 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(2 0 10,  6 0 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 10.0);
+       ASSERT_DOUBLE_EQUAL(dist, 2.0);
+
+  /* Tracks, colliding, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 10 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(5 -8 0,  5 8 10)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 5.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /* Tracks crossing, NOT colliding, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 10 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(8 -5 0,  8 5 10)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 6.5);
+       ASSERT_DOUBLE_EQUAL(rint(dist*100), 212.0);
+
+  /*  Same origin, different direction, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(0 0 1, -100 0 10)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 1.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /*  Same ending, different direction, 2d */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(10 0 1, 0 0 10)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(0 -100 1, 0 0 10)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 10.0);
+       ASSERT_DOUBLE_EQUAL(dist, 0.0);
+
+  /* Converging tracks, 3d */
+
+       g1 = lwgeom_from_wkt("LINESTRING ZM(0 0 0 10, 10 0 0 20)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING ZM(0 0 8 10, 10 0 5 20)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 20.0);
+       ASSERT_DOUBLE_EQUAL(dist, 5.0);
+
+
+  /* G1 stops at t=1 until t=4 to let G2 pass by, then continues */
+  /* G2 passes at 1 meter from G1 t=3 */
+
+       g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 0 1 1, 0 1 4, 0 10 13)", LW_PARSER_CHECK_NONE);
+       g2 = lwgeom_from_wkt("LINESTRING M(-10 2 0, 0 2 3, 12 2 13)", LW_PARSER_CHECK_NONE);
+  dist = -1;
+       m = lwgeom_tcpa(g1, g2, &dist);
+       lwgeom_free(g1);
+       lwgeom_free(g2);
+       ASSERT_DOUBLE_EQUAL(m, 3.0);
+       ASSERT_DOUBLE_EQUAL(dist, 1.0);
+
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -887,4 +1109,5 @@ void measures_suite_setup(void)
        PG_ADD_TEST(suite, test_lw_arc_length);
        PG_ADD_TEST(suite, test_lw_dist2d_pt_ptarrayarc);
        PG_ADD_TEST(suite, test_lw_dist2d_ptarray_ptarrayarc);
+       PG_ADD_TEST(suite, test_lwgeom_tcpa);
 }
index 5093d833b183de6adb74bd8924351dac6d5b71ba..ccbb22663fa48f10a98a70665c0039be2a6a678e 100644 (file)
@@ -250,6 +250,7 @@ cu_errorreporter(const char *fmt, va_list ap)
 {
   vsnprintf (cu_error_msg, MAX_CUNIT_MSG_LENGTH, fmt, ap);
   cu_error_msg[MAX_CUNIT_MSG_LENGTH]='\0';
+  /* fprintf(stderr, "ERROR: %s\n", cu_error_msg); */
 }
 
 void
index c9642dcb0955bd1bcd4af504ea31d99360f4ec1c..c8aab1d28c6a1e081a19500141594a1b7fcf353c 100644 (file)
@@ -3,6 +3,8 @@
  * PostGIS - Spatial Types for PostgreSQL
  * http://postgis.net
  *
+ * Copyright (C) 2009 Paul Ramsey <pramsey@cleverelephant.ca>
+ *
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU General Public Licence. See the COPYING file.
  *
@@ -20,3 +22,11 @@ void cu_error_msg_reset(void);
 
 /* Our internal callback to register Suites with the main tester */
 typedef void (*PG_SuiteSetup)(void);
+
+#define ASSERT_DOUBLE_EQUAL(o,e) do { \
+  if ( o != e ) \
+    fprintf(stderr, "[%s:%d]\n Expected: %g\n Obtained: %g\n", __FILE__, __LINE__, (e), (o)); \
+  CU_ASSERT_EQUAL(o,e); \
+} while (0);
+
+
index d1698c776890cca3fb9da5adb249a697dfe1780a..0d68d045d8d6a9ba23e6ea3f032d4575676815e1 100644 (file)
@@ -1396,6 +1396,16 @@ extern LWCOLLECTION* lwgeom_locate_between(const LWGEOM *lwin, double from, doub
 */
 extern double lwgeom_interpolate_point(const LWGEOM *lwin, const LWPOINT *lwpt);
 
+/**
+* Find the time of closest point of approach
+*
+* @param mindist if not null will be set to the minimum distance between
+*                the trajectories at the closest point of approach.
+*
+* @return the time value in which the minimum distance was reached
+*/
+extern double lwgeom_tcpa(const LWGEOM *g1, const LWGEOM *g2, double *mindist);
+
 /*
  * Ensure every segment is at most 'dist' long.
  * Returned LWGEOM might is unchanged if a POINT.
index 3f3f4cfd7efc17d2982a0ec9ba290af30eb356df..b728580d35d9cad33e265adb603dbc3a283db4bc 100644 (file)
@@ -2,7 +2,9 @@
  *
  * PostGIS - Spatial Types for PostgreSQL
  * http://postgis.net
- * Copyright 2011 Paul Ramsey
+ *
+ * Copyright (C) 2015 Sandro Santilli <strk@keybit.net>
+ * Copyright (C) 2011 Paul Ramsey
  *
  * This is free software; you can redistribute and/or modify it under
  * the terms of the GNU General Public Licence. See the COPYING file.
@@ -11,7 +13,7 @@
 
 #include "liblwgeom_internal.h"
 #include "lwgeom_log.h"
-
+#include "measures3d.h"
 
 static int 
 segment_locate_along(const POINT4D *p1, const POINT4D *p2, double m, double offset, POINT4D *pn)
@@ -849,3 +851,350 @@ lwgeom_interpolate_point(const LWGEOM *lwin, const LWPOINT *lwpt)
        }
        return ret;
 }
+
+/*
+ * Time of closest point of approach
+ * 
+ * Given two vectors (p1-p2 and q1-q2) and
+ * a time range (t1-t2) return the time in which
+ * a point p is closest to a point q on their
+ * respective vectors, and the actual points
+ *
+ * Here we use algorithm from softsurfer.com
+ * that can be found here
+ * http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
+ *
+ * @param p0 start of first segment, will be set to actual
+ *           closest point of approach on segment.
+ * @param p1 end of first segment
+ * @param q0 start of second segment, will be set to actual
+ *           closest point of approach on segment.
+ * @param q1 end of second segment
+ * @param t0 start of travel time
+ * @param t1 end of travel time
+ *
+ * @return time of closest point of approach
+ *
+ */
+static double
+segments_tcpa(POINT4D* p0, const POINT4D* p1,
+        POINT4D* q0, const POINT4D* q1,
+        double t0, double t1)
+{
+  POINT3DZ pv; /* velocity of p, aka u */
+  POINT3DZ qv; /* velocity of q, aka v */
+  POINT3DZ dv; /* velocity difference */
+  POINT3DZ w0; /* vector between first points */
+
+/*
+  lwnotice("FROM %g,%g,%g,%g -- %g,%g,%g,%g",
+    p0->x, p0->y, p0->z, p0->m,
+    p1->x, p1->y, p1->z, p1->m);
+  lwnotice("  TO %g,%g,%g,%g -- %g,%g,%g,%g",
+    q0->x, q0->y, q0->z, q0->m,
+    q1->x, q1->y, q1->z, q1->m);
+*/
+
+  /* PV aka U */
+  pv.x = ( p1->x - p0->x );
+  pv.y = ( p1->y - p0->y );
+  pv.z = ( p1->z - p0->z );
+  /*lwnotice("PV:  %g, %g, %g", pv.x, pv.y, pv.z);*/
+
+  /* QV aka V */
+  qv.x = ( q1->x - q0->x );
+  qv.y = ( q1->y - q0->y );
+  qv.z = ( q1->z - q0->z );
+  /*lwnotice("QV:  %g, %g, %g", qv.x, qv.y, qv.z);*/
+
+  dv.x = pv.x - qv.x;
+  dv.y = pv.y - qv.y;
+  dv.z = pv.z - qv.z;
+  /*lwnotice("DV:  %g, %g, %g", dv.x, dv.y, dv.z);*/
+
+  double dv2 = DOT(dv,dv);
+  /*lwnotice("DOT: %g", dv2);*/
+
+  if ( dv2 == 0.0 )
+  {
+    /* Distance is the same at any time, we pick the earliest */
+    return t0;
+  }
+
+  /* Distance at any given time, with t0 */
+  w0.x = ( p0->x - q0->x );
+  w0.y = ( p0->y - q0->y );
+  w0.z = ( p0->z - q0->z );
+
+  /*lwnotice("W0:  %g, %g, %g", w0.x, w0.y, w0.z);*/
+
+  /* Check that at distance dt w0 is distance */
+
+  /* This is the fraction of measure difference */
+  double t = -DOT(w0,dv) / dv2;
+  /*lwnotice("CLOSEST TIME (fraction): %g", t);*/
+
+  if ( t > 1.0 ) {
+    /* Getting closer as we move to the end */
+    /*lwnotice("Converging");*/
+    t = 1;
+  } else if ( t < 0.0 ) {
+    /*lwnotice("Diverging");*/
+    t = 0;
+  }
+
+  /* Interpolate the actual points now */
+
+  p0->x += pv.x * t;
+  p0->y += pv.y * t;
+  p0->z += pv.z * t;
+
+  q0->x += qv.x * t;
+  q0->y += qv.y * t;
+  q0->z += qv.z * t;
+
+  t = t0 + (t1 - t0) * t;
+  /*lwnotice("CLOSEST TIME (real): %g", t);*/
+
+  return t;
+}
+
+static int
+ptarray_collect_mvals(const POINTARRAY *pa, double tmin, double tmax, double *mvals)
+{
+  POINT4D pbuf;
+  int i, n=0;
+  for (i=0; i<pa->npoints; ++i)
+  {
+    getPoint4d_p(pa, i, &pbuf); /* could be optimized */
+    if ( pbuf.m >= tmin && pbuf.m <= tmax )
+      mvals[n++] = pbuf.m;
+  }
+  return n;
+}
+
+static int
+compare_double(const void *a, const void *b)
+{
+  double *dpa = (double *)a;
+  double *dpb = (double *)b;
+  return *dpb < *dpa;
+}
+
+/* Return number of elements in unique array */
+static int
+uniq(double *vals, int nvals)
+{
+  int i, last=0;
+  for (i=1; i<nvals; ++i)
+  {
+    /*lwnotice("(I%d):%g", i, vals[i]);*/
+    if ( vals[i] != vals[last] )
+    {
+      vals[++last] = vals[i];
+      /*lwnotice("(O%d):%g", last, vals[last]);*/
+    }
+  }
+  return last+1;
+}
+
+/*
+ * Find point at a given measure
+ *
+ * The function assumes measures are linear so that always a single point
+ * is returned for a single measure.
+ *
+ * @param pa the point array to perform search on
+ * @param m the measure to search for
+ * @param p the point to write result into
+ * @param from the segment number to start from
+ *
+ * @return the segment number the point was found into
+ *         or -1 if given measure was out of the known range.
+ */
+static int
+ptarray_locate_along_linear(const POINTARRAY *pa, double m, POINT4D *p, int from)
+{
+       int i = from;
+       POINT4D p1, p2;
+               
+       /* Walk through each segment in the point array */
+  getPoint4d_p(pa, i, &p1);
+       for ( i = 1; i < pa->npoints; i++ )
+       {
+               getPoint4d_p(pa, i, &p2);
+
+               if ( segment_locate_along(&p1, &p2, m, 0, p) == LW_TRUE )
+                       return i-1; /* found */
+
+    p1 = p2;
+       }       
+
+       return -1; /* not found */
+}
+
+double
+lwgeom_tcpa(const LWGEOM *g1, const LWGEOM *g2, double *mindist)
+{
+  LWLINE *l1, *l2;
+  int i;
+  const GBOX *gbox1, *gbox2;
+  double tmin, tmax;
+  double *mvals;
+  int nmvals = 0;
+  double mintime;
+  double mindist2; /* minimum distance, squared */
+
+       if ( ! lwgeom_has_m(g1) || ! lwgeom_has_m(g2) ) {
+               lwerror("Both input geometries must have a measure dimension");
+    return -1;
+  }
+
+  l1 = lwgeom_as_lwline(g1);
+  l2 = lwgeom_as_lwline(g2);
+
+       if ( ! l1 || ! l2 ) {
+               lwerror("Both input geometries must be linestrings");
+    return -1;
+  }
+
+  if ( l1->points->npoints < 2 || l2->points->npoints < 2 ) {
+               lwerror("Both input lines must have at least 2 points");
+    return -1;
+  }
+
+  gbox1 = lwgeom_get_bbox(g1);
+  gbox2 = lwgeom_get_bbox(g2);
+
+  assert(gbox1); /* or the npoints check above would have failed */
+  assert(gbox2); /* or the npoints check above would have failed */
+
+  /*
+   * Find overlapping M range
+   */
+
+  tmin = FP_MAX(gbox1->mmin, gbox2->mmin);
+  tmax = FP_MIN(gbox1->mmax, gbox2->mmax);
+
+  if ( tmax == tmin ) /* both exists only at a given time */
+  {
+    /*lwnotice("Inputs only exist both at a single time");*/
+    if ( mindist ) 
+    {
+      *mindist = lwgeom_mindistance3d(g1, g2);
+    }
+    return tmax;
+  }
+
+  if ( tmax < tmin ) {
+    lwerror("Inputs never exist at the same time");
+    return -1;
+  }
+
+  /* lwnotice("Min:%g, Max:%g", tmin, tmax); */
+
+  /*
+   * Collect M values in common time range from inputs
+   */
+
+  mvals = lwalloc( sizeof(double*) *
+                   ( l1->points->npoints + l2->points->npoints ) );
+
+  /* TODO: also clip the lines ? */
+  nmvals  = ptarray_collect_mvals(l1->points, tmin, tmax, mvals);
+  nmvals += ptarray_collect_mvals(l2->points, tmin, tmax, &(mvals[nmvals]));
+
+  /* Sort values in ascending order */
+  qsort(mvals, nmvals, sizeof(double), compare_double);
+
+  /* Remove duplicated values */
+  nmvals = uniq(mvals, nmvals);
+
+  if ( nmvals < 2 )
+    return mvals[0]; /* there's a single time, must be that one... */
+
+  /*
+   * For each consecutive pair of measures, compute time of closest point
+   * approach and actual distance between points at that time
+   */
+  mintime = tmin;
+  for (i=1; i<nmvals; ++i)
+  {
+    double t0 = mvals[i-1];
+    double t1 = mvals[i];
+    double t;
+    POINT4D p0, p1, q0, q1;
+    int seg;
+    double dist2;
+
+    /*lwnotice("T %g-%g", t0, t1);*/
+
+    seg = ptarray_locate_along_linear(l1->points, t0, &p0, 0);
+    if ( -1 == seg )
+    {
+      lwfree(mvals);
+      lwerror("Non-linear measures in first geometry");
+      return -1;
+    }
+    /*lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z);*/
+
+    seg = ptarray_locate_along_linear(l1->points, t1, &p1, seg);
+    if ( -1 == seg )
+    {
+      lwfree(mvals);
+      lwerror("Non-linear measures in first geometry");
+      return -1;
+    }
+    /*lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z);*/
+
+    seg = ptarray_locate_along_linear(l2->points, t0, &q0, 0);
+    if ( -1 == seg )
+    {
+      lwfree(mvals);
+      lwerror("Non-linear measures in second geometry");
+      return -1;
+    }
+    /*lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z);*/
+
+    seg = ptarray_locate_along_linear(l2->points, t1, &q1, seg);
+    if ( -1 == seg )
+    {
+      lwfree(mvals);
+      lwerror("Non-linear measures in second geometry");
+      return -1;
+    }
+    /*lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z);*/
+
+    t = segments_tcpa(&p0, &p1, &q0, &q1, t0, t1);
+
+/*
+    lwnotice("Closest points: %g,%g,%g and %g,%g,%g at time %g",
+      p0.x, p0.y, p0.z,
+      q0.x, q0.y, q0.z, t);
+*/
+
+    dist2 = ( q0.x - p0.x ) * ( q0.x - p0.x ) +
+           ( q0.y - p0.y ) * ( q0.y - p0.y ) +
+           ( q0.z - p0.z ) * ( q0.z - p0.z );
+    if ( i == 1 || dist2 < mindist2 )
+    {
+      mindist2 = dist2;
+      mintime = t;
+      /* lwnotice("MINTIME: %g", mintime); */
+    }
+
+  }
+
+  /*
+   * Release memory
+   */
+
+  lwfree(mvals);
+
+  if ( mindist ) {
+    *mindist = sqrt(mindist2);
+  }
+  /*lwnotice("MINDIST: %g", sqrt(mindist2));*/
+
+  return mintime;
+}
index 1f01da16be6a7e66156019cdc7794f669ec5e02d..07c11107f05d6ce30193e2aa35fbc8200ebbb6e4 100644 (file)
@@ -750,3 +750,29 @@ Datum LWGEOM_locate_between_m(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(gout);
 }
 
+/***********************************************************************
+ *
+ * Spatio-Temporal functions
+ *
+ ************************************************************************/
+
+/*
+ * Return the measure at which interpolated points on the two
+ * input lines are at the smallest distance.
+ */
+Datum ST_ClosestPointOfApproach(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_ClosestPointOfApproach);
+Datum ST_ClosestPointOfApproach(PG_FUNCTION_ARGS)
+{
+  GSERIALIZED *gs0 = PG_GETARG_GSERIALIZED_P(0);
+  GSERIALIZED *gs1 = PG_GETARG_GSERIALIZED_P(1);
+  /* All checks already performed by liblwgeom, not worth checking again */
+  LWGEOM *g0 = lwgeom_from_gserialized(gs0);
+  LWGEOM *g1 = lwgeom_from_gserialized(gs1);
+  double m = lwgeom_tcpa(g0, g1, NULL);
+       lwgeom_free(g0);
+       lwgeom_free(g1);
+  PG_FREE_IF_COPY(gs0, 0);
+  PG_FREE_IF_COPY(gs1, 1);
+       PG_RETURN_FLOAT8(m);
+}
index 69114c0fe4f0cd3efdb0e52571c78243248d4510..ad297b0446a8e37fd3c92b9dbd17ed0191509fdc 100644 (file)
@@ -3043,6 +3043,16 @@ CREATE OR REPLACE FUNCTION ST_AddMeasure(geometry, float8, float8)
        RETURNS geometry 
        AS 'MODULE_PATHNAME', 'ST_AddMeasure' 
        LANGUAGE 'c' IMMUTABLE STRICT;
+
+---------------------------------------------------------------
+-- TEMPORAL
+---------------------------------------------------------------
+
+-- Availability: 2.2.0
+CREATE OR REPLACE FUNCTION ST_ClosestPointOfApproach(geometry, geometry)
+       RETURNS float8 
+       AS 'MODULE_PATHNAME', 'ST_ClosestPointOfApproach' 
+       LANGUAGE 'c' IMMUTABLE STRICT;
     
 ---------------------------------------------------------------
 -- GEOS
index 7663058cc31965abeab61c09b6cb3c4e000488bb..9e6b42d72fd7e5c280616036d1a4aa8d54d30ac4 100644 (file)
@@ -60,3 +60,20 @@ select 'line_substring_12', ST_AsText(ST_LineSubstring('LINESTRING(0 0 10, 1 1 5
 
 select 'line_interpolate_point', ST_AsText(ST_LineInterpolatePoint('LINESTRING(0 0, 1 1)', 0));
 select 'line_interpolate_point', ST_AsText(ST_LineInterpolatePoint('LINESTRING(0 0 10, 1 1 5)', 0.5));
+
+--- ST_PointOfClosestApproach
+
+-- Converging
+select 'pca1', ST_ClosestPointOfApproach(
+  'LINESTRINGZM(0 0 0 0, 10 10 10 10)',
+  'LINESTRINGZM(0 0 0 1, 10 10 10 10)');
+-- Following
+select 'pca2', ST_ClosestPointOfApproach(
+  'LINESTRINGZM(0 0 0 0, 10 10 10 10)',
+  'LINESTRINGZM(0 0 0 5, 10 10 10 15)');
+-- Crossing
+select 'pca3', ST_ClosestPointOfApproach(
+  'LINESTRINGZM(0 0 0 0, 0 0 0 10)',
+  'LINESTRINGZM(-30 0 5 4, 10 0 5 6)');
+
+-- TODO: test ST_AddMeasures 
index a20d29c629984875fbc13a95321ee91323ed45b0..5814422823aa80afc68f6dd9461eb671cefb3eb0 100644 (file)
@@ -34,3 +34,6 @@ line_substring_11|POINT(0 0)
 line_substring_12|POINT Z (0.5 0.5 7.5)
 line_interpolate_point|POINT(0 0)
 line_interpolate_point|POINT Z (0.5 0.5 7.5)
+pca1|10
+pca2|5
+pca3|5.5