In a previous article, I introduced AIS Tools, as well as how they can be utilized to collect data from various sources. This article is about visualization on a interactive map. The term AIS-PLTR was derived from AIS chart plotter. The presentation of AIS information is commonly referred to as chart plotting. In order to be able to display geographic information on a interactive map, a backend is required which can provide the data via REST and updates in real time using a web socket. To solve this task, two projects where developed. AIS-PLTR for the front end and AIS-PLTR-SRV for the backend.

Visualize AIS Data with LeafletJS and MaterialUI Web

AIS-PLTR is intended to be the simplest possible AIS chart plotter with which ship positions can be displayed. The development was done with TypeScript and LeafletJS as Map API and MaterialUI Web as layout library. No other front end frameworks were used. Click here to open the AIS PLTR Application

Installation

The installation is done by clone the project repository and installing the dependencies.

1
2
3
git clone https://github.com/3epnm/ais-pltr
cd ais-pltr
npm install

AIS-PLTR uses webpack to bundle the source files. npm start is called to run the development server or npm run build to build a bundle of the application. The resulting dist folder within the project directory can then be served via a webserver, eg. nginx. At the end of this article, a example for a possible nginx configuration is given.

Features

The application only covers the area of the Port of Hamburg. Usually there are no more than 370 ships in the port, so that each of these ships can be renewed in real time without getting into performance problems.

The range of features is limited, but allows interesting things to be observed. In addition to a table of all ships, a detailed view of the ship parameters and a table of all ship positions of the last hour, the user can display and record the positions track for up to 8 ships.

The Map

The center of the application is a map on which the ship positions are rendered. The red dot indicates the mounting position of the AIS transceiver. Unfortunately, not all ships report the exact direction in which the bow is pointing. In this case, the ships are shown as a simple marker when they are not moving and this information cannot be derived from the course of the ship.

Arrival area of the port ferries at Hamburg Landungsbrücken
Arrival area of the port ferries at Hamburg Landungsbrücken

The Ship Table

The Ship table can be opened via the main menu drawer. The latter is shown if the user activates the menu icon in the top, left corner. If a ship in this table has updates, the corresponding row is animated. The table can be sorted if the user clicks on a column header. On the top right of the ship table a search field is located, where the table can be filtered by name, type or MMSI of a ship. A function, where the table can be filtered by port ferries can be found left from the search field. A click on a row centers the map to the current position of the selected vessel.

The ship table filtered by port ferries
The ship table filtered by port ferries

Vessel Detail Views

If the User clicks on a ship marker, a popup with some details about the ship is shown as well as a ship image, if a free to use image is available. The popup offers a more detailed view to see all up-to-date AIS Details. When clicked on the Details button, a modal with two tabs is shown. The first tab, Ship Data, shows static and voyage related data. The second tab, Position, shows the current position report. The fields are updated, if changes occur.

The port ferry Altenwerder underway
The port ferry Altenwerder underway

Positions Table and Position Lock

The position table can be opened via the Positions button of the ship popup. In the position table, the history of positions can be viewed where the most recent report is displayed on top. The table is updated if a new position report occurs. The user can use the Lock function found in the top right corner of the position table to automatically re-center the map to the most recent position of the selected ship if it moves.

Position table for port ferry Oortkaten
Position table for port ferry Oortkaten

Track Rendering

The user can create up to eight track recordings. The last button in the detail popup, labeled Track, enables or disables a position recording for a ship. By default, the track of positions for the last hour is displayed. This can be enabled or disabled with the Load Track History button from the main menu. The following screen recording illustrates this functionality. The position of the biggest ship of the three is locked. The other two ships are tugs which support the cargo ship. The track history is turned off.


Screen recording of cargo ships reach the port of Hamburg

If the max number of Tracks is reached, a popup is shown, where the user can remove a recording to make space for a new track recording. This popup is also available over the track collection button from the main menu.

Track Collection Popup
Track Collection Popup

Additional functions

Some additional functions are located on the right side of the title bar.

Additional functions
Additional functions


Some additional functions are located on the right side of the title bar. With the + and - button zoom the view in or out. The lock icon releases the position lock without having to open the position table of the ship in question. The bookmark icon before the help icon opens a list of regions, to which the map will zoom/pan if an entry is clicked. The last icon opens the help function, where the user can learn about the main features of this application.

Provide AIS Data with Rest and via Web Socket

For the AIS PLTR to be functional, a backend service is needed. AIS PLTR SRV is a simple backend for this purpose. The requirements are not particularly high. The only available resources are endpoints for reading shipdata and positions from the database as well as a web socket for streaming updates in real time.

Installation

The installation is done by clone the project repository and installing the dependencies.

1
2
3
git clone https://github.com/3epnm/ais-pltr
cd ais-pltr
npm install

Before the project can be started, the configuration must first be adjusted.

Configuration

The configuration is done with node-config-ts. A simple but effective configuration manager for typescript based projects. The easiest way to adapt the configuration is to adapt the file default.json from the config directory. In practice, it has proven useful to use a new configuration file depending on the NODE_ENV variable. How this works is explained in the documentation of node-config-ts. Refer to the npm page node-config-ts to learn all its features.

The configuration file has the following content, which is explained below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"database": {
"url": "mongodb://127.0.0.1:27017",
"options": {
"useNewUrlParser": true,
"useUnifiedTopology": true
},
"dbName": ""
},
"http": {
"port": 4000
},
"logger": {
"level": "warn",
"filter": 0,
"filename": ""
},
"ssh": {
"enabled": true,
"forward": "61126:127.0.0.1:61126",
"host": "***"
}
}

Database section

The configuration of the database is as follows:

Parameter Description
url The connection url to the MongoDB instance.
options

Options to setup the Database Connection.
All options of the native MongoDB Driver are possible.
dbName The name of the database where to store report data.
Http section

The only configuration which can be done is setting the port number on which the server is started.

Parameter Description
port The port number on which the server is listen for requests.
Logger section

Logging is done with winston universal logging library.

Parameter Description
level

The logging levels are named after npm logging levels and allows
to configure how verbose the messages are written to a logfile or to stdout.
filter


The filter is a MMSI Number - an identifier every AIS Report has
and which is unique to the vessel.
Used to log the processing of Reports for a specific vessel.
filename

The filename where the log file is written to.
If empty, the messages are written to stdout.

The logger uses the winston-daily-rotate-file to archive log files if used in a debug level for a longer period of time. In addition, the logger can also be controlled via the NODE_ENV environment variable. If set to “debug”, the log messages also written to stdout, regardless of whether the filename parameter is set or not.

SSH section

The following configuration enables an SSH tunnel to be created if required, which is started as a child process.

Parameter Description
enabled Whether the function is used or not
forward Which port from the source is forwarded to a port at the destination
host The host of the source

Start the Service

Once the configuration is done, the service can be started with npm start

REST Endpoints

The REST services serve as a proxy to MongoDB. MongoDB offers an easy way to create queries with a JSON object. This fact makes it possible to develop a REST service that essentially works like a query on a MongoDB collection. Datetime parameters can be written as a ISO date string and are automatically transformed to JavaScript Date Objects for a valid request. Another parameter is limit in order to limit the result set according to the number of objects returned.

Ships Endpoint

The /api/ships endpoint accepts requests against the ship data collection. Queries are defined using the cgi query parameter.
An additional limit parameter allows the number of documents to be limited.

Parameter Description
filter

A MongoDB Collection Filter, ISO date strings are automatically transformed to JavaScript Date Objects
limit

The number of documents to be loaded from the collection, if no filter is defined, the limit defaults to 500

All parameters are optional and the resulting collection is automatically sorted by ship name.

Example

HTTP GET /api/ships?filter={"MMSI":211207080}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[
{
"_id": "5ee0cf84532a3962b0f8c94a",
"AIS": 5,
"Channel": "A",
"MMSI": 211207080,
"TimeStamp": "2020-06-18T17:05:47.158+02:00",
"Data": {
"AISversion": 0,
"PositionType": 0,
"IMOnumber": 0,
"CallSign": "DD4794",
"Name": "ABY/LOU",
"ShipType": 70,
"DimA": 75,
"DimB": 10,
"DimC": 7,
"DimD": 3,
"Draught": 2.8,
"ETA": "2021-01-21T11:00:00+00:00",
"Destination": "DEHAM00700CITYX06230"
},
"CreatedAt": "2020-06-10T12:18:12.870Z",
"CreatedBy": "hub-3262",
"UpdatedAt": "2020-06-10T12:18:12.870Z",
"UpdatedBy": "hub-3262",
"Sender": [
{
"Name": "hub-3262",
"TimeStamp": "2020-06-10T12:18:12.870Z"
}
],
"RAW": [
"!AIVDM,2,1,6,A,539gmv800000@;O3C80iDEA@F0@DE8p0000000150@A33t0Ht3R0C@UD,0*3C",
"!AIVDM,2,2,6,A,Qh0000000000000,2*1B"
]
}
]

Position Endpoints

The /api/positions endpoint accepts requests against the positions collection.

Parameter Description
filter

A MongoDB Collection Filter, ISO date strings are automatically transformed to JavaScript Date Objects
limit

The number of documents to be loaded from the collection, if no filter is defined, the limit defaults to 500
options This parameter accepts a serialized JSON Object.

The only option awailable is { "unique": true }. If this option is set, the positions are unique by the vessels MMSI. All parameters are optional and the resulting collection is automatically sorted ascending by ais-timestamp.

The /api/position endpoint is similar like the /api/positions endpoint. The difference is that only a single and most recent position within the result set is returned.

Parameter Description
filter

A MongoDB Collection Filter, ISO date strings are automatically transformed to JavaScript Date Objects
Example

HTTP GET /api/position?filter={"MMSI":211207080}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"_id": "5eeb8881d0977465a5453770",
"AIS": 1,
"Channel": "B",
"MMSI": 211207080,
"TimeStamp": "2020-06-18T17:30:10.000+02:00",
"Location": {
"type": "Point",
"coordinates": [
9.98226,
53.48301
]
},
"Data": {
"Longitude": 9.98226,
"Latitude": 53.48301,
"ROT": -128,
"SOG": 0.1,
"COG": 238.2,
"TrueHeading": 511,
"NavigationStatus": 0,
"PositionAccuracy": 1,
"TimeStampStatus": 10
},
"CreatedAt": "2020-06-18T15:30:42.297Z",
"CreatedBy": "rpi-2673",
"UpdatedAt": "2020-06-18T15:31:09.000Z",
"UpdatedBy": "hub-3262",
"Sender": [
{
"Name": "rpi-2673",
"TimeStamp": "2020-06-18T15:30:42.297Z"
},
{
"Name": "hub-2847",
"TimeStamp": "2020-06-18T15:30:49.925Z"
},
{
"Name": "hub-3262",
"TimeStamp": "2020-06-18T15:31:09.000Z"
}
],
"RAW": [
"!AIVDM,1,1,,B,139K3b0P01PedOJNVVk9CgvDR@5S,0*4F"
]
}

Websocket

AIS PLTR SRV offers a websocket connection, where ship and position updates are broadcast to the clients. The websocket service is implemented with socket.io.

The server can be controlled with messages. As soon as a connection is established, subscribe and unsubscribe can be used to inform the service that the client is ready to receive data or that the connection is to be interrupted. Messages are sent to the server using the socket instance method emit. If the subscription is successful, the server sends new ship data and positions, which results in a corresponding event.

Commands Description
subscribe Informs the server to start sending data.
unsubscribe Informs the server to stop sending data.

A JSON object with a uuid is used by the server to identify the recipient. See the examples below.

Events Description
positions Newly received position data report.
ships Newly received static and voyage data report.
Example

The following example illustrates how this service can be utilized.

1
2
3
4
5
6
7
8
const socket = io()

socket.on('positions', (data: INmeaShipdata) => { 'shipdata object' })
socket.on('ships', (data: INmeaShipdata) => { 'shipdata object' })

socket.emit('subscribe', { "uuid": "<<some uuid>>" })
...
socket.emit('unsubscribe', { "uuid": "<<some uuid>>" })

Proxy for Front- and Backend with Nginx

To bring the front end AIS PLTR and the backend AIS PLTR SRV together, it is good practice to use a proxy server, eg. NGINX, to combine multiple services and static data on a single host. A possible NGINX documentation is as follows.

Behind the /osm route a redirection to openstreetmap is executed. Returned map tiles are cached to improve the performance and reduce the load to openstreetmap. Behind the /api and /socket.io route a redirection ais-pltr-srv. From root, the dist folder of the ais-pltr front end is served.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
http {
...
proxy_cache_path /usr/local/data/nginx
levels=1:2
keys_zone=openstreetmap-backend-cache:8m
max_size=500000m
inactive=1000d;

proxy_temp_path /usr/local/data/nginx/tmp;

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

upstream openstreetmap_backend {
server a.tile.openstreetmap.org;
server b.tile.openstreetmap.org;
server c.tile.openstreetmap.org;
}

upstream ais-pltr-srv {
server 0.0.0.0:4000;
}

server {
listen 0.0.0.0:80;

location /osm {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X_FORWARDED_PROTO http;
proxy_set_header Host $http_host;
proxy_cache openstreetmap-backend-cache;
proxy_cache_valid 200 302 365d;
proxy_cache_valid 404 1m;
proxy_redirect off;
if (!-f $request_filename) {
rewrite ^/osm(/.*)$ $1 break;
proxy_pass http://openstreetmap_backend;
break;
}
}

location /api {
proxy_pass http://ais-pltr-srv;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
}

location /socket.io {
proxy_pass http://ais-pltr-srv;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
}

location / {
index index.html;
alias /var/www/ais-plrt/dist/;
}
}
}