After the Ever Given

Derick OngeriDerick Ongeri
6 min read

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"
      }
    ] */
    ee.Geometry.Polygon(
        [[[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'))
  .filterBounds(geometry)
  .filterDate("2021-03-23","2021-03-27");
print("s1",s1)
print(s1.size())
Map.centerObject(geometry, 10); 
Map.addLayer(s1, {min: -15, max: 0}, 's1')

cover.png

  • 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.
//corrode 
function erode(img, distance) {
  var d = (img.not().unmask(1)
       .fastDistanceTransform(256).sqrt()
       .multiply(ee.Image.pixelArea().sqrt()))
  return img.updateMask(d.gt(distance))
}
//Swell
function dilate(img, distance) {
  var d = (img.fastDistanceTransform(256).sqrt()
       .multiply(ee.Image.pixelArea().sqrt()))
  return d.lt(distance)
}
  • Creating the Land and ocean mask
//DEMandlandmask
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
                    .focal_mode({
                      radius: 10, kernelType: 'octagon', units: 'pixels', iterations: 1
                    })
                    .mask(oceanMask.gte(1))

var crude = oceanMask.updateMask(oceanMask.connectedPixelCount(1024, false).gte(1024))
                     .unmask(smooth_map)
var landdeal=s1.map(function(img){  
   img=img
   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 = input.map(function(img){
      return img.set('Date', ee.Date(img.get('system:time_start')).format('YYYY-MM-dd'));//FORMAT DATES
}).map(function(img){
      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 = acqDatesAppend.select('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)
})
//print("ships",ships)
var addShipBand = ships.map(function(img){
  return img.addBands(img.get('Ships')).rename(['VH', 'ships']);//Add the ships as a band 
});
print(addShipBand.limit(100))
  • Adding the Latitude and longitude property to the shipband
var latLong = addShipBand.select(['VH', 'ships']).map(function(img){
var proj = img.select([0]).projection()
var latlon = ee.Image.pixelLonLat().reproject(proj)
var coords = latlon.select(['longitude', 'latitude'])//Gives us the lat, long
                 .reduceRegion({
  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))
})
print("latLong",latLong)
  • 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 = latLong.select(['VH', 'ships']).map(function(img){
  var vectors = img.select('ships').toInt().reduceToVectors({
    geometry: ee.Feature(img).geometry(), 
    scale: 10, 
    geometryType: 'Polygon',
    crs: 'EPSG:4326',
    maxPixels: 1e12,
    tileScale:4
  })
  return vectors
  .map(function(feat){
    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
    )
  })
}).flatten()
print('Feature Col Of Ships', reduceToFeatureCol.limit(100))

List of ships printed on the console list.png

  • Export as shapefile
Export.table.toDrive({
collection:reduceToFeatureCol,
description: 'ships',
fileFormat:'SHP',
});
1
Subscribe to my newsletter

Read articles from Derick Ongeri directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Derick Ongeri
Derick Ongeri