5.2 Ship Routing

Author

Clyde Blanco


Ship Routing is another tool that can be developed using the real-time data collection by VISTools. In this section, we describe how such tool can be run. Our partners from the ILIAD project, namely Foundation for Research and Technology – Hellas (FORTH), Meteorological Environmental Earth Observation (MEEO), and Hidromod - Modelação em Engenharia, successfully packaged the VISIR-2 model that suggests shipping routes based on weather forecasts. The model can optimize for shortest distance, fastest travel time, or lowest CO₂ emissions.You can explore their use case in Greece through this link.

In our implementation, however, we did not deploy the tool for users because the simulation results did not fully align with our specific use case.

Important

The docker execution was done in a Microsoft operating system.

Requirements

You need to install some software before you can test the dockerized model in your local machine. Download and install the latest version of Docker. Then, install an Ubuntu terminal from the Microsoft store. we have used the Ubuntu 20.04.6 LTS. You can then run the following codes in the said terminal. Make sure the Docker software is open.

cd /mnt/c/path/to/the/working/directory #go to the directory

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install cwltool

CWL Tool

The tool relies on Common Workflow Language (CWL) which describes the pipeline on how the data inputs are processed into outputs. Below is the CWL file you need to execute to run the docker container of VISIR-2.

Show CWL file

#!/usr/bin/env cwl-runner

$namespaces:
  s: https://schema.org/
  cwltool: http://commonwl.org/cwltool#

