[prev in list] [next in list] [prev in thread] [next in thread] 

List:       postgis-users
Subject:    Re: [postgis-users] Issue with ST_Intersection seems related to lines crossing themself
From:       magog002 () web ! de
Date:       2022-10-31 15:08:04
Message-ID: trinity-90d1c190-45b4-4ac5-a8b2-c5e58bc379cf-1667228884494 () 3c-app-webde-bs22
[Download RAW message or body]

[Attachment #2 (text/html)]

<html><head></head><body><div style="font-family: Verdana;font-size: 12.0px;"><div>Hi \
Rory,</div>

<div>&nbsp;</div>

<div>thank you for that blog post. That was helpful.</div>

<div>&nbsp;</div>

<div>What I implemented also on friday was the query down below for the most recent \
data catched directly from the stored ais position reports. There are some of the \
checks missing you added into your example.</div>

<div>This is basically the part to get track from today (for older days this can be \
combined using&nbsp;UNION to get the details directly from the stored table with the \
stored MultiLinestrings +&nbsp;MultiPoints created before).</div>

<div>&nbsp;</div>

<div>In general the missing part is still the option to get back to the PointM (the \
stored timestamp) when using ST_Intersection because I need to look into the data of \
a complete month and looking for 1.8 Mio single points per day&nbsp;* 31 days of \
points is too slow. Using the linestring with ST_Intersecs from the daily stored \
data&nbsp;is much&nbsp;faster and I can get the first and last Point on that day from \
it...the the annoying issue of loosing the timestamp.</div>

<div>So basically when I have the mmsi,&nbsp;the points and I know at least the day \
and I need to do another lookup with only this to find that Point in my stored \
MultiPointM column.&nbsp;</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>--<br/>
-- Vessel tracks with separated LineStrings combined into Multilinestring</div>

<div>-- A track is separated if a point is &gt; 7min away from the previous \
entry.</div>

<div>-- What&#39;s missing is the check that at least 2 points are required to build \
a linestring.</div>

<div>-- Also additional checks could be added.<br/>
--<br/>
WITH cte_point_m_pos_today AS (<br/>
&nbsp; &nbsp; --<br/>
&nbsp; &nbsp; -- Get data from today (since 00:00)<br/>
&nbsp; &nbsp; --<br/>
&nbsp; &nbsp; SELECT ais_positions_age_tagged.mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,ais_positions_age_tagged.geom<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,ais_positions_age_tagged.created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,ais_positions_age_tagged.ais_position_to_old<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,SUM(ais_positions_age_tagged.ais_position_to_old) \
OVER (PARTITION BY ais_positions_age_tagged.mmsi, \
CAST(ais_positions_age_tagged.created_on AS DATE) ORDER BY \
ais_positions_age_tagged.created_on) AS ais_position_cluster_group<br/> &nbsp; &nbsp; \
FROM (<br/> &nbsp; &nbsp; &nbsp; &nbsp; SELECT mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,geo_lat_long_point AS geom<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,COALESCE( CAST( created_on - \
(LAG(created_on) OVER (PARTITION BY mmsi, CAST(created_on AS DATE) ORDER BY \
created_on)) &gt; CAST(&#39;00:07:00&#39; AS INTERVAL) AS INTEGER), 0) AS \
ais_position_to_old<br/> &nbsp; &nbsp; &nbsp; &nbsp; FROM &nbsp; \
ais_position_report<br/> &nbsp; &nbsp; &nbsp; &nbsp; WHERE created_on &gt;= CAST( \
CURRENT_DATE AS TIMESTAMPTZ)<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND created_on \
&lt; &nbsp;CAST( CURRENT_DATE AS TIMESTAMPTZ) + INTERVAL &#39;1 day&#39;<br/> &nbsp; \
&nbsp; &nbsp; &nbsp; &nbsp; AND mmsi =&nbsp; :the_mmsi_to_lookup<br/> &nbsp; &nbsp; \
&nbsp;) ais_positions_age_tagged<br/> ) -- CTE-1<br/>
SELECT mmsi<br/>
&nbsp; &nbsp; &nbsp; ,MIN(min_created_on) AS min_created_on<br/>
&nbsp; &nbsp; &nbsp; ,MAX(max_created_on) AS max_created_on<br/>
&nbsp; &nbsp; &nbsp; ,ST_Collect(geom ORDER BY min_created_on) AS \
geo_lat_long_line<br/> FROM (<br/>
&nbsp; &nbsp;SELECT mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,MIN(created_on) AS min_created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,MAX(created_on) AS max_created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,CAST(created_on AS DATE) AS created_on &nbsp; \
&nbsp; &nbsp;<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,ST_MakeLine(geom ORDER BY mmsi, \
created_on) AS geom<br/> &nbsp; &nbsp;FROM cte_point_m_pos_today<br/>
&nbsp; &nbsp;GROUP BY mmsi, CAST(created_on AS DATE), ais_position_cluster_group<br/>
) AS my_daily_multilinestring_geom<br/>
GROUP BY mmsi, created_on<br/>
;</div>

<div>&nbsp;
<div>&nbsp;
<div name="quote" style="margin:10px 5px 5px 10px; padding: 10px 0 10px 10px; \
border-left:2px solid #C3D9E5; word-wrap: break-word; -webkit-nbsp-mode: space; \
-webkit-line-break: after-white-space;"> <div style="margin:0 0 10px \
0;"><b>Gesendet:</b>&nbsp;Freitag, 28. Oktober 2022 um 10:22 Uhr<br/> \
<b>Von:</b>&nbsp;&quot;Rory Meyer&quot; &lt;rory.meyer@VLIZ.be&gt;<br/> \
<b>An:</b>&nbsp;&quot;PostGIS Users Discussion&quot; \
&lt;postgis-users@lists.osgeo.org&gt;<br/> <b>Betreff:</b>&nbsp;Re: [postgis-users] \
Issue with ST_Intersection seems related to lines crossing themself</div>

<div name="quoted-content"><!--P {
	margin-top: 0;
	margin-bottom: 0;
}
-->
<div>
<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">Hi Juergen,</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">I wrote a quick blog post about how I grouped up AIS points into \
lines, but broken up on steps in time, distance, and calculated speed.&nbsp;</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">It&#39;s very short but contains some thinking and some \
code.</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="elementToProof ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);"><a href="https://openais.xyz/post/2022-10-28-ais-traj/" \
target="_blank">https://openais.xyz/post/2022-10-28-ais-traj/</a></div>

<div class="_Entity _EType_OWALinkPreview _EId_OWALinkPreview \
_EReadonly_1">&nbsp;</div>

<div class="elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">You could add an additional spatial filter in the first part of \
the blog query to only fetch AIS points that are within the geometry that defines \
your port area.</div>

<div class="elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">Rory</div>

<div id="appendonsend">&nbsp;</div>

<hr style="display: inline-block;width: 98.0%;"/>
<div id="divRplyFwdMsg"><font color="#000000" face="Calibri, sans-serif" \
style="font-size: 11.0pt;"><b>From:</b> postgis-users \
&lt;postgis-users-bounces@lists.osgeo.org&gt; on behalf of magog002@web.de \
&lt;magog002@web.de&gt;<br/> <b>Sent:</b> 24 October 2022 19:40<br/>
<b>To:</b> postgis-users@lists.osgeo.org &lt;postgis-users@lists.osgeo.org&gt;<br/>
<b>Subject:</b> Re: [postgis-users] Issue with ST_Intersection seems related to lines \
crossing themself</font>

<div>&nbsp;</div>
</div>

<div>
<div style="font-family: Verdana;font-size: 12.0px;">
<div>
<div><br/>
Hello Rory,</div>

<div>Hello Simon,</div>

<div>&nbsp;</div>

<div>thank you both for the feedback.</div>

<div>&nbsp;</div>

<div>Sorry for the delay. I&#39;m now back on this topic after my crazy&nbsp;last \
week.</div>

<div>&nbsp;</div>

<div><br/>
First some additional explanation:<br/>
==================================<br/>
I use the LineString also to draw the track on a map (for each vessel / MMSI).<br/>
The track LineString&nbsp;from 00:00 of the current date to NOW() is generated \
&quot;on-the-fly&quot; from the stored GPS points (basically the RAW data \
you&nbsp;you get from the &quot;AIS&nbsp;position report&quot;).<br/> For today&#39;s \
data + 1-2 days older, the &quot;on-the-fly&quot; generated information is combined \
with a UNION from the information retrieved from \
table&nbsp;&#39;ais_trajectory_mmsi_daily&#39;.<br/> Currently I&#39;ve got ~2 \
million new GPS position&nbsp;entries each day.</div>

<div>&nbsp;</div>

<div>In general my aggregation of points per day&nbsp;produces lines that go \
&quot;over land&quot; if there is no data for some time until the next Antenna picks \
up something again (or&nbsp;jumping points).<br/> As you Rory pointed out I might \
have&nbsp;to go the Multi-LineString way so if I don&#39;t get new GPS points for \
about ~10+ Min (vessel out of antenna range; by AIS definition the max. interval \
is&nbsp;180 seconds)&nbsp;I start a new LineString (plus some other checks like \
distance).<br/> More on this further down below as this is an extra task.</div>

<div>&nbsp;</div>

<div><br/>
To answer both of your questions:<br/>
=================================<br/>
Yes I can also use MultiPoint data.<br/>
I have now added a new column to store the MultiPoint (type: PointM) to the table \
&#39;ais_trajectory_mmsi_daily&#39;.<br/> To test it I updated the missing data from \
my stored individual point data &quot;AIS&nbsp;position report&quot; (which is only a \
short time storage due to the large amount of data coming in).<br/> I also fixed the \
old LineString column definition which was missing the explicit SRID before.</div>

<div>&nbsp;</div>

<div>--<br/>
-- Add extra column to also store the PointM (Point + Timestamp) data as MultiPointM \
                geometry (WGS84/GPS).<br/>
--<br/>
ALTER TABLE ais_trajectory_mmsi_daily &nbsp;ADD COLUMN geom_multipointm \
GEOMETRY(MULTIPOINTM, 4326);</div>

<div>&nbsp;</div>

<div>--<br/>
-- Change column definition to geom type of LineStringM with WGS84 (SRID: 4326).<br/>
--<br/>
ALTER TABLE ais_trajectory_mmsi_daily ALTER COLUMN geo_lat_long_line TYPE \
GEOMETRY(LINESTRINGM, 4326) USING ST_SetSRID(geo_lat_long_line, 4326);</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>The complete table looks like this now:<br/>
=======================================<br/>
CREATE TABLE IF NOT EXISTS aisstream.ais_trajectory_mmsi_daily<br/>
(<br/>
&nbsp;&nbsp;&nbsp;&nbsp;mmsi&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;integer,<br/> &nbsp;&nbsp;&nbsp;&nbsp;min_created_on&nbsp;&nbsp;&nbsp;&nbsp;timestamp \
with time zone,<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;max_created_on&nbsp;&nbsp;&nbsp;&nbsp;timestamp with time \
zone,<br/> &nbsp;&nbsp;&nbsp;&nbsp;geom_linestring&nbsp;&nbsp;&nbsp;geometry(LineStringM,4326),&nbsp;&nbsp;&nbsp;&nbsp; \
-- Fixed: Added exact type + SRID 4326!<br/> \
&nbsp;&nbsp;&nbsp;&nbsp;geom_multipointm&nbsp;&nbsp;geometry(MultiPointM,4326)<br/> \
)</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>Index (so far no index on the geometry columns):</div>

<div>================================================<br/>
CREATE INDEX IF NOT EXISTS ais_trajectory_mmsi_daily_mmsi&nbsp;&nbsp;ON \
aisstream.ais_trajectory_mmsi_daily USING btree&nbsp;(mmsi ASC NULLS LAST);</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>Additional indexes didn&#39;t really speed things up so far.<br/>
I&#39;ve checked an GIST index on the &#39;geom_linestring&#39; used in the \
ST_Intersects query as well as the &#39;min_created_on&#39; column. I did an ANALYZE \
on the table afterwards to update the statistics. ;)</div>

<div><br/>
So far I still use the &quot;... WHERE ST_Intersects(geom_linestring, geom_polygon) \
AND ...&quot; with the LineString on my Polygon which is so far fast enough (0.5 \
seconds). Once I run &quot;ST_Dump(ST_Intersection(geom_linestring, geom_polygon)) in \
the SELECT part the query times completely&nbsp;explode (30+ minutes). With \
MultiPoints the ST_DUMP is much faster.<br/> With the suggested MultiPoint approach \
(the newly added column)&nbsp;the query times&nbsp;are&nbsp;fine if I run \
&quot;ST_Intersection(geom_multipointm, geom_polygon) on the already \
&#39;pre-filtered&#39; data.</div>

<div>So, I now have my points inside my polygon still in ~0.5 seconds (if I check for \
a range of&nbsp;~8 days).</div>

<div>&nbsp;</div>

<div><br/>
Here is my current&nbsp;query:<br/>
=====================<br/>
--<br/>
-- Find lines intersecting the given polygon (port/berth area).<br/>
-- From those get the actual points (at least 2 point) that are inside the given \
                polygon (port/berth area).<br/>
-- What we want for now is the timestamp of the first and last point inside the \
                polygon.<br/>
-- ==&gt; This information is lost by calling ST_Intersection(...)<br/>
-- &nbsp; &nbsp; in the subquery (see alias: geom_intersection)!</div>

<div>--</div>

<div>-- The query also dumps some additional details&nbsp;(now many points do we \
                have,..).<br/>
--<br/>
--EXPLAIN ANALYZE<br/>
WITH my_port AS (<br/>
&nbsp; &nbsp; SELECT ST_GeometryFromText(&#39;POLYGON((6.972692012786865 \
50.929290943993465,6.971780061721802 50.930535164787784,6.971426010131836 \
50.93042697299645,6.972337961196899 50.92918951148323,6.972692012786865 \
50.929290943993465))&#39;, 4326) AS geom<br/> )<br/>
,my_traj AS (<br/>
&nbsp; &nbsp; SELECT ais.mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,ais.min_created_on &nbsp; &nbsp; &nbsp; &nbsp;AS \
mintime<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,ais.max_created_on &nbsp; &nbsp; \
&nbsp; &nbsp;AS maxtime<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
,ais.geom_linestring<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
,ais.geom_multipointm<br/> &nbsp; &nbsp; FROM ais_trajectory_mmsi_daily AS ais<br/>
)<br/>
SELECT geom_intersection.mmsi<br/>
&nbsp; &nbsp; &nbsp; ,geom_intersection.created_on_date<br/>
&nbsp; &nbsp; &nbsp; ,ST_NPoints(geom_intersection.port_mpoint_intersect) AS \
point_count<br/> &nbsp; &nbsp; &nbsp; \
,ST_GeometryN(geom_intersection.port_mpoint_intersect, 1) AS first_pointm<br/> &nbsp; \
&nbsp; &nbsp; --<br/> &nbsp; &nbsp; &nbsp; -- The following does not exist any \
more<br/> &nbsp; &nbsp; &nbsp; --<br/>
&nbsp; &nbsp; &nbsp; ,ST_M(ST_GeometryN(geom_intersection.port_mpoint_intersect, 1)) \
AS first_pointm_m_value<br/> &nbsp; &nbsp; &nbsp; --<br/>
&nbsp; &nbsp; &nbsp; \
,ST_GeometryType(ST_GeometryN(geom_intersection.port_mpoint_intersect, 1)) AS \
first_pointm_geom_type<br/> &nbsp; &nbsp; &nbsp; \
,geom_intersection.port_mpoint_intersect<br/> &nbsp; &nbsp; &nbsp; \
,ST_GeometryType(geom_intersection.port_mpoint_intersect) AS geom_type<br/> FROM \
(<br/> &nbsp; &nbsp; SELECT my_traj.mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ,CAST(my_traj.mintime AS date) AS \
created_on_date<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
,ST_Intersection(geom_multipointm, my_port.geom) &nbsp; &nbsp;AS \
port_mpoint_intersect<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
--,ST_3DIntersection(geom_multipointm, my_port.geom) &nbsp;AS port_mpoint_3dintersect \
&nbsp;-- function does not exist error<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
,my_traj.geom_linestring<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
--,(ST_Dump(ST_Intersection(my_traj.geom_linestring, my_port.geom) )).geom &nbsp; \
&nbsp; AS geom_dump &nbsp; &nbsp;-- extremely slow (linestring)<br/> &nbsp; &nbsp; \
&nbsp; &nbsp; &nbsp; --,(ST_Dump(ST_Intersection(my_traj.geom_multipointm, \
my_port.geom) )).geom &nbsp; &nbsp;AS geom_dump &nbsp; &nbsp;-- fast \
(multipoint)<br/> &nbsp; &nbsp; FROM my_port, my_traj<br/>
&nbsp; &nbsp; WHERE ST_Intersects(my_traj.geom_linestring, my_port.geom)<br/>
&nbsp; &nbsp; &nbsp; AND my_traj.mintime &gt;= &#39;2022-10-16&#39;<br/>
&nbsp; &nbsp; &nbsp; AND my_traj.maxtime &lt; &nbsp;&#39;2022-10-24&#39;<br/>
&nbsp; &nbsp; GROUP BY mmsi, created_on_date , my_port.geom, my_traj.geom_multipointm \
,my_traj.geom_linestring<br/> ) AS geom_intersection<br/>
WHERE ST_NPoints(geom_intersection.port_mpoint_intersect) &gt; 1<br/>
GROUP BY mmsi, created_on_date, port_mpoint_intersect<br/>
;</div>

<div>&nbsp;</div>

<div><br/>
1 - Issue - How do I get my timestamp for the points inside my polygon?:<br/>
========================================================================<br/>
I now have the issue that I lost the timestamp stored within my Point (&quot;M&quot; \
value) because&nbsp;ST_Intersection(..) removes this (<a \
href="https://postgis.net/docs/ST_Intersection.html" \
target="_blank">https://postgis.net/docs/ST_Intersection.html</a>).</div>

<div><br/>
The documentation kind of suggests &#39;ST_3DIntersection&#39; (at least the Z is not \
removed...so I assume &quot;M&quot; would be available too after calling this \
function). This function&nbsp;should be available with PostGIS 2.1.0+.<br/> I&#39;m \
running PostgreSQL 13.7&nbsp;with PostGIS 3.2.1&nbsp;here (Ubuntu Linux).<br/> The \
problem is that I get the error &quot;function st_3dintersection(geometry, geometry) \
does not exist&quot;. I don&#39;t get why this is the case.</div>

<div>&nbsp;</div>

<div>So how can I get back the timestamp for my identified points?<br/>
Some kind of LATERAL JOIN on my original &quot;geom_multipointm&quot; column \
data?<br/> Sorry, I&#39;m lost here.</div>

<div>&nbsp;</div>

<div><br/>
2 - How to creating a Multi-LineString instead of a single LineString?<br/>
I assume some kind of window function LAG/LEAD between the points.<br/>
=================================================================<br/>
The other (new)&nbsp;task is break down my single LineString to \
Multi-LineString&nbsp;while aggregating the geom point data.<br/> Currently the data \
is aggreated per day and MMSI. The &#39;Point&#39; is converted to a &#39;PointM&#39; \
(with the created_on timestamp) to be added \
to&nbsp;ST_MakeLine(geom_pointm&nbsp;ORDER BY created_on).</div>

<div>It seems I need some help on how to create this \
&quot;multi-linestring&quot;.<br/> I assume I need to use a window function to \
compare the current with the next and if &gt;= 10 Min this following point is in the \
next linestring (minimum of two points required in each group) + check distance (to \
remove total nonsense).<br/> For starters the &gt;= 10 Min gap to generate \
Multi-LineStrings would be ok. The rest is for additional fine tuning later on. I \
started experimenting with the duration between two points using a window \
function.</div>

<div>&nbsp;</div>

<div><br/>
Many thanks in advance!<br/>
&nbsp;</div>
</div>

<div>
<div>&nbsp;</div>

<div>Juergen</div>

<div>&nbsp;
<div style="margin: 10.0px 5.0px 5.0px 10.0px;padding: 10.0px 0 10.0px \
10.0px;border-left: 2.0px solid rgb(195,217,229);"> <div style="margin: 0 0 10.0px \
0;"><b>Gesendet:</b>&nbsp;Montag, 17. Oktober 2022 um 15:09 Uhr<br/> \
<b>Von:</b>&nbsp;&quot;Rory Meyer&quot; &lt;rory.meyer@VLIZ.be&gt;<br/> \
<b>An:</b>&nbsp;&quot;PostGIS Users Discussion&quot; \
&lt;postgis-users@lists.osgeo.org&gt;<br/> <b>Betreff:</b>&nbsp;Re: [postgis-users] \
Issue with ST_Intersection seems related to lines crossing themself</div>

<div>
<div>
<div class="x_elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">Hi Juergen,</div>

<div class="x_elementToProof" style="font-family: Calibri , Arial , Helvetica , \
sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="x_elementToProof x_ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">I&#39;ve been helping on an open source project for processing AIS \
data in postgis with a focus on scientific applications instead of commercial \
ones:&nbsp;<a href="http://openais.xyz/" id="LPNoLPOWALinkPreview" \
target="_blank">http://openais.xyz/</a>&nbsp;It&#39;s still a little immature and the \
database portion of the project is still closed while I rewrite some of the \
containers.</div>

<div class="x_elementToProof x_ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">&nbsp;</div>

<div class="x_elementToProof x_ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);">Somethings that have helped me:&nbsp;</div>

<div class="x_elementToProof x_ContentPasted0" style="font-family: Calibri , Arial , \
Helvetica , sans-serif;font-size: 12.0pt;color: rgb(0,0,0);background-color: \
rgb(255,255,255);"> <ul>
	<li>Since AIS is a timeseries, geospatial dataset it often helps to have a \
timeseries plugin to work with. The TimeScaleDB HA version contains both PostGIS 3+ \
and TimescaleDB plugins. Timescaledb lets you automatically partition a table based \
on time and to automatically create binned data from live data. You could have a \
first/last position per ship MMSI per hour/day/week.&nbsp;</li>  <li>The GPS on AIS \
is generally pretty accurate but can suffer from poor GPS lock when in ports that \
have high buildings, cranes etc near the harbour. This causes the vessels to \
&quot;jump&quot; around much more than expected. There are also instances of the \
vessels jumping several hundred km&#39;s in either the latitude or longitude \
direction. This might be due to bit-flip errors in the AIS VHF message.&nbsp;</li>  \
<li>I would say that having a line intersect with your port geometry, even if no \
points are within it, is the kind of behaviour that you want. If this is not the \
case, then I would just check for points within the port area instead of the \
trajectory.&nbsp;</li>  <li>For handling a trajectory that is on the edge of the port \
area and causing multiple, close in time, entrance and exit signals it might be worth \
looking into some of the following:  <ul style="list-style-type: circle;">
		<li>Some kind hysteresis function by having a combination of the port boundaries \
and a buffer of the port boundaries.&nbsp;</li>  <li>Filtering the entrance/exit \
signals through time. TimeScaleDB gives aggregate functions like \
&quot;first(&lt;column&gt;, timestamp)&quot; that would select the first item in the \
column by timestamp. First grouping the data by some sensible time window (daily) and \
then selecting the first/last entrance/exit signal per vessel&nbsp;</li>  <li \
class="x_elementToProof">Ignoring entrance/exit signals where the reverse signal \
occurred within X seconds.</li>  <li class="x_elementToProof">You might need more \
complex Trajectory generation code, something that uses window functions to break \
trajectories on obviously bad points (gaps between messages longer than 1 hour, \
sudden jumps of 100km+, points where calculated speed &gt; 2* reported \
speed)&nbsp;</li>  <li class="x_elementToProof">It might be worth dumping the Z-value \
of the trajectory to get the first/last time in the intersection segment.&nbsp;</li>  \
</ul>  </li>
	<li class="x_elementToProof">To make your SQL a little easier to read you can use \
Common Table Expressions:</li> </ul>

<div>&#96;&#96;&#96;</div>

<div class="x_ContentPasted1">WITH my_port AS
<div class="x_ContentPasted1">(SELECT \
ST_GeometryFromText(&#39;POLYGON((7.1084729427821 50.7352828020046,7.1089771980769 \
50.7353778664669,7.10848259899649 50.7365542732199,7.10801482200623 \
50.7364673592064,7.1084729427821 50.7352828020046))&#39;, 4326) AS geom ),</div>

<div>&nbsp;</div>

<div class="x_ContentPasted1">my_traj AS</div>

<div class="x_ContentPasted1">(SELECT</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ais.mmsi,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ais.created_on_date \
as date,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ais.min_created_on \
as mintime,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ais.max_created_on \
as maxtime,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ais.geo_lat_long_line \
as geom --renaming these to just help me out here</div>

<div class="x_ContentPasted1">&nbsp;FROM ais_trajectory_mmsi_daily AS ais</div>

<div class="x_ContentPasted1">)</div>

<div class="x_ContentPasted1">SELECT</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;my_traj.mmsi,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;my_traj.date,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ST_Intersection(my_traj.geom, \
my_port.geom) as port_traj_intersect,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ST_ZMin(ST_Intersection(my_traj.geom, \
my_port.geom)) as segment_start_time,</div>

<div class="x_ContentPasted1">&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;ST_ZMax(ST_Intersection(my_traj.geom, \
my_port.geom)) as segment_end_time</div>

<div class="x_ContentPasted1">FROM my_geom, my_traj</div>

<div class="x_ContentPasted1">WHERE ST_Intersects(my_traj.geom, my_port.geom)</div>

<div class="x_ContentPasted1">AND my_traj.mintime &gt; &#39;2022-10-01&#39;</div>
AND my_traj.maxtime &lt; &#39;2022-10-02&#39;</div>

<div>&nbsp;</div>

<div>&#96;&#96;&#96;</div>

<div>Hope that helps some,</div>

<div>Rory</div>
</div>

<div class="x__Entity x__EType_OWALinkPreview x__EId_OWALinkPreview \
x__EReadonly_1">&nbsp;</div> &nbsp;

<div id="x_appendonsend">&nbsp;</div>

<hr style="display: inline-block;width: 98.0%;"/>
<div id="x_divRplyFwdMsg"><font color="#000000" face="Calibri, sans-serif" \
style="font-size: 11.0pt;"><b>From:</b> postgis-users \
&lt;postgis-users-bounces@lists.osgeo.org&gt; on behalf of Simon SPDBA Greener \
&lt;simon@spdba.com.au&gt;<br/> <b>Sent:</b> 17 October 2022 01:56<br/>
<b>To:</b> postgis-users@lists.osgeo.org &lt;postgis-users@lists.osgeo.org&gt;<br/>
<b>Subject:</b> Re: [postgis-users] Issue with ST_Intersection seems related to lines \
crossing themself</font>

<div>&nbsp;</div>
</div>

<div>
<p>Would using a MultiPoint (or GeometryCollection with Points) for the GPS locations \
rather than a Linestring be possible?</p>

<p>Simon</p>

<div class="x_x_moz-cite-prefix">On 17/10/2022 10:10 am, <a \
class="x_x_moz-txt-link-abbreviated" href="mailto:magog002@web.de" \
onclick="parent.window.location.href=&#39;mailto:magog002@web.de&#39;; return false;" \
target="_blank"> magog002@web.de</a> wrote:</div>

<blockquote>
<div style="font-family: Verdana;font-size: 12.0px;">
<div>Hi,</div>

<div>&nbsp;</div>

<div>I&#39;ve got an issue with an SQL PostGIS&nbsp;query and hope to get a solution \
here.</div>

<div>&nbsp;</div>

<div>First&nbsp;some details.</div>

<div>I&#39;m storing GPS locations of vessels by their MMSI identification \
number&nbsp;(AIS)&nbsp;that are received in 3 Sek to 3 Minutes ranges in a \
table.</div>

<div>As this becomes quite unhandy in a very short time, each day the basic details \
mmsi,&nbsp;received_at (Timestamp), lat, long are converted into a Points with the \
Timestamp integrated and this is then converted into a LineString for each MMSI, per \
day, with the first and last timestamp of the day for which a point exists as direct \
columns.</div>

<div>&nbsp;</div>

<div>Now I want to know when some vessel is/was inside a specific area (harbor, \
berth) so I have a Polygon which the LineString has to intersect.</div>

<div>So far so good.</div>

<div>This works but can create false positives (two points outside the polygon are \
connected and this created line goes through the Polygon but there is no actual Point \
of that line in the Polygon)...so I need to re-check again by pulling out the points \
from my LineString.</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>Here the basic search query that basically works:</div>

<div>================================</div>

<div>--<br/>
-- Return all vessels (MMSI) found in the selected timeframe where the<br/>
-- LineString Intersects the Polygon given as parameter to search.<br/>
--<br/>
SELECT ais_trajectory_mmsi_daily.mmsi<br/>
&nbsp; &nbsp; &nbsp; ,ais_trajectory_mmsi_daily.min_created_on<br/>
&nbsp; &nbsp; &nbsp; ,ais_trajectory_mmsi_daily.max_created_on<br/>
&nbsp; &nbsp; &nbsp; ,ST_SetSRID(ais_trajectory_mmsi_daily.geo_lat_long_line, 4326) \
AS geo_lat_long_line<br/> FROM ais_trajectory_mmsi_daily<br/>
WHERE ST_Intersects(ST_SetSRID(ais_trajectory_mmsi_daily.geo_lat_long_line, \
4326),<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
ST_GeometryFromText(&#39;POLYGON((7.1084729427821 50.7352828020046,7.1089771980769 \
50.7353778664669,7.10848259899649 50.7365542732199,7.10801482200623 \
50.7364673592064,7.1084729427821 50.7352828020046))&#39;, 4326) )<br/> &nbsp; AND \
min_created_on &gt;= CAST(&#39;2022-10-01&#39; AS TIMESTAMPTZ)<br/> &nbsp; AND \
max_created_on &lt; &nbsp;CAST(&#39;2022-10-15&#39; AS TIMESTAMPTZ)<br/> ;</div>

<div>&nbsp;</div>

<div>&nbsp;</div>

<div>Here is now the query I use with ST_Intersection and where it starts to go \
wrong.</div>

<div>When a vessel stays a the same place the GPS coordinates are the same + \
drift.</div>

<div>&nbsp;</div>

<div>As those points are all added to the LineString the line sections are running \
over themself and it looks like a &quot;woolen noodle&quot;.</div>

<div>This seems to cause issues with ST_Intersection as I use it because instead of \
getting only the entering and exiting details as normally 1 row&nbsp;where the \
LineString hits my Polygon with times I now get hundreds of rows as a result and the \
visual result shown on the map is weird and sometimes wrong (the line goes out of the \
polygon in a way the original LineString did never exist).</div>

<div>&nbsp;</div>

<div>Here is the query in question (basically the part above) with ST_Dump and \
ST_Intersection in the SELECT part + plus an outer query to get more details on what \
was the first and last time there was an intersection.</div>

<div>&nbsp;</div>

<div>--<br/>
-- Outer query with union to visualize the Polygon and the&nbsp;<br/>
-- data received from the LineString that Intersects<br/>
-- with the polygon via e.g. pgAdmin.<br/>
--<br/>
SELECT NULL AS mmsi<br/>
&nbsp; &nbsp; &nbsp; ,NULL AS created_on_date<br/>
&nbsp; &nbsp; &nbsp; ,NULL AS min_created_on<br/>
&nbsp; &nbsp; &nbsp; ,NULL AS max_created_on<br/>
&nbsp; &nbsp; &nbsp; ,ST_GeometryFromText(&#39;POLYGON((7.1084729427821 \
50.7352828020046,7.1089771980769 50.7353778664669,7.10848259899649 \
50.7365542732199,7.10801482200623 50.7364673592064,7.1084729427821 \
50.7352828020046))&#39;, 4326) AS geom<br/> &nbsp; &nbsp; &nbsp; ,NULL AS \
geom_start<br/> &nbsp; &nbsp; &nbsp; ,NULL AS geom_end<br/>
UNION<br/>
-- The actual data of interest<br/>
SELECT intersection.mmsi<br/>
&nbsp; &nbsp; &nbsp; ,intersection.created_on_date<br/>
&nbsp; &nbsp; &nbsp; ,intersection.min_created_on<br/>
&nbsp; &nbsp; &nbsp; ,intersection.max_created_on<br/>
&nbsp; &nbsp; &nbsp; ,intersection.geom<br/>
&nbsp; &nbsp; &nbsp; ,MIN(DATE_TRUNC(&#39;second&#39;, \
TO_TIMESTAMP(ST_InterpolatePoint(intersection.geo_lat_long_line, \
ST_StartPoint(intersection.geom)) )::timestamptz )) AS geom_start<br/> &nbsp; &nbsp; \
&nbsp; ,MAX(DATE_TRUNC(&#39;second&#39;, \
TO_TIMESTAMP(ST_InterpolatePoint(intersection.geo_lat_long_line, \
ST_EndPoint(intersection.geom)) &nbsp; )::timestamptz )) AS geom_end<br/> FROM (<br/>
&nbsp; &nbsp;SELECT ais_trajectory_mmsi_daily.mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;,CAST(ais_trajectory_mmsi_daily.min_created_on AS \
date) AS created_on_date&nbsp;<br/> &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;,ais_trajectory_mmsi_daily.min_created_on<br/> &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;,ais_trajectory_mmsi_daily.max_created_on<br/> &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;,ST_SetSRID(ais_trajectory_mmsi_daily.geo_lat_long_line, 4326) AS \
geo_lat_long_line</div>

<div>&nbsp; &nbsp; &nbsp; &nbsp; --</div>

<div>&nbsp; &nbsp; &nbsp; &nbsp; -- Here seems to be the&nbsp;problem...</div>

<div>&nbsp; &nbsp; &nbsp; &nbsp; --<br/>
&nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;,(ST_Dump(ST_Intersection(ST_GeometryFromText(&#39;POLYGON((7.1084729427821 \
50.7352828020046,7.1089771980769 50.7353778664669,7.10848259899649 \
50.7365542732199,7.10801482200623 50.7364673592064,7.1084729427821 \
50.7352828020046))&#39;, 4326)<br/> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
,ST_SetSRID(ais_trajectory_mmsi_daily.geo_lat_long_line, 4326)<br/> &nbsp; &nbsp; \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )<br/> &nbsp; \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )).geom AS geom<br/> &nbsp; \
&nbsp;FROM ais_trajectory_mmsi_daily<br/> &nbsp; &nbsp;WHERE \
ST_Intersects(ST_SetSRID(ais_trajectory_mmsi_daily.geo_lat_long_line, 4326),<br/> \
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \
&nbsp;ST_GeometryFromText(&#39;POLYGON((7.1084729427821 \
50.7352828020046,7.1089771980769 50.7353778664669,7.10848259899649 \
50.7365542732199,7.10801482200623 50.7364673592064,7.1084729427821 \
50.7352828020046))&#39;, 4326) )<br/> &nbsp; &nbsp; &nbsp; &nbsp; AND min_created_on \
&gt;= CAST(&#39;2022-10-01&#39; AS TIMESTAMPTZ)<br/> &nbsp; &nbsp; &nbsp; &nbsp; AND \
max_created_on &lt; &nbsp;CAST(&#39;2022-10-02&#39; AS TIMESTAMPTZ)<br/> ) AS \
intersection<br/> GROUP BY intersection.mmsi<br/>
&nbsp; &nbsp; &nbsp; &nbsp; ,intersection.created_on_date<br/>
&nbsp; &nbsp; &nbsp; &nbsp; ,intersection.min_created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; ,intersection.max_created_on<br/>
&nbsp; &nbsp; &nbsp; &nbsp; ,intersection.geom<br/>
ORDER BY created_on_date, mmsi, geom_start<br/>
;</div>

<div>&nbsp;</div>

<div>If needed I can try to create some sample with data to put \
on&nbsp;sqlfiddle.</div>

<div>&nbsp;</div>

<div>On Stackoverflow I found this which seems to be related to the same issue:</div>

<div><a class="x_x_moz-txt-link-freetext" \
href="https://stackoverflow.com/questions/62090829/why-st-intersection-from-postgis-returns-multilinestring-for-self-intersecting-l" \
target="_blank">https://stackoverflow.com/questions/62090829/why-st-intersection-from-postgis-returns-multilinestring-for-self-intersecting-l</a></div>


<div>&nbsp;</div>

<div>Many thanks in advance for hints to solve the problem.</div>

<div>&nbsp;</div>

<div>Juergen</div>

<div>&nbsp;</div>
</div>
&nbsp;

<fieldset class="x_x_moz-mime-attachment-header">&nbsp;</fieldset>

<pre class="x_x_moz-quote-pre">_______________________________________________
postgis-users mailing list
<a class="x_x_moz-txt-link-abbreviated" href="mailto:postgis-users@lists.osgeo.org" \
onclick="parent.window.location.href=&#39;mailto:postgis-users@lists.osgeo.org&#39;; \
return false;" target="_blank">postgis-users@lists.osgeo.org</a> <a \
class="x_x_moz-txt-link-freetext" \
href="https://lists.osgeo.org/mailman/listinfo/postgis-users" \
target="_blank">https://lists.osgeo.org/mailman/listinfo/postgis-users</a> </pre>
</blockquote>

<pre class="x_x_moz-signature">--
Simon Greener
39 Cliff View Drive, Allens Rivulet, 7150, Tasmania, Australia
(m) +61 418 396 391
(w) <a class="x_x_moz-txt-link-abbreviated" href="http://www.spdba.com.au" \
target="_blank">www.spdba.com.au</a> (m) <a class="x_x_moz-txt-link-abbreviated" \
href="mailto:simon@spdba.com.au" \
onclick="parent.window.location.href=&#39;mailto:simon@spdba.com.au&#39;; return \
false;" target="_blank">simon@spdba.com.au</a></pre> </div>
_______________________________________________ postgis-users mailing list \
postgis-users@lists.osgeo.org <a \
href="https://lists.osgeo.org/mailman/listinfo/postgis-users" target="_blank"> \
https://lists.osgeo.org/mailman/listinfo/postgis-users</a></div> </div>
</div>
</div>
</div>
</div>
</div>
_______________________________________________ postgis-users mailing list \
postgis-users@lists.osgeo.org <a \
href="https://lists.osgeo.org/mailman/listinfo/postgis-users" \
target="_blank">https://lists.osgeo.org/mailman/listinfo/postgis-users</a></div> \
</div> </div>
</div>
</div></div></body></html>



_______________________________________________
postgis-users mailing list
postgis-users@lists.osgeo.org
https://lists.osgeo.org/mailman/listinfo/postgis-users


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic