Skip to contents

Function gfw_sar_fishing_detections() allows users to access Global Fishing Watch’s dataset containing vessels detections using Sentinel-1 Synthetic Aperture Radar (SAR), initially published in Paolo et al. 2024.

This dataset is available publicly in Global Fishing Watch platforms, including our map, APIs, and R and python packages.

gfw_sar_fishing_detections() has the same general structure of functions gfw_ais_vessel_presence() and gfw_ais_fishing_hours(), with arguments for spatial and temporal resolution, start and end dates, region options, and options for grouping and filtering the results.

For more details about the usage of these parameters, please refer to the help menus in the function and to the vessel presence and fishing activity vignettes.

gfw_sar_vessel_detections(
  spatial_resolution = NULL,
  temporal_resolution = NULL,
  start_date = NULL,
  end_date = NULL,
  region_source = NULL,
  region = NULL,
  group_by = NULL,
  filter_by = NULL,
  key = gfw_auth(),
  print_request = FALSE)

SAR and AIS matching

A key characteristic of the SAR vessel detections dataset is that SAR vessel detections go through a matching process with AIS transmissions. For details about the matching process, please refer to Paolo et al. 2024.

Matched detections have therefore vessel identity information and other characteristics available in Global Fishing Watch AIS pipelines.

Unmatched detections can happen for several reasons, including that:

  • Not all vessels are required to use AIS devices, as regulations vary by country, vessel size, and activity.

  • There are large “blind spots” along coastal waters, arising from restrictions to access AIS data by satellites, or from poor reception due to high vessel density and low-quality AIS devices.

  • Vessels can turn off their AIS transponders or manipulate the locations they broadcast, operating “in the dark”, often against regulations. Not every vessel that turns off its transponder is necessarily engaging in suspicious or illicit activities, but the contrary tends to be true.

Basic usage

A simple call, with no group_by or filter_by options will return a simple tibble object with the number of vesselIDs detected and the number of detections by pixel, at the requested resolution.

Let’s request the monthly detections in the Brazil EEZ for 2023.

id <- gfw_region_id(region = "Brazil", region_source = "EEZ") |> 
  # to select only the ids corresponding to EEZs
  filter(POL_TYPE == "200NM") |> 
  dplyr::pull(id)

(brazil_sar <- gfw_sar_vessel_detections(spatial_resolution = "LOW", 
                          temporal_resolution = "MONTHLY",
                          start_date = "2023-01-01",
                          end_date = "2024-01-01",
                          region_source = "EEZ", 
                          region = id))
#> # A tibble: 11,522 × 5
#>      Lat   Lon `Time Range` `Vessel IDs` Detections
#>    <dbl> <dbl> <chr>               <dbl>      <dbl>
#>  1 -25.1 -47.4 2023-12                 1          1
#>  2  -1.6 -39.4 2023-10                 1          1
#>  3  -6.9 -34.7 2023-04                 1          1
#>  4 -25.6 -48.1 2023-12                13         20
#>  5   3.4 -48   2023-10                 1          1
#>  6 -26   -46.1 2023-06                 1          1
#>  7   4.7 -49.7 2023-06                 3          3
#>  8   4.1 -48.5 2023-08                 1          1
#>  9  -1.9 -44.1 2023-09                 1          1
#> 10 -10.5 -35.3 2023-06                 1          1
#> # ℹ 11,512 more rows

group_by

gfw_sar_fishing_detections() has the same group_by options than gfw_ais_vessel_presence() and gfw_ais_fishing_hours(): FLAG, GEARTYPE, FLAGANDGEARTYPE, MMSI and VESSELID.

Like in these other functions, group_by = "VESSEL_ID" returns all available identity fields.

However, gfw_sar_fishing_detections() results can contain NA values corresponding to unmatched detections.

Let’s repeat the request, this time grouping by MMSI:

(sar_by_mmsi <- gfw_sar_vessel_detections(spatial_resolution = "LOW",
                                         temporal_resolution = "MONTHLY",
                                         group_by = "MMSI",
                                         start_date = "2023-01-01",
                                         end_date = "2024-01-01",
                                         region_source = "EEZ",
                                         region = id))