$graph:
- class: Workflow

  hints:
    "cwltool:Secrets":
      secrets: [wrf_username, wrf_password, cmems_username, cmems_password]

  inputs:
    wrf_ftpserver:
      doc: FTP Server for WRF Download
      type: string
    wrf_password:
      doc: Password for WRF Download
      type: string
    wrf_username:
      doc: Username for WRF Download
      type: string
    wrf_remotedir:
      doc: Remote dir path from root for WRF Download
      type: string
    vessel_type:
      type: string
      doc: Vessel Type (sail or motor)
    cmems_username:
      type: string
      doc: CMEMS Username
    cmems_password:
      type: string
      doc: CMEMS Password
    departure_date:
      type: string
      doc: Departure Date (%Y-%m-%dT%H:%M:00Z)
    start_lat:
      type: float
      doc: Starting Latitude
    start_lon:
      type: float
      doc: Starting Longitude
    end_lat:
      type: float
      doc: Ending Latitude
    end_lon:
      type: float
      doc: Ending Longitude
    maxDraught:
      type: float
      doc: Max Draught
    bathymetry_file:
      type: File?
      doc: Optional Bathymetry File

  outputs:
    visir_output:
      type: Directory
      outputSource: running/visir_output

  steps:
    running:
      in:
        wrf_ftpserver: wrf_ftpserver
        wrf_username: wrf_username
        wrf_password: wrf_password
        wrf_remotedir: wrf_remotedir
        vessel_type: vessel_type
        cmems_username: cmems_username
        cmems_password: cmems_password
        departure_date: departure_date
        start_lat: start_lat
        start_lon: start_lon
        end_lat: end_lat
        end_lon: end_lon
        maxDraught: maxDraught
        bathymetry_file: bathymetry_file
      run: '#running'
      out:
      - visir_output

  id: run_visir
  s:author:
  - class: s:Person
    s:email: vasmeth@iacm.forth.gr
    s:name: Vassiliki Metheniti
  - class: s:Person
    s:email: antonisparasyris@iacm.forth.gr
    s:name: Antonios Parasyris
  - class: s:Person
    s:email: joao.ribeiro@hidromod.com
    s:name: João Ribeiro
  - class: s:Person
    s:email: miguel.delgado@hidromod.com
    s:name: Miguel Delgado
  s:contributor:
  - class: s:Person
    s:email: vasmeth@iacm.forth.gr
    s:name: Vassiliki Metheniti
  - class: s:Person
    s:email: antonisparasyris@iacm.forth.gr
    s:name: Antonios Parasyris
  - class: s:Person
    s:email: joao.ribeiro@hidromod.com
    s:name: João Ribeiro
  - class: s:Person
    s:email: miguel.delgado@hidromod.com
    s:name: Miguel Delgado
  s:description: |-
    Solving Dijkstra's optimization algorithm to find optimal ship routes that minimize CO2 emissions, trip time and distance. Using VISIR II software (https://zenodo.org/records/10960842 created by CMCC), that runs using forcing from either CMRL's (https://crl.iacm.forth.gr/en/) high resolution models in the southern Greece region forecasting atmospheric state (WRF 3km x 3km), hydrodynamics (NEMO 1km x 1km) and waves (Wavewatch III 1km x 1km), or the GFS/CMEMS lower resolution forecasts to be able to generalize to routes globally.
  s:keywords:
  - hidromod
  - visir
  - VISIR
  - least CO2 emissions
  - optimal ship routes
  - least distance
  - least time
  - regional
  - high resolution
  - global
  - FORTH
  - CMRL
  s:name: Execution of VISIR model
  s:programmingLanguage: python
  s:softwareVersion: 1.0.0
  s:producer:
    class: s:Organization
    s:name: FORTH
    s:url: https://www.iacm.forth.gr/
    s:address:
        class: s:PostalAddress
        s:addressCountry: GR
  s:sourceOrganization:
  - class: s:Organization
    s:name: FORTH
    s:url: https://www.iacm.forth.gr/
    s:address:
        class: s:PostalAddress
        s:addressCountry: GR
  - class: s:Organization
    s:name: CMRL
    s:url: https://crl.iacm.forth.gr/en/
    s:address:
        class: s:PostalAddress
        s:addressCountry: GR
  - class: s:Organization
    s:name: Hidromod
    s:url: https://hidromod.com/
    s:address:
        class: s:PostalAddress
        s:addressCountry: PT
- class: CommandLineTool

  requirements:
    - class: InlineJavascriptRequirement
    - class: ShellCommandRequirement
    - class: DockerRequirement
      dockerPull: antonisparasyris/iliad:ship_routing
    - class: NetworkAccess
      networkAccess: true
    - class: LoadListingRequirement
      loadListing: deep_listing
    - class: InitialWorkDirRequirement
      listing:
        - entryname: /VISIR/__data/bathymetry/GEBCO_2024_sub_ice_topo.nc
          entry: $(inputs.bathymetry_file)

  hints:
    "cwltool:Secrets":
      secrets: [wrf_username, wrf_password, cmems_username, cmems_password]

  inputs:
    wrf_ftpserver:
      type: string
    wrf_password:
      type: string
    wrf_username:
      type: string
    wrf_remotedir:
      type: string
    vessel_type:
      type: string
    cmems_username:
      type: string
    cmems_password:
      type: string
    departure_date:
      type: string
    start_lat:
      type: float
    start_lon:
      type: float
    end_lat:
      type: float
    end_lon:
      type: float
    maxDraught:
      type: float?
    bathymetry_file:
      type: File?

  outputs:
    visir_output:
      type: Directory
      outputBinding:
        glob: ./OUTPUT

  baseCommand:
  - /bin/bash
  - -c
  arguments:
    - valueFrom: |
        "/VISIR/run.sh '$(inputs.wrf_ftpserver)' '$(inputs.wrf_username)' '$(inputs.wrf_password)' '$(inputs.wrf_remotedir)' \
        '$(inputs.vessel_type)' '$(inputs.cmems_username)' '$(inputs.cmems_password)' '$(inputs.departure_date)' $(inputs.start_lat) \
        $(inputs.start_lon) $(inputs.end_lat) $(inputs.end_lon)$(inputs.maxDraught ? ' ' + inputs.maxDraught : '')"
      shellQuote: false

  id: running
  s:author:
  - class: s:Person
    s:email: vasmeth@iacm.forth.gr
    s:name: Vassiliki Metheniti
  - class: s:Person
    s:email: antonisparasyris@iacm.forth.gr
    s:name: Antonios Parasyris
  - class: s:Person
    s:email: joao.ribeiro@hidromod.com
    s:name: João Ribeiro
  - class: s:Person
    s:email: miguel.delgado@hidromod.com
    s:name: Miguel Delgado
  s:contributor:
  - class: s:Person
    s:email: vasmeth@iacm.forth.gr
    s:name: Vassiliki Metheniti
  - class: s:Person
    s:email: antonisparasyris@iacm.forth.gr
    s:name: Antonios Parasyris
  - class: s:Person
    s:email: joao.ribeiro@hidromod.com
    s:name: João Ribeiro
  - class: s:Person
    s:email: miguel.delgado@hidromod.com
    s:name: Miguel Delgado
  s:description: |-
    Solving Dijkstra's optimization algorithm to find optimal ship routes that minimize CO2 emissions, trip time and distance. Using VISIR II software (https://zenodo.org/records/10960842 created by CMCC), that runs using forcing from either CMRL's (https://crl.iacm.forth.gr/en/) high resolution models in the southern Greece region forecasting atmospheric state (WRF 3km x 3km), hydrodynamics (NEMO 1km x 1km) and waves (Wavewatch III 1km x 1km), or the GFS/CMEMS lower resolution forecasts to be able to generalize to routes globally.
  s:keywords:
  - hidromod
  - visir
  - VISIR
  - least CO2 emissions
  - optimal ship routes
  - least distance
  - least time
  - regional
  - high resolution
  - global
  s:name: Execution of VISIR model
  s:programmingLanguage: python
  s:softwareVersion: 1.0.0
  s:producer:
    class: s:Organization
    s:name: FORTH
    s:url: https://www.iacm.forth.gr/
    s:address:
      class: s:PostalAddress
      s:addressCountry: GR
  s:sourceOrganization:
  - class: s:Organization
    s:name: FORTH
    s:url: https://www.iacm.forth.gr/
    s:address:
      class: s:PostalAddress
      s:addressCountry: GR
  - class: s:Organization
    s:name: CMRL
    s:url: https://crl.iacm.forth.gr/en/
    s:address:
      class: s:PostalAddress
      s:addressCountry: GR
  - class: s:Organization
    s:name: Hidromod
    s:url: https://hidromod.com/
    s:address:
      class: s:PostalAddress
      s:addressCountry: PT


cwlVersion: v1.2

Job YAML File

Aside from the CWL file, you need to have a job file which contains important input details such as the starting and end point of the route, departure date and time, type of the vessel, maximum vessel draught, and CMEMS credentials.

wrf_ftpserver: InsertWRFServer
wrf_username: InsertWRFusername
wrf_password: InsertWRFpassword
wrf_remotedir: InsertWRFremotedir
start_lat: 51.394615
start_lon: 3.168766
end_lat: 54.01584
end_lon: 1.88759
vessel_type: motor   #sail or motor
cmems_username: CMEMSusername
cmems_password: CMEMSpassword
departure_date: 2025-09-04T08:30:00Z
maxDraught: 5
bathymetry_file:
  class: File
  path: /mnt/c/path/to/the/custom/bathymetry/file/output.nc

Custom Bathymetry

The docker container uses the GEBCO Sub Ice layer to construct the nodes of the network graph. Moreover, the max draught argument in the job file above filters out nodes that are too shallow for the vessel. Hence these nodes are not considered in the route optimzation. We can also add non-navigable areas such as offshore windfarms. This can be done by setting the depth value in this areas to zero. Currently, the docker container can only take NetCDF file from GEBCO. This means that if we import and process this GEBCO file in R, the original structure of the file is altered and this can’t be read properly by the docker. To solve this, we can perform 2 steps - (1) create a mask in R using the original GEBCO bathymetry file, (2) process the GEBCO bathymetry file using the mask through the command line software NCO.

First, you need to download the NetCDF file from GEBCO. Specify the bounding box of the area of interest. Once you have downloaded the NetCDF file, you have to process it in R. The resulting mask layer should contain cell values of 1 for navigable areas and values of 0 for non-navigable areas.

library(terra)
library(sf)
library(tidyverse)
library(ncdf4)

# import bathymetry file to R
gebco <- rast("./gebco_cropped.nc") 

# import the shapefile that will mask the bathymetry raster. In this case, we are using windfarms
OWF <- st_read("./OWF/EMODnet_HA_WindFarms_pg_20220324.shp") %>%
  filter(STATUS %in% c("Production","Construction")) %>%
  vect() %>%
  project(crs(gebco))

# perform masking, this will update the raster cells inside the polygons into NA  
gebco_masked <- mask(gebco,OWF, inverse=T, updatevalue = NA)

# convert the NA cells into zero and the rest into 1
gebco_masked <- ifel(is.na(gebco_masked), 0, 1)

# flip the raster vertically
mask <- flip(gebco_masked, direction = "vertical")

writeCDF(mask,"mask.nc", varname="mask", overwrite=TRUE)


In the second last line of code, the raster is flipped vertically. We have done this because the command line package NCO, which will further process the NetCDF file, reads the cells inversely compared to terra. Then, we need to install NCO, apply the mask, and run the VISIR docker container.

sudo apt install nco

ncrename -d latitude,lat -d longitude,lon mask.nc

ncks -A -v mask mask.nc gebco_cropped.nc

ncap2 -s 'elevation=elevation*mask' gebco_cropped.nc output.nc


cwltool workflow.cwl#run_visir job.yml


When the execution is successful, an Output folder is created containing the simulation results. The files containing the name *_voyageplan.json are the one we are interested in and we can visualize this in R.

VISIR
└── OUTPUT
    ├── 𝘊𝘖2𝘵_𝘷𝘰𝘺𝘢𝘨𝘦𝘱𝘭𝘢𝘯.𝘫𝘴𝘰𝘯
    ├── 𝘤𝘶𝘳𝘳.𝘯𝘤
    ├── 𝘥𝘪𝘴𝘵_𝘷𝘰𝘺𝘢𝘨𝘦𝘱𝘭𝘢𝘯.𝘫𝘴𝘰𝘯
    ├── 𝘚𝘰𝘭𝘶𝘵𝘪𝘰𝘯𝘴.𝘫𝘴𝘰𝘯
    └── 𝘵𝘪𝘮𝘦_𝘷𝘰𝘺𝘢𝘨𝘦𝘱𝘭𝘢𝘯.𝘫𝘴𝘰𝘯


We can quickly visualize the routes suggested using the mapview R package. This route starts from Zeebrugge, Belgium and ends near a windpark in the UK. As you can see the suggested route navigates around the windparks instead of going through them.

Code
library(jsonlite)
library(mapview)
library(lubridate)

least_CO2 <- fromJSON("./Data/Polygons/OUTPUT/CO2t_voyageplan.json") %>%
  arrange(ISO_date) %>%
  st_as_sf(coords=c("lon","lat"), crs=st_crs(4326)) %>%
  summarise(do_union=F) %>%
  sf::st_cast(.,"LINESTRING") 


least_dist <- fromJSON("./Data/Polygons/OUTPUT/dist_voyageplan.json") %>%
  arrange(ISO_date) %>%
  st_as_sf(coords=c("lon","lat"), crs=st_crs(4326)) %>%
  summarise(do_union=F) %>%
  sf::st_cast(.,"LINESTRING") 

least_time <- fromJSON("./Data/Polygons/OUTPUT/time_voyageplan.json") %>%
  arrange(ISO_date) %>%
  st_as_sf(coords=c("lon","lat"), crs=st_crs(4326)) %>%
  summarise(do_union=F) %>%
  sf::st_cast(.,"LINESTRING") 

mapview(OWF) +
  mapview(least_CO2, color="#5ec962") +
  mapview(least_dist, color="#31688e") +
  mapview(least_time, color="#bc3754") 



We can also visualize one route and see the direction and speed. Let’s have a look at the route with least CO2 emissions.

Code
library(leaflet)
library(leaflet.extras2)

points_sf <- fromJSON("./Data/Polygons/OUTPUT/CO2t_voyageplan.json") %>%
  arrange(ISO_date) %>%
  drop_na(HDG) %>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326) %>%
  st_transform(points_sf, crs = 32630) 

arrow_length <-3000  # you can scale this with SOG or keep constant
arrow_lines <- points_sf %>%
  mutate(coords = st_coordinates(geometry)) %>%
  mutate(
    hdg_rad = (90 - HDG) * pi / 180,
    x_start = coords[, 1],
    y_start = coords[, 2],
    x_end = x_start + arrow_length * cos(hdg_rad),
    y_end = y_start + arrow_length * sin(hdg_rad)) %>%
  st_set_geometry(NULL) %>%
  select(-coords) %>%
  mutate(line_geom = paste0("LINESTRING(",x_start," ",y_start,", ",x_end," ",y_end,")")) %>%
  st_as_sf(., wkt="line_geom", crs=32630) %>%
  st_transform(points_sf, crs = 4326) 

pal <- colorNumeric("YlOrRd", domain = arrow_lines$SOG)

points <- fromJSON("./Data/Polygons/OUTPUT/CO2t_voyageplan.json") %>%
  arrange(ISO_date) %>%
  drop_na(HDG) %>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326)

