Derick Ongeri's Blog

Derick Ongeri's Blog

After the Ever Given

Assessing the Ship traffic in the Suez Canal Using Sentinel 1 on Google Earth Engine

After the Ever Given

The Ever Given, a Panama-flagged ship that carries cargo between Asia and Europe, ran aground Tuesday in the narrow canal that runs between Africa and the Sinai Peninsula. The ship was blown off course by high winds on its way through the Suez Canal.

At 400 metres long, the Ever Given is longer than the canal is wide, and the ship became wedged firmly in both banks, completely blocking traffic.

We will attempt to use earth observation to tell this story and asses the traffic situation using Sentinel 1 imagery form ESA and Google Earth Engine cloud computing platform. GEE makes it easier to view and analyze it from above and realize the impedance to Canal navigation initiated by the Ever Given ship.

To do this:

  • Import Sentinel 1 image GRD image collection on Google Earth Engine covering the area around the Suez canal
    var sentinel1 = ee.ImageCollection("COPERNICUS/S1_GRD"),
    geometry = 
    /* color: #d6cdcb */
    /* shown: false */
    /* displayProperties: [
        "type": "rectangle"
    ] */
        [[[32.07628670280444, 30.474108789352172],
          [32.07628670280444, 30.152830750595967],
          [32.68328133171069, 30.152830750595967],
          [32.68328133171069, 30.474108789352172]]], null, false);
  • Filter the image collection by date between the 2021 March 23 and 2021 March 27 and select the interferometric wide swath mode.
var s1 = sentinel1
  // Filter to get images with VH polarization.
  //.filter(ee.Filter.listContains('transmitterReceiverPolarisation', 'VH'))
  // Filter to get images collected in interferometric wide swath mode.
  .filter(ee.Filter.eq('instrumentMode', 'IW'))
Map.centerObject(geometry, 10); 
Map.addLayer(s1, {min: -15, max: 0}, 's1')


  • Now we get the distance, as determined by the specified distance metric, to the nearest non-zero valued pixel in the input. The output contains values for all pixels within the given neighborhood size, regardless of the input's mask. Note that the default distance metric returns squared distance so we have to do a squre root.
function erode(img, distance) {
  var d = (img.not().unmask(1)
  return img.updateMask(
function dilate(img, distance) {
  var d = (img.fastDistanceTransform(256).sqrt()
  • Creating the Land and ocean mask
var land = ee.Image("USGS/SRTMGL1_003").unmask(0).gt(0)
var landMask = erode(dilate(land, 200), 100).mask().eq(1)  //These two functions are used to make raster buffer
var oceanMask = erode(land.not(), 50).mask().eq(1)  
var smooth_map = oceanMask
                      radius: 10, kernelType: 'octagon', units: 'pixels', iterations: 1

var crude = oceanMask.updateMask(oceanMask.connectedPixelCount(1024, false).gte(1024))
   var landmaskdeal =img.mask(crude)
   return landmaskdeal ;

cover 2.png

2021-03-27 23_04_41-_sentinel1Ship_detection - Earth Engine Code Editor.png

  • Get the accusition dates of each of the Sentinel 1 composites to append to a ship band later
var input = landdeal.filterBounds(geometry).sort('system:time_start');
var Dates ={
      return img.set('Date', ee.Date(img.get('system:time_start')).format('YYYY-MM-dd'));//FORMAT DATES
      return img.clip(geometry);//CLIP IMAGE TO GEOMETRY
  • Creating a band for images of ships with the acquisition dates added as a propery of each band
var acqDatesAppend = Dates.filter(
  ee.Filter.equals({leftField: 'acqDate1', rightField: 'acqDate2'}));
  ee.Filter.equals({leftField: 'system:time_end', rightField: 'system:time_start'});
//Mask ships. So any ship has to be above 0.5 in the VH band
// A negative VH suggests ocean and we only want ships!
var ships ='VH').map(function(img){
  var shipsOnly = img.gte(0.5);//Binary of ships
  var DEMWithHeight = shipsOnly.mask(img);//Detect just the ships
  return img.set('Ships', DEMWithHeight)
var addShipBand ={
  return img.addBands(img.get('Ships')).rename(['VH', 'ships']);//Add the ships as a band 
  • Adding the Latitude and longitude property to the shipband
var latLong =['VH', 'ships']).map(function(img){
var proj =[0]).projection()
var latlon = ee.Image.pixelLonLat().reproject(proj)
var coords =['longitude', 'latitude'])//Gives us the lat, long
  reducer: ee.Reducer.mean(),//Get mean lat, longs in image
  geometry: geometry,
  scale: 30,
  maxPixels: 1e12
var lat = ee.List(coords.get('latitude'))
var lon = ee.List(coords.get('longitude'))
return img.set('latitude', ee.Number(lat)
              ,'longitude', ee.Number(lon))
  • Now here is where the magic happens, we reduce the image collection to vectors. This is how we deal with each ship individually and assign each ship a lattitude and longitude as a mean of the image.
var reduceToFeatureCol =['VH', 'ships']).map(function(img){
  var vectors ='ships').toInt().reduceToVectors({
    geometry: ee.Feature(img).geometry(), 
    scale: 10, 
    geometryType: 'Polygon',
    crs: 'EPSG:4326',
    maxPixels: 1e12,
  return vectors
    var getDate =  ee.Date(img.get('system:time_start')).format('YYYY-MM-dd')
    var cords = img.get('system:footprint')
    var getLat = img.get('latitude')
    var getLon = img.get('longitude')
          return feat.set(
    'Date', getDate//Set date
    ,'Latitude', getLat//Set latitude
    ,'Longitude', getLon//Set longitude 
    ,'Time', img.get('segmentStartTime')// time 
    ,'Company', img.get('GRD_Post_Processing_facility_org')//Company
print('Feature Col Of Ships', reduceToFeatureCol.limit(100))

List of ships printed on the console list.png

  • Export as shapefile
description: 'ships',
Share this