#> # A tibble: 20,996 × 7
#>      Lat   Lon `Time Range`      mmsi `Entry Timestamp`   `Exit Timestamp`   
#>    <dbl> <dbl> <chr>            <dbl> <dttm>              <dttm>             
#>  1  -1.9 -32.3 2023-10      538009746 2023-10-02 07:52:54 2023-10-02 07:52:54
#>  2 -13   -38.6 2023-03      636015411 2023-03-10 08:11:58 2023-03-10 08:11:58
#>  3 -24.2 -46.4 2023-12      538006311 2023-12-21 08:31:36 2023-12-21 08:31:36
#>  4 -24.2 -46.2 2023-01      241768000 2023-01-31 08:31:30 2023-01-31 08:31:30
#>  5 -25.5 -48.4 2023-02      412289000 2023-01-12 08:40:16 2023-10-10 08:32:05
#>  6  -3.6 -38.4 2023-01      538005596 2023-01-12 08:33:49 2023-07-20 08:09:59
#>  7  -1.8 -43.9 2023-02      477118900 2023-02-20 21:17:36 2023-11-17 08:14:12
#>  8 -24.1 -46.2 2023-03      477752500 2023-03-08 08:31:29 2023-12-30 08:03:33
#>  9 -26.3 -47.6 2023-09      710001106 2023-05-07 08:31:58 2023-09-16 08:32:30
#> 10  -5.6 -34.8 2023-12      311001087 2023-12-30 08:01:53 2023-12-30 08:01:53
#> # ℹ 20,986 more rows
#> # ℹ 1 more variable: Detections <dbl>

In this example in the Brazilian EEZ, roughly a fifth of the detections were unmatched to AIS.

sar_by_mmsi |> dplyr::count(is.na(mmsi))
#> # A tibble: 2 × 2
#>   `is.na(mmsi)`     n
#>   <lgl>         <int>
#> 1 FALSE         16566
#> 2 TRUE           4430

filter_by

To filter only by matched or unmatched detections, the function allows for options "matched='true'" and "matched='false'" in the filter_by options:

Note: When filters are applied, the API does not return any field indicating this, so we recommend adding a column to identify the datasets. Another option is to use NA values in the columns as indicators of unmatched detections.

Let’s fetch matched and unmatch detections, create a matched column and bind the two tables to map matched and unmatched detections in the region:

# matched ddetections 
matched_sar <- gfw_sar_vessel_detections(spatial_resolution = "LOW", 
                          temporal_resolution = "MONTHLY",
                          group_by = "VESSEL_ID",
                          start_date = "2023-01-01",
                          end_date = "2024-01-01",
                          region_source = "EEZ", 
                          region = id, 
                          filter_by = "matched='true'"
                          ) |> 
  mutate(matched = "Matched")

matched_sar
#> # A tibble: 16,585 × 17
#>      Lat   Lon `Time Range` `Vessel ID`  Flag  `Vessel Name` `Entry Timestamp`  
#>    <dbl> <dbl> <chr>        <chr>        <chr> <chr>         <dttm>             
#>  1 -24.2 -46.4 2023-09      e6d126a6e-e… SGP   ELIZABETH     2023-08-08 08:05:15
#>  2  -2.5 -44.4 2023-09      3757b559c-c… BRA   SAO GABRIEL   2023-01-27 21:17:06
#>  3 -17.1 -37.9 2023-01      95a70d8dc-c… ATG   BBC GREENLAND 2023-01-28 08:05:09
#>  4 -25.6 -48.1 2023-09      c7d265240-0… LBR   CAPTAIN CHRI… 2023-06-24 08:32:01
#>  5 -22.4 -41.8 2023-03      a0a6f569d-d… BRA   LOCAR XXI     2023-02-26 08:14:31
#>  6 -10.6 -34.7 2023-10      adc939607-7… LBR   EVANGELISTRIA 2023-10-14 07:55:01
#>  7   0.4 -46.1 2023-06      491b9e98b-b… HKG   JIN PING      2023-06-27 08:49:43
#>  8  -2.3 -39.6 2023-01      8ce7f0dbb-b… MHL   SALSA         2023-01-26 08:17:15
#>  9 -25.7 -48.3 2023-06      86d1fd275-5… PAN   GLORY LOONG   2023-02-24 08:31:29
#> 10 -25.9 -47.5 2023-05      344aef521-1… BRA   D.MATTOS=IV   2023-05-19 08:32:24
#> # ℹ 16,575 more rows
#> # ℹ 10 more variables: `Exit Timestamp` <dttm>, `Gear Type` <chr>,
#> #   `Vessel Type` <chr>, MMSI <dbl>, IMO <dbl>, CallSign <chr>,
#> #   `First Transmission Date` <dttm>, `Last Transmission Date` <dttm>,
#> #   Detections <dbl>, matched <chr>