leaflet() %>%
  addProviderTiles("CartoDB.DarkMatter") %>%
  setView(lng = 1.5, lat = 51.5, zoom = 6) %>%
  addArrowhead(
    data = arrow_lines,
    color = ~pal(SOG),  # color by speed
    weight = 2,
    opacity = 1,
    options = arrowheadOptions(yawn = 60, size = "50%"),
    popup = ~paste0("Speed: ", round(SOG, digits=2), " knots <br> 
                   Heading: ", round(HDG, digits=2), "° <br> 
                   Timestamp: ", ymd_hms(ISO_date))) %>%
  addCircles(
    data = points,
    color = ~pal(SOG),
    weight = 4,
    opacity = 1,
    popup = ~paste0("Speed: ", round(SOG, digits=2), " knots <br> 
                   Heading: ", round(HDG, digits=2), "° <br> 
                   Timestamp: ", ymd_hms(ISO_date))) %>%
  addPolygons(data=OWF, stroke=F, color="beige") %>%
  addLegend(values = c(min(arrow_lines$SOG),max(arrow_lines$SOG)), pal = pal, opacity=1,
            title = "Speed Over <br>Ground (knots)")

Practical Takeaways and What’s Next

The plot above shows the suggested Speed Over Ground (SOG) along the simulated route. However, these values are unrealistically high for a typical Belgian beam trawl fishing vessel, which reaches a maximum steaming speed of only about 10 knots. While this demonstrates that the Dockerized VISIR-2 model can be executed successfully in our region and has potential as a deployable tool, the simulated routes currently suggest speed profiles that are not operationally feasible. For this reason, the model cannot yet be applied to our use case.

To tailor VISIR-2 more closely to our context, vessel performance curves need to be developed. These curves describe how the vessel behaves under varying sea and weather conditions, including both achievable speeds and associated CO₂ emissions, and they serve as a critical input to the VISIR-2 model. Such curves can be derived using the fuel consumption data currently being collected through VISTools.

In addition, the operational area of Belgian beam trawlers is subject to specific navigational constraints. For instance, vessels transiting through Traffic Separation Schemes (TSS) must remain in the designated lane for their direction of travel, and any crossing must occur at right angles. At present, the VISIR-2 graph search algorithm does not account for these navigational rules, which further limits its immediate applicability.