Calculate the trajectories of each ship based on an AIS dataset - postgresql

I have a table of kinematic data (position reports from vessels). The table has the following rows:
row name: type: Description:
timestamp double precision timestamp in UNIX epochs (seconds from 1/1/1970)
type integer AIS message type
mmsi integer MMSI identifier for vessel
status integer Navigational status
lon double precision Longitude (georeference: WGS 1984)
lat double precision Latitude (georeference: WGS 1984)
heading integer True heading in degrees (0-359), relative to true north
turn double precision Rate of turn, right or left, 0 to 720 degrees per minute
speed double precision Speed over ground in knots (allowed values: 0-102.2 knots)
course double precision Course over ground (allowed values: 0-359.9 degrees).
After I imported the data, I added one more row of type geometry formed using the lon, lat and timestamp rows:
SELECT AddGeometryColumn ('public', 'gis_may', 'geom_time', 4326, 'POINTZ', 3);
UPDATE public.gis_may SET geom_time = ST_Transform(ST_SetSRID(ST_MakePoint(lon, lat, gis_may.timestamp ), 4326),4326);
I have to calculate the trajectories of every ship, based on this data. I tried using the code below:
CREATE TABLE ship_trajectories AS SELECT st_makeline(st_setsrid(st_makepoint(lon::REAL, lat::REAL, gis_may.timestamp), 4326)) as traj
FROM gis_may
GROUP BY mmsi;
…but the problem is, this code returns only one linestring/trajectory based only on the coordinates of each ship and does not take into account the times the ship stopped moving.
I think the solution to this is using the row: status (I searched and found what navigational status is: https://help.marinetraffic.com/hc/en-us/articles/203990998-What-is-the-significance-of-the-AIS-Navigational-Status-Values-). When status is 0 it means the ship is moving and when it is 1 it means it is anchored so it no longer forms a trajectory.

I have worked on a project which solved the same problem by first looking at all AIS-points to determine trajectory breaking-points. If you think that the AIS-variable status is enough for your purposes, then good for you. You could check each instance of status changing to 0. Be aware that AIS-data has other status values too. See this.
What if, within each mmsi, you would like to mark the row when a ship goes from status 1 to status 0. This would create the points of delimitation for your trajectories to begin with. Look at this after first adding your longitude-latitude coordinations as a point geometry called gis_point:
-- Make a point geometry and work with those.
SELECT AddGeometryColumn('public', 'gis_may', 'gis_point', 4326, 'POINT', 2);
UPDATE gis_may SET gis_point = ST_Transform(ST_SetSRID(
ST_MakePoint(longitude, latitude),
4326), 4326);
-- Look ahead to keep stuff within the same mmsi. Create a switch for when the status changes:
CREATE TABLE lookahead AS
SELECT aislag.mmsi, aislag.timestamp1, aislag.gis_point, EXTRACT(EPOCH FROM (aislag.ts2 - aislag.ts1)) AS timediff, aislag.g,
CASE WHEN aislag.status = '1' AND aislag.newstatus ='0' THEN 'Start' ELSE '.' END AS newtrajectory
FROM
(SELECT mmsi, timestamp as timestamp1, status, gis_point,
LEAD(mini_c.timestamp) OVER (ORDER BY mmsi, timestamp) AS timestamp2,
LEAD(mini_c.mmsi) OVER (ORDER BY mmsi, timestamp) AS mmsi2,
mini_c.status AS status,
LEAD(mini_c.status) OVER (ORDER BY mmsi, timestamp) AS newstatus
FROM mini_c) AS aislag
WHERE aislag.mmsi = aislag.mmsi2
I've added a lagged time-variable too. Maybe you would like to enlarge the status-switcher to include observations with obsene time-differences to next-following AIS-point for that same ship. Now that you have your delimiters, you can create a new id for rows in lookahead which follows the mmsi, ts-sorting. Then make a new table trajectories with pairs of those ids as id1 and id2 from a lagged selection on lookahead WHERE newstatus = 'Start' so that you get a row for each distance between switches of status. That table basically has trajectories as observations. Joining on the lookahead you can make your line-geometries per trijectory with ST_Makeline(gis_point) using WHERE newid BETWEEN trajectories.is1 AND trajectories.id2 and GROUP BY mmsi.
Sorry for not writing out all the code.
A more advanced approach is to use the actual AIS-data to look at rolling average speeds and distances over time for each mmsi to determine where the ship appears to be standing still for longer periods. The logic above to distinguish delimiters for trajectories would than apply in the same way, only that the AIS-variable switch of status is exchanged for a calculation of behavioral patterns of ships.
Best of luck

Related

Indexing issue in postgres

It is impossible to speed up the database due to indexing.
I create a table:
CREATE TABLE IF NOT EXISTS coordinate( Id serial primary key,
Lat DECIMAL(9,6),
Lon DECIMAL(9,6));
After that I add indexing:
CREATE INDEX indeLat ON coordinate(Lat);
CREATE INDEX indeLon ON coordinate(Lon);
Then the table is filled in:
INSERT INTO coordinate (Lat, Lon) VALUES(48.685444, 44.474254);
Fill in 100k random coordinates.
Now I need to return all coordinates that are included in a radius of N km from a given coordinate.
SELECT id, Lat, Lon
FROM coordinate
WHERE acos(sin(radians(48.704578))*sin(radians(Lat)) + cos(radians(48.704578))*cos(radians(Lat))*cos(radians(Lon)-radians(44.507112))) * 6371 < 50;
The test execution time is approximately 0.2 seconds, and if you do not do CREATE INDEX, the time does not change. I suspect that there is an error in the request, maybe you need to rebuild it somehow?
I'm sorry for my english
An index can only be used if the indexed expression is exactly what you have on the non-constant side of the operator. That is obviously not the case here.
For operations like this, you need to use the PostGIS extension. Then you can define a table like:
CREATE TABLE coordinate (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
p geography NOT NULL
);
and query like this:
SELECT id, p
FROM coordinate
WHERE ST_DWithin(p, 'POINT(48.704578 44.507112)'::geography, 50);
This index would speed up the query:
CREATE INDEX ON coordinate USING gist (p);

Calculating sum with no direct join column

I have a table (ShipJourneys) where I need to calculate the Total Fuel Consumed which is a float value. See the image below.
This value is obtained by summing all the individual consumers of fuel for a given vessel over the timeframe specified. This data is contained in a second table.
Boxed in area in red shows there were 5 fuel consumers (specified by the FK_RmaDataSumsystemConfigID) and that 3 of the consumers had burned 0 units of fuel and 2 had each burned 29.
To calculate the totalFuelConsumed for that range of time frames, for a given vessel (stipulated by the FK_RmaID), the following query could be used
Select sum(FuelCalc)
from FuelCalc
where Timestamp >= '2019-07-24 00:00:00'
and Timestamp <= '2019-07-24 00:02:00'
and FK_RmaID = 660
Using something like the query below does not work, resulting in bogus values
UPDATE ShipJourneys
SET TotalFuelConsumed =
(Select sum(FuelCalc) from FuelCalc as f
WHERE f.timestamp >= StartTimeUTC
and f.timestamp <= EndTimeUTC
and f.FK_RmaID = FK_RmaID)
Any suggestions on how I could join them
You could try something like that:
UPDATE myTable // Put the table correct name here
SET TotalFuelConsumed =
Select sum(FuelUsed) from FuelTimeTbl as fuelTbl
WHERE fuelTbl.timestamp >= '2019-10-21 22:13:55.000'
and fuelTbl.imestamp <= '2019-11-27 17:10:58.000'
and fuelTbl.FK_RmaID = myTable.RmaID // Put the correct attribute name

Create new row when condition is not right in postgis

I have executed this query:
SELECT
id, user_id, device_id, date(creation_date_time) as day, shape
from olocations
where user_id = 'd0edfc59-9923-44c3-9c34-ef5aad3cb810'
and device_id = '89984320001811791540'
group by id, creation_date_time
ORDER BY creation_date_time ASC
And it is return this:
All data is ordered by time and ready to distance comparison.
It is possible to check distance between points (point1 with point2 - point2 with point3 and so on) if it was for example more than 10 meter, make new row and points that have correct condition inserted as a group into one row else?
I know that st_dwithin command can check distance but how can i change row when distances between geo points more than 10?

Distance between sequential points postgres

My table is as follows
1;"2015-10-02";"POINT(lat,lon) as geometry"
2;"2015-10-03";"POINT(lat,lon) as geometry"
3;"2015-10-04";"POINT(lat,lon) as geometry"
How can I find the distance between two sequential points?
So I'd have id=1 and id=2 distance between them = 99m (distances would be found between [1,2],[2,3],[3,4] and so on
then if distance < 100m aggregate them
I have not go very far with it
This gives me the distance but I don't know how to get the next row's geometry
SELECT st_distance_sphere(t.latlon,next.latlon) from test as t where id=1
Then I tried to read the distance as a additional column but could figure out a correct query
UPDATE test SET dist=ST_Distance(test.latlon, next.geom)
FROM (SELECT latlon FROM test WHERE id = test.id + 1) into b;
1;"2015-10-02";"POINT(lat,lon) as geometry";distance between 1 and 2
2;"2015-10-03";"POINT(lat,lon) as geometry";distance between 2 and 3
3;"2015-10-04";"POINT(lat,lon) as geometry";distance between 3 and 4
To take the distance between current point and next point you can use a window function lead like this:
select
test.*,
st_distance(latlon, lead(latlon, 1, latlon) over(order by id)) as distance
from test;

Unit of return value of ST_Distance

I need to calculate the distance between
all buildings and
all hospitals
in a map imported from OSM.
I use following query:
SELECT building_id, hospital_id, ST_Distance(building_centroid, hospital_location)
FROM
(
select planet_osm_polygon.osm_id building_id, ST_Centroid(planet_osm_polygon.way) building_centroid
from planet_osm_polygon
where building = 'yes'
) buildings,
(
select planet_osm_point.osm_id hospital_id, planet_osm_point.way hospital_location
from planet_osm_point
where amenity = 'hospital') hospitals
I get strange results - the distance is always smaller than 1.
How can I get the to know the unit, in which these values are reported?
Update 1: Sample result:
Update 2: This query seems to work
SELECT building_id, hospital_id, ST_Distance_sphere(building_centroid, hospital_location) distance
FROM
(
select planet_osm_polygon.osm_id building_id, ST_Centroid(planet_osm_polygon.way) building_centroid
from planet_osm_polygon
where building = 'yes'
) buildings,
(
select planet_osm_point.osm_id hospital_id, planet_osm_point.way hospital_location
from planet_osm_point
where amenity = 'hospital') hospitals
ORDER BY distance
The general rule for units is that the output length units are the same as the input length units.
The OSM way geometry data has length units of degrees of latitude and longitude (SRID=4326). Therefore, the output units from ST_Distance will also have lenth units of degrees, which are not really useful.
There are several things you can do:
Use ST_Distance_Sphere for fast/approximate distances in metres
Use ST_Distance_Spheroid for accurace distances in metres
Convert the lat/long geometry data types to geography, which automagically makes ST_Distance and other functions to use linear units of metres