# unmatched ddetections 

unmatched_sar <- gfw_sar_vessel_detections(spatial_resolution = "LOW", 
                          temporal_resolution = "MONTHLY",
                          group_by = "VESSEL_ID",
                          start_date = "2023-01-01",
                          end_date = "2024-01-01",
                          region_source = "EEZ", 
                          region = id, 
                          filter_by = "matched='false'"
                          ) |> 
  mutate(matched = "Unmatched")

# bind rows: 
(all_sar_detections <- dplyr::bind_rows(matched_sar, unmatched_sar))
#> # A tibble: 21,006 × 17
#>      Lat   Lon `Time Range` `Vessel ID`  Flag  `Vessel Name` `Entry Timestamp`  
#>    <dbl> <dbl> <chr>        <chr>        <chr> <chr>         <dttm>             
#>  1 -24.2 -46.4 2023-09      e6d126a6e-e… SGP   ELIZABETH     2023-08-08 08:05:15
#>  2  -2.5 -44.4 2023-09      3757b559c-c… BRA   SAO GABRIEL   2023-01-27 21:17:06
#>  3 -17.1 -37.9 2023-01      95a70d8dc-c… ATG   BBC GREENLAND 2023-01-28 08:05:09
#>  4 -25.6 -48.1 2023-09      c7d265240-0… LBR   CAPTAIN CHRI… 2023-06-24 08:32:01
#>  5 -22.4 -41.8 2023-03      a0a6f569d-d… BRA   LOCAR XXI     2023-02-26 08:14:31
#>  6 -10.6 -34.7 2023-10      adc939607-7… LBR   EVANGELISTRIA 2023-10-14 07:55:01
#>  7   0.4 -46.1 2023-06      491b9e98b-b… HKG   JIN PING      2023-06-27 08:49:43
#>  8  -2.3 -39.6 2023-01      8ce7f0dbb-b… MHL   SALSA         2023-01-26 08:17:15
#>  9 -25.7 -48.3 2023-06      86d1fd275-5… PAN   GLORY LOONG   2023-02-24 08:31:29
#> 10 -25.9 -47.5 2023-05      344aef521-1… BRA   D.MATTOS=IV   2023-05-19 08:32:24
#> # ℹ 20,996 more rows
#> # ℹ 10 more variables: `Exit Timestamp` <dttm>, `Gear Type` <chr>,
#> #   `Vessel Type` <chr>, MMSI <dbl>, IMO <dbl>, CallSign <chr>,
#> #   `First Transmission Date` <dttm>, `Last Transmission Date` <dttm>,
#> #   Detections <dbl>, matched <chr>

Let’s plot the resulting dataset:

sar_effort_light <- rev(c("#ff4573", "#7b2e8d", "#093b76", "lightblue"))

all_sar_detections |>
   group_by(Lat, Lon, matched) |>
   summarize(detections = sum(Detections, na.rm = T)) |>
   ggplot() +
   geom_tile(aes(x = Lon, y = Lat, fill = detections)) +
   geom_sf(data = rnaturalearth::ne_countries(returnclass = "sf", scale = "medium")) +
   coord_sf(xlim = c(min(all_sar_detections$Lon), max(all_sar_detections$Lon)),
            ylim = c(min(all_sar_detections$Lat), max(all_sar_detections$Lat))) +
   scale_fill_gradientn(
      trans = "log10",
      colors = sar_effort_light,
      na.value = "lightblue",
      labels = scales::label_comma()
   ) +
   labs(title = "",
        subtitle = "",
        fill = "SAR Detections") +
   theme(
      panel.background = element_rect(fill = "aliceblue"),
      axis.text.x = element_text(angle = 90),
      plot.background = element_rect(fill = "white")
   ) + 
  facet_grid(~matched) + 
  theme_minimal()
#> `summarise()` has grouped output by 'Lat', 'Lon'. You can override using the
#> `.groups` argument.

References

Paolo, F., Kroodsma, D., Raynor, J., Hochberg, T., Davis, P., Cleary, J., Marsaglia, L., Orofino, S., Thomas, C., Halpin, P., 2024. Satellite mapping reveals extensive industrial activity at sea. Nature 625, 85–91. https://doi.org/10.1038/s41586-023-06825-8