Files
secondo/bin/Scripts/importNariDynamic.sec
2026-01-23 17:03:45 +08:00

442 lines
13 KiB
Plaintext

# Script for importing AIS data (maritime trajectories from the Brest area in France)
#
# Required: folder SECONDO with data
# file Areas
# SymbolicTrajectoryAlgebra must be activated
#
# Preparations: place file Areas into secondo/bin directory
# enter location of SECONDO folder in the next line
#
# running times shown from Apple MacBook with SSD disk
# define the directory containing the data
let dir = '/home/ralf/Schreibtisch/Maritime/SECONDO/'
# import raw csv data
# the constant relation used in this command
# corresponds to the schema in the csv file
let Nari =
[const rel(tuple([
Mmsi: int,
Status: int,
Turn: real,
Speed: real,
Course: real,
Heading: int,
Lon: real,
Lat: real,
Time: longint
]))
value ()]
csvimport[dir + 'AIS Data/nari_dynamic.csv', 1, ""] consume
# about 6 minutes
# result size 19035630
# The ais data use as a time stamp an integer value
# meaning seconds to the start time used in Unix.
# For using in secondo, we have to convert this format
# into a datetime
let UnixStart = [const instant value "1970-1-1"]
# We convert the raw trajectory data into moving points.
# Here, we just use the positional data.
# For each Mmsi, a single trip is created from the
# sequence of (time, position) pairs. If there is
# a gap of 380 seconds between two reported positions,
# the trip contains also a gap.
let NariD = Nari feed
sortby[Mmsi, Time]
projectextend[Mmsi, Status, Turn, Speed, Course, Heading
; Pos: makepoint(.Lon, .Lat),
Time: UnixStart + create_duration(.Time, "s")]
groupby[Mmsi; Trip: group feed
approximate[Time, Pos, [const duration value (0 380000)] ]
]
consume
# about 2 - 3 minutes
# result size 5055
# Define the position of the antenna that is connected
# the the receiver recording the ais data.
let Antenna = [const point value (-4.57091 48.35923)]
# divide into trips with separation 2 hours
let NariTrips = NariD feed
projectextendstream[Mmsi
; Trip: .Trip sim_trips[ [const duration value (0 7200000)]] ]
consume
# 1:25 min
# result size 94281
# Here, also trips are created from an object trajectory.
# A counter for the trip number is added
# for each object.
let NariTrips2 = NariD feed
loopsel[fun(t: TUPLE)
attr(t, Trip) sim_trips[ [const duration value (0 7200000)]]
namedtransformstream[Trip]
addcounter[TripNo, 1]
extend[Mmsi: attr(t, Mmsi)]
project[Mmsi, TripNo, Trip]
]
consume
# 1:28 min
# result size 94281
# We simplify each trip using a variant of Douglas Peucker
# using a maximum derivation of 30 meters to the original
# trajectory.
# For checking the 'compression' rate, we add the number of
# units of the original trajectory and the simplified
# trajectory.
let NariTrips3 = NariTrips2 feed extend[Old: no_components(.Trip)]
projectextend[Mmsi, TripNo, Old
; Trip: simplify(.Trip, 30.0, [const geoid value WGS1984]) ]
extend[New: no_components(.Trip)]
consume
# 2:00 min
# Convert the simplified trips into a unit representation.
let NariUnits = NariTrips3 feed
projectextendstream[Mmsi; UTrip: units(.Trip)]
addcounter[UnitId, 1]
consume
# Old from NariD
# 4:01 min
# 16907598
# new
# 8.6 seconds
# result size 615453
# create a btree for easy finsing all units belonging
# to a certain mmsi
let NariUnits_Mmsi_btree = NariUnits createbtree[Mmsi]
# old
# 1.06 min
# new
# 3.9 seconds
# Import some non-moving data from a csv file.
let NariS =
[const rel(tuple([
SourceMMSI: int,
IMO: int,
CallSign: text,
ShipName: text,
ShipType: text,
ToBow: int,
ToStern: int,
ToStarBoard: int,
ToPort: int,
ETA: text,
Draught: real,
Destination: text,
MotherShipMMSI: int,
Time: longint
]))
value ()]
csvimport[dir + 'AIS Data/nari_static.csv', 1, ""] consume
# 42 seconds
# result size 1078617
# How many ships?
# Some mmsi are present multiple times in the csv file, e.g.,
# with a different destination, thus, we have to remove
# duplicates.
query NariS feedproject[SourceMMSI] sort rdup count
# 11 seconds
# result 4842
# Extract the pure static ship data.
let Ships = NariS feedproject[SourceMMSI, IMO, CallSign, ShipName, ShipType] sort rdup consume
# Create an index for the ships.
let Ships_SourceMMSI_btree = Ships createbtree[SourceMMSI]
# compute near collision situations
# A little test for building bounding boxes extended by 500 meters
query NariUnits feed extend[BBox: bbox(.UTrip), Box: bbox2d(.UTrip)]
extend[Box2: .Box extendGeo[500]]
extend[Box3: box3d(.Box2, deftime(.UTrip))]
head[3] consume
# For a near collision, the bounding boxes must not overlap.
# To exploit an r-tree for this purpose, we extend one of the
# bouding boxes by 500 meters.
query
NariUnits feed extend[Box: bbox(.UTrip)] {u1}
NariUnits feed extend[Box:
box3d(bbox2d(.UTrip) extendGeo[500], deftime(.UTrip))] {u2}
itSpatialJoin[Box_u1, Box_u2]
count
# 1:41 min
# result 3145061
# Matching units must come from different ships.
query
NariUnits feed extend[Box: bbox(.UTrip)] {u1}
NariUnits feed extend[Box:
box3d(bbox2d(.UTrip) extendGeo[500], deftime(.UTrip))] {u2}
itSpatialJoin[Box_u1, Box_u2]
filter[.Mmsi_u1 < .Mmsi_u2]
count
# 1:43 min
# result 737637
# Instead of only counting the candidates, we collect them
# into a relation here.
let Nearby =
NariUnits feed extend[Box: bbox(.UTrip)] {u1}
NariUnits feed extend[Box:
box3d(bbox2d(.UTrip) extendGeo[500], deftime(.UTrip))] {u2}
itSpatialJoin[Box_u1, Box_u2]
filter[.Mmsi_u1 < .Mmsi_u2]
consume
# 1:58 min
# Do the same thing as before.
# Here, a variant of the extendGeo operator is used instead of
# building the 3D-box manually.
let Nearby2 =
NariUnits feed extend[Box: bbox(.UTrip)] {u1}
NariUnits feed extend[Box: bbox(.UTrip) extendGeo[500]] {u2}
itSpatialJoin[Box_u1, Box_u2]
filter[.Mmsi_u1 < .Mmsi_u2]
consume
# Define the standard geod used in the data as an object.
let wgs1984 = create_geoid("WGS1984")
# Look at pairs of matching units. Select one by number.
# Reduce units to the overlap time.
# For testing, just take the one result.
query Nearby feed
extend[Speed1: val(final(speed(.UTrip_u1, wgs1984))) * 3.6 / 1.852,
Speed2: val(final(speed(.UTrip_u2, wgs1984))) * 3.6 / 1.852,
CommonTime: intersection(deftime(.UTrip_u1), deftime(.UTrip_u2))]
projectextend[
Speed1, Speed2; Mmsi1: .Mmsi_u1, Mmsi2: .Mmsi_u2,
UTrip1: (.UTrip_u1 atperiods .CommonTime) transformstream extract[Elem],
UTrip2: (.UTrip_u2 atperiods .CommonTime) transformstream extract[Elem]]
addcounter[No, 1]
filter[.No = 2]
head[1]
consume
# additionally consider direction and projection to space
query Nearby feed
extend[
Speed1: val(final(speed(.UTrip_u1, wgs1984))) * 3.6 / 1.852,
Speed2: val(final(speed(.UTrip_u2, wgs1984))) * 3.6 / 1.852,
CommonTime: intersection(deftime(.UTrip_u1), deftime(.UTrip_u2))]
projectextend[Speed1, Speed2; Mmsi1: .Mmsi_u1, Mmsi2: .Mmsi_u2,
UTrip1: (.UTrip_u1 atperiods .CommonTime) transformstream extract[Elem],
UTrip2: (.UTrip_u2 atperiods .CommonTime) transformstream extract[Elem]]
extend[Heading1: direction2heading(direction(val(initial(.UTrip1)),
val(final(.UTrip1))))]
extend[Heading2: direction2heading(direction(val(initial(.UTrip2)),
val(final(.UTrip2))))]
extend[Traj1: trajectory(.UTrip1)]
extend[Traj2: trajectory(.UTrip2)]
addcounter[No, 1]
filter[.No = 1]
head[1]
consume
# Importing environmental data
# adapt local directory
let AnchorageAreas =
dbimport2(dir+'/Anchorage Areas/Anchorage Areas.dbf')
shpimport2(dir + 'Anchorage Areas/Anchorage Areas.shp') namedtransformstream[Area]
obojoin
consume
let EuropeCoastline =
dbimport2(dir+'/European Coastline/Europe Coastline.dbf')
shpimport2(dir + '/European Coastline/Europe Coastline.shp' ) namedtransformstream[Coast]
obojoin
consume
let WorldSeas =
dbimport2(dir+'/IHO World Seas/World_Seas.dbf')
shpimport2(dir + '/IHO World Seas/World_Seas.shp' ) namedtransformstream[Sea]
obojoin
consume
let ports =
dbimport2(dir+'/Ports of Brittany/port.dbf')
shpimport2(dir + '/Ports of Brittany/port.shp' ) namedtransformstream[Port]
obojoin
consume
let tssQuessant =
dbimport2(dir+'/TSS Ouessant/TSS Ouessant.dbf')
shpimport2(dir + '/TSS Ouessant/TSS Ouessant.shp' ) namedtransformstream[Quessant]
obojoin
consume
# define another directory containing source data
let dir2 = dir + 'AIS Data/Status, Codes and Types/'
# Import some csv files.
# First define the schema used in the file as an empty relation
# and then actually import the data.
let MmsiCountryCodesT = [ const rel(tuple([Code : int, Country : text])) value ()]
let MmsiCountryCodes = MmsiCountryCodesT csvimport[dir2+'MMSI Country Codes.csv',1,""] consume
let NaviStatusT = [ const rel(tuple([Code : int, Status : text])) value ()]
let NaviStatus = NaviStatusT csvimport[dir2+'Navigational Status.csv',1,""] consume
let shipTypeT = [const rel(tuple([ID_shipType : int, Shiptype_min : int, ShipType_max : int,
Type_Name : text, Ais_type_summary : text]))
value ()]
let shipType = shipTypeT csvimport[dir2+'Ship Types List.csv',1,""] consume
let shipTypeDetailT = [const rel(tuple([Id_DetailedType : int, Detailed_Type : text,
Id_ShipType : int]))
value () ]
let shipTypeDetail = shipTypeDetailT csvimport[dir2+'Ship Types Detailled List.csv',1,""] consume
# Devide the coast line of Europe into single segments
let EuropeCoast = EuropeCoastline feed projectextendstream[; Seg: segments(.Coast)] consume
# 2:31 min
# result size 2847446
# Constructing a table of interesting areas
# constructed manually
#
# The following only works on the machine where these areas have been drawn manually.
# let Areas =
# Transrade feed namedtransformstream[Area] extend[Label: [const label value "Transrade"]]
# NavalAcademy feed namedtransformstream[Area] extend[Label: [const label value "NavalAcademy"]] concat
# Goulet feed namedtransformstream[Area] extend[Label: [const label value "Goulet"]] concat
# Camaret feed namedtransformstream[Area] extend[Label: [const label value "Camaret"]] concat
# BrestMilitary feed namedtransformstream[Area] extend[Label: [const label value "BrestMilitary"]] concat
# BrestSailing1 feed namedtransformstream[Area] extend[Label: [const label value "BrestSailing1"]] concat
# BrestSailing2 feed namedtransformstream[Area] extend[Label: [const label value "BrestSailing2"]] concat
# BrestCommercial feed namedtransformstream[Area] extend[Label: [const label value "BrestCommercial"]] concat
# Douarnenez feed namedtransformstream[Area] extend[Label: [const label value "Douarnenez"]] concat
# Morgat feed namedtransformstream[Area] extend[Label: [const label value "Morgat"]] concat
# Conquet feed namedtransformstream[Area] extend[Label: [const label value "Conquet"]] concat
# Out feed namedtransformstream[Area] extend[Label: [const label value "Out"]] concat
# consume
# save Areas to Areas
# on other machines, get the file Areas and put it into the bin directory. Then
restore Areas from Areas
# Extract a single object from the relation of areas.
let Transrade = Areas feed filter[.Label = "Transrade"] extract[Area]
# find all trips passing the Transrade Area
let R1 = NariTrips3 feed extend[Box: bbox2d(.Trip)]
Areas feed extend[Box2: bbox(.Area)]
itSpatialJoin[Box, Box2]
filter[.Trip passes .Area]
consume
# Collect the units of the trips that intersect an area of the Areas relation.
let R2 = R1 feed extendstream[Piece: components(.Trip at .Area)] consume
# Append the currently passed Area as a moving label.
let R3 = R2 feed extend[Time0: inst(initial(.Piece))]
extend[Unit: the_unit(.Label, getIntervals(deftime(.Piece)) transformstream extract[Elem])]
sortby[Mmsi, TripNo, Time0]
groupby[Mmsi, TripNo; Symbolic: group feed makemvalue[Unit]]
consume
# Name: matches
# Signature: {mlabel(s)|mplace(s)} x {pattern|text} -> bool
# Syntax: M matches P
# Meaning: Checks whether the trajectory M matches the pattern
# P.
# Example: query mlabel1 matches '(_ "Eving") * (_ "Innenstadt-Ost") +'
# R3 contains only the parts of a trip that are inside an area.
# Here, append the whole trip.
let NariTrips4 = NariTrips3 feed {n}
R3 feed
itHashJoin[Mmsi_n, Mmsi]
filter[.TripNo_n = .TripNo]
consume
# Here are some examples of pattern queries:
# find trips involving a subtrip from Transrade to NavalAcademy
# query NariTrips4 feed filter[.Symbolic matches '* (_ "Transrade") * (_ "NavalAcademy") *'] extend[Traj: trajectory3(.Trip_n)] consume
# find trips starting in Morgat
# query NariTrips4 feed filter[.Symbolic matches '(_ "Morgat") * '] extend[Traj: trajectory3(.Trip_n)] consume
# find trips only in Douarnenez
# query NariTrips4 feed filter[.Symbolic matches '(_ "Douarnenez")'] extend[Traj: trajectory3(.Trip_n)] consume
# find trips only in Douarnenez at night
# query NariTrips4 feed filter[.Symbolic matches '(night "Douarnenez")'] extend[Traj: trajectory3(.Trip_n)] consume