After the Ever Given
Assessing the Ship traffic in the Suez Canal Using Sentinel 1 on Google Earth Engine
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')
- 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 ;
});
- 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
- Export as shapefile
Export.table.toDrive({
collection:reduceToFeatureCol,
description: 'ships',
fileFormat:'SHP',
});