The goal of this assignment was to create a method to determine the outline of a speed reduction zone and its effect on the local marine traffic for Dominica.
The goal of this assignment was to create a method to determine the outline of a speed reduction zone and its effect on the local marine traffic for Dominica. The speed reduction zone encompasses the preferred habitat of the local sperm whales. This area was approximated as areas of frequent whale sightings off the coast.
# libraries for the assignment:
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import Polygon
We downloaded the country outline into the notebook.
= "data/dominica/dma_admn_adm0_py_s1_dominode_v2.shp"
fp
= gpd.read_file(fp)
dom_outline
= dom_outline.to_crs("EPSG:4602") dom_outline
We plot the outline for exploration.
= plt.subplots(figsize=(3, 3), dpi = 200)
fig, ax True)
ax.grid(= ax,
dom_outline.plot(ax = "darkgreen")
color
"Dominica Outline")
ax.set_title("Longitude")
ax.set_xlabel("Latitude") ax.set_ylabel(
Text(94.618846252753, 0.5, 'Latitude')
First, we read in the data and projected it into an appropriate CRS.
= "data/sightings2005_2018.csv"
fp_whales
= gpd.read_file(fp_whales)
whales
= gpd.points_from_xy(whales.Long, whales.Lat, crs = "EPSG:4326")
geometry = whales.set_geometry(geometry) whales_geom
Next we convert the geometries from degrees to meters.
= whales_geom.to_crs("EPSG:2002")
whales_geom
= dom_outline.to_crs("EPSG:2002") dom_outline
Making the point observations into a habitat region.
= whales_geom.total_bounds
xmin, ymin, xmax, ymax
= 2000
length = 2000
wide
= list(np.arange(xmin, xmax + wide, wide))
cols = list(np.arange(ymin, ymax + length, length))
rows
= []
polygons for x in cols[:-1]:
for y in rows[:-1]:
+wide, y), (x+wide, y+length), (x, y+length)]))
polygons.append(Polygon([(x,y), (x
= gpd.GeoDataFrame({'geometry':polygons})
grid "grid.shp") grid.to_file(
= "grid.shp"
fp_grid
= gpd.read_file(fp_grid)
grid = grid.set_crs("EPSG:2002")
grid = grid.to_crs("EPSG:2002") grid
Plotting the grid to make sure it looks how we expected.
= plt.subplots(figsize=(4, 4), dpi=200)
fig, ax True)
ax.grid(
= ax,
grid.plot(ax = "none",
facecolor = 0.1) lw
<AxesSubplot:>
Now we spatially joined the grid we made in the previous step with our sighting data to count the number of sightings in each cell.
= grid.sjoin(whales_geom, how = "inner") grid_join
'count'] = grid_join.groupby(grid_join.index).count()['index_right'] grid[
= grid.loc[(grid['count'] > 20)] grid
We used unary_union
to create a Multipolygon containing
the union of all geometries in the GeoSeries
= grid.unary_union
speed_zone speed_zone
= gpd.GeoSeries(speed_zone)
speed_zone = speed_zone.convex_hull
speed_zone speed_zone.plot()
<AxesSubplot:>
The plot above is now our habitat zone. Next we added it to the Dominica outline.
= gpd.GeoDataFrame({'geometry':speed_zone}, crs = 2002) speed_zone
= plt.subplots(figsize = (10,10), dpi = 200)
fig, ax = ax,
dom_outline.plot(ax = "darkgreen")
color = ax,
speed_zone.plot(ax = "lightyellow",
color = "black",
edgecolor = 0.5)
alpha "Dominica Outline and Sperm Whale Habitat")
ax.set_title("Longitude")
ax.set_xlabel("Latitude") ax.set_ylabel(
Text(364.48357887628714, 0.5, 'Latitude')
There is some overlap from the outline of Dominica and the habitat but that is fine for our calculations. Next we will upload the vessel data.
Using the Automatic Identification System (AIS) we loaded the vessel data.
= "data/station1249.csv"
fp_vessels
= gpd.read_file(fp_vessels)
vessels
= gpd.points_from_xy(vessels.LON, vessels.LAT, crs = "EPSG:4326")
geometry = vessels.set_geometry(geometry) vessels_geom
= vessels_geom.to_crs("EPSG:2002") vessels_geom
Next we explored the datatypes to make sure they are what we expected.
print(vessels_geom.dtypes)
field_1 object
MMSI object
LON object
LAT object
TIMESTAMP object
geometry geometry
dtype: object
The ‘TIMESTAMP’ data is not in datetime formatting so we changed it.
'TIMESTAMP'] = pd.to_datetime(vessels_geom['TIMESTAMP'], format = '%Y-%m-%d %H:%M:%S') vessels_geom[
print(vessels_geom.dtypes)
field_1 object
MMSI object
LON object
LAT object
TIMESTAMP datetime64[ns]
geometry geometry
dtype: object
A few things needed to be done to make the necessary calculations: 1) Spatially subset vessel tracks within whale habitat 2) Sort the dataframe by MMSI and TIMESTAMP 3) Create a copy of the dataframe and shift each observation down one row using shift() and then join with the original dataset 4) Drop all rows in the joined dataframe in which the MMSI of the left is not the same as the one on the right. 5) Set the geometry column with the set_geometry() function
# 1) spatial subsetting
= vessels_geom.sjoin(speed_zone, how = "inner") vessels_geom
# 2) sorted the df by MMSI and TIMESTAMP
= vessels_geom.sort_values(by = ["MMSI", "TIMESTAMP"]) vessels_geom
# 3) created a copy of our df and shifted down a row then joined back with original dataset
= vessels_geom.shift(1)
vs_geom_copy
# joined back with original dataset
= vs_geom_copy.join(vessels_geom,
vessels_joined = "left",
how = 'start',
lsuffix = 'end') rsuffix
# 4) Dropped rows where the MMSI did not match
= vessels_joined[vessels_joined["MMSIstart"] == vessels_joined["MMSIend"]] vessels_joined_filt
# 5) Set the geometry column
= vessels_joined_filt.set_geometry("geometrystart") vessels_joined_filt
# 1) Distance between observations
'distance'] = vessels_joined_filt['geometrystart'].distance(vessels_joined_filt['geometryend']) vessels_joined_filt[
# 2) Time difference between observations
'time_diff'] = vessels_joined_filt['TIMESTAMPend'] - vessels_joined_filt['TIMESTAMPstart'] vessels_joined_filt[
# 3) The average speed between each observation
'avg_speed_m_s'] = vessels_joined_filt['distance']/vessels_joined_filt['time_diff'].dt.total_seconds() vessels_joined_filt[
# 4) Time that the distance would have taken at 10 knots.
# 10 knot = 5.1444 m/s
= 5.1444
kts_10 'time_10kts_tot_s'] = (vessels_joined_filt['distance'] / kts_10) vessels_joined_filt[
# 5) Difference between the time that it actually took and time it would have taken at 10 knots
'time_10kts_diff_hr'] = (vessels_joined_filt['time_10kts_tot_s'] - vessels_joined_filt['time_diff'].dt.total_seconds()) / (60 *60)
vessels_joined_filt[
# Below is what the head() of the data looks like after adding the columns from these calculations
vessels_joined_filt.head()
field_1start | MMSIstart | LONstart | LATstart | TIMESTAMPstart | geometrystart | index_rightstart | field_1end | MMSIend | LONend | LATend | TIMESTAMPend | geometryend | index_rightend | distance | time_diff | avg_speed_m_s | time_10kts_tot_s | time_10kts_diff_hr | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
235018 | 235025 | 203106200 | -61.40929 | 15.21021 | 2015-02-25 15:32:20 | POINT (462476.396 1680935.224) | 0.0 | 235018 | 203106200 | -61.41107 | 15.21436 | 2015-02-25 15:34:50 | POINT (462283.995 1681393.698) | 0 | 497.209041 | 0 days 00:02:30 | 3.314727 | 96.650541 | -0.014819 |
235000 | 235018 | 203106200 | -61.41107 | 15.21436 | 2015-02-25 15:34:50 | POINT (462283.995 1681393.698) | 0.0 | 235000 | 203106200 | -61.41427 | 15.22638 | 2015-02-25 15:42:19 | POINT (461936.769 1682722.187) | 0 | 1373.116137 | 0 days 00:07:29 | 3.058165 | 266.914730 | -0.050579 |
234989 | 235000 | 203106200 | -61.41427 | 15.22638 | 2015-02-25 15:42:19 | POINT (461936.769 1682722.187) | 0.0 | 234989 | 203106200 | -61.41553 | 15.2353 | 2015-02-25 15:47:19 | POINT (461798.818 1683708.377) | 0 | 995.792381 | 0 days 00:05:00 | 3.319308 | 193.568226 | -0.029564 |
234984 | 234989 | 203106200 | -61.41553 | 15.2353 | 2015-02-25 15:47:19 | POINT (461798.818 1683708.377) | 0.0 | 234984 | 203106200 | -61.41687 | 15.23792 | 2015-02-25 15:49:50 | POINT (461654.150 1683997.765) | 0 | 323.533223 | 0 days 00:02:31 | 2.142604 | 62.890371 | -0.024475 |
234972 | 234984 | 203106200 | -61.41687 | 15.23792 | 2015-02-25 15:49:50 | POINT (461654.150 1683997.765) | 0.0 | 234972 | 203106200 | -61.41851 | 15.24147 | 2015-02-25 15:54:49 | POINT (461476.997 1684389.925) | 0 | 430.317090 | 0 days 00:04:59 | 1.439188 | 83.647673 | -0.059820 |
# 6) Final calculation
= vessels_joined_filt[vessels_joined_filt['time_10kts_diff_hr'] > 0]
increased_time print("The increased travel time for the ship traffic is ",
round((increased_time['time_10kts_diff_hr'].sum())/24, 2),
" days.")
The increased travel time for the ship traffic is 27.88 days.
For attribution, please cite this work as
DeCesaro (2021, Nov. 16). Joe DeCesaro: Protecting Whales from Ships. Retrieved from https://joedecesaro.github.io/posts/2022-05-08-protecting-whales-from-ships/
BibTeX citation
@misc{decesaro2021protecting, author = {DeCesaro, Joe}, title = {Joe DeCesaro: Protecting Whales from Ships}, url = {https://joedecesaro.github.io/posts/2022-05-08-protecting-whales-from-ships/}, year = {2021} }