import matplotlib.pyplot as plt
import numpy as np
from scipy.special import i0
= np.linspace(-np.pi, np.pi, 200)
x
def von_misses(kappa):
= 0
mu = np.exp(kappa*np.cos(x-mu))/(2*np.pi*i0(kappa))
y return y
plt.figure()
for i in [0.5, 1, 2, 5, 10, 20]:
=f'$\kappa = {i}$')
plt.plot(x, von_misses(i), label
plt.legend()min(), x.max())
plt.xlim(x. plt.show()
6 Ichthyop processes
In this section, the different Ichthyop processes are described. These processes are implemented in the classes within the action
folder.
Parameters associated with these processes must be included in action
blocks. They can be activated or deactivated by setting the enabled
tag (cf. Section 2.1).
6.1 Growth
There is 2 different implementations of the growth module. They can be selected by setting the class_name
parameter in the action.growth
action configuration block.
6.1.1 Linear growth
To use the linear growth method described in Lett et al. (2008), choose the LinearGrowthAction.java
class.
Length increment is provided as follows:
\[ \Delta L = C_1 + C_2 \times \dfrac{F}{F + K_S} \times max(T, T_{thres}) \times \Delta t \]
where \(\Delta t\) is the time-step (in days), \(C_1\) and \(C_2\) are parameters (coeff1
and coeff2
), \(T_{thres}\) is a temperature threshold (threshold_temp
parameter), \(F\) is the food quantity and \(K_S\) is a half-saturation constant (half_saturation
parameter). If the latter is not defined or equals 0, \(Q\) is assumed to be \(1\).
The name of the food and temperature variables are provided by the food_field
and temperature_field
parameters.
6.1.2 Sole growth
If the {samp}SoleGrowthAction.java
class is selected, the growth model used in Tanner et al. (2017) is used. It relies on the growth equation from Fonds (1979) and given by:
\[ \Delta L = C_1 \times T^{C_2} \times \Delta t \]
with \(L\) the length, \(T\) the temperature and \(\Delta t\) the time step in days, and \(C_1\) and \(C_2\) are parameters (c1
and c2
respectively).
The temperature field is provided by the temperature_field
parameter.
6.2 Lethal temperature and salinity
In Ichthyop, there is the possibility to define a range of temperature and salinity beyond which the particle is killed.
6.2.1 Lethal temperature
The functionning og the lethal temperature module depends on whether the growth module is enabled or not.
Growth disabled
lethal temperature can either be provided in a CSV file (lethal_temp_file
parameter), which provides the lower and upper temperature values that can be supported by the particle as a function of age (in hours). The file must have the following format:
Time(hour);Cold temperature (C);Warm temperature (C)
0;14;22
48;13;22
96;12;22
In this case, three age classes will be considered: \([0, 48[\), \([48, 96[\) and \([96, \infty[\)
If the lethal_temp_file
parameter is not defined, single values will be used independently of the age. These values are provided in the cold_lethal_salinity_egg
and warm_lethal_salinity_egg
parameters.
Note that the temperature_field
parameter, providing the name of the temperature variable, must also be provided.
Growth enabled
If the growth module is enabled, two cold and warm lethal temperatures must be provided. One for eggs (cold_lethal_temperature_egg
and hot_lethal_temperature_egg
), one for larva (cold_lethal_temperature_larva
and hot_lethal_temperature_larva
).
The stage (egg
or larva
) is determined by the growth module, and the right temperature range is applied to the particle.
6.2.2 Lethal salinity
Lethal salinity can either be provided in a CSV file (lethal_salt_file
parameter), which provides the lower and upper salinity values that can be supported by the particle as a function of age (in hours). The file must have the following format:
Time(hour);Fresh salinity (PSU);Salty salinity (PSU)
0;35;40
48;30;40
96;30;45
In this case, three age classes will be considered: \([0, 48[\), \([48, 96[\) and \([96, \infty[\)
If the lethal_salt_file
parameter is not defined, single values will be used independently of the age. These values are provided in the fresh_lethal_salinity_egg
and saline_lethal_salinity_egg
parameters.
Note that the salinity_field_
parameter, providing the name of the salinity variable, must also be provided.
6.3 Buoyancy
The buoyancy module allow to vertically displace a particle, depending on it’s density and on the sea water density, following Parada et al. (2003).
The buoyancy-induced vertical velocity of the particle is given by:
\[ W_{buoy} = \dfrac{1}{24} \times g \times a \times b \times \dfrac{\rho_{water} - \rho_{part}}{\rho_{water}} \mu^{-1} \log\left(2 \dfrac{a}{b} + 0.5\right); \]
with \(a\) and \(b\) the semi-major axis and semi-minor axis of an ellipse (mean_major_axis
and mean_minor_axis
parameters), \(\mu\) the molecular viscosity (molecular_viscosity
parameter), \(\rho_{water}\) the water density, \(\rho_{part}\) the particle density and \(g\) the gravitational acceleration (\(cm.s^{-2}\)).
If the particle density varies with age, it can be provided in a CSV file (density_file
parameter), formatted as follows:
Age(hour);Density (g/cm3)
0;1.0235625
24;1.02374
48;1.023335
60;1.0239
66;1.025672
If the density_file
parameter is not found, a constant density will be assumed (particle_density
parameter).
The buoyancy process is only applied for the early life stages. If the growth process is disabled, it is controlled by the age_max
parameter (provided in days). If this parameter is not provided, the buoyancy process will always be applied.
If the growth process is enabled, the application of the buoyancy module will be automatically managed.
6.4 Daily vertical migration
The MigrationAction.java
manages the daily migration of the particles. It is done by providing daytime and nighttime depths, and the timing of the sunset and sunrise.
If the daytime_depth_file
parameter is defined, it provides the depth of the particle during daytime. It is formatted as follows:
Age (day);Depth (m)
0.0;-20
3.0;-25
5.0;-30
8.0;-35
If this parameter is not found, a constant daytime depth, provided by the daytime_depth
parameter, is assumed.
Same thing for the nighttime depths, which can be set by either the nighttime_depth_file
or the nighttime_depth
parameters.
The sunset and sunrise hours are set by the sunset
and sunrise
parameters, which must have a HH::mm
format.
If the growth module is deactivated, the user must provide the minimum age (in days) at which the particle starts to migrate (age_min
parameter). If the growth module is activated, it manages the activation or deactivation of the daily migration.
When the target depth is greater than the total depth, the particle does not move.
6.5 Ontogenetic vertical migration
The ontogenetic migration module controls the migration to different habitats as a function of age. Its implementation in Ichthyop follows the CMS one. It uses the CMS configuration file (cms_ovm_config_file
parameter), which is formatted as follows:
nTime
nDepth
z1 z2 z3 z4 z5 z6 ... znDepth
t1 t2 t3 t4 t5 t6 ... tnTime
P1,1 P1,2 ... P1,nTime
P2,1 P2,2 ... P2,nTime
P3,1 P3,2 ... P3,nTime
...
PnDepth,1 PnDepth,2 ... PnDepth,nTime
The first line provides the number of time steps in the CMS file, the second line provides the number of vertical levels in the CMS file. The third line provides the depth values and the fourth lines provides the time step values.
The remaining lines provide the probability matrix \(P_{z, t}\).
The sum of the probability matrix along the depth dimension should equal 100.
At each time step, the index of the CMS time step, \(k\), is determined by comparing the simulation and CMS times as follows:
\[ t_{CMS}(k) < t \leq t_{CMS}(k + 1) \]
When the CMS time index changes, all the particles are randomly distributed on the vertical, following the probability distribution of the given CMS time. If the CMS time index remains unchanged, nothing is done.
6.6 Wind drift
The wind-drift is implemented in the WindDriftFileAction.java
class. This class requires that 2D (time, latitude, longitude) wind fields are provided. This is done by providing a input_path
and a file_filter
parameter, which specify the location and names of the wind files.
The user also provides field_time
, wind_u
, wind_v
, longitude
, latitude
parameters, which specify the names of the time, zonal wind, meridional wind, longitude and latitude variables in the NetCDF files.
The user also provides a depth_application
parameter, which specifies the depth at which the wind will impact the trajectories (only valid for 3D simulations), a wind_factor
(\(F\), multiplication factor), an angle
(\(\theta\)) and a wind_convention
parameter (\(\varepsilon\), +1 if convention is ocean-based, i.e. wind-to, -1 if convention is atmospheric based, i.e. wind-from).
The changes in particle longitude (\(\lambda\)) and latitude (\(\phi\)) is provided as follows:
\[ \Delta \lambda_0 = \frac{\Delta t \times U_W }{\frac{R \pi}{180} \times \cos(\frac{\pi \phi}{ 180})} \]
\[ \Delta \phi_0 = \frac{\Delta t \times V_W}{ \frac{R \pi}{180}} \]
\[ \Delta \lambda = \varepsilon \times F \times \left(\Delta \lambda_0 \times \cos\left(\frac{\theta \pi}{180}\right) - \Delta \phi_0 \times \sin\left(\frac{\theta \pi}{180}\right)\right) \]
\[ \Delta \phi = \varepsilon \times F \times \left(\Delta \lambda_0 \times \sin\left(\frac{\theta \pi}{180}\right) + \Delta \phi_0 \times \sin\left(\frac{\theta \pi}{180}\right)\right) \]
\(R\) is the Earth Radius and equals 6367.74 km. In the code, \(\dfrac{R \pi}{180}\), which is the distance in m of a 1 degree cell, is approximated to 111138 m
6.7 Random swimming
Ichthyop allows particles to randomly swim. This is achieved by using the SwimmingAction.java
. The velocity may vary with the age of the particle.
The user provides a CSV absolute velocity file (velocity_file
parameter), which must me formatted as follows:
Age (days);Speed (m/s)
0;0.1
5;0.2
15;0.3
The user also provides a boolean parameter (constant_velocity
) that specifies whether the velocity should remain as defined in the CSV file, or if the absolute velocity should be randomly selected as follows:
\[ \|U\| = \|U\|_{file} \times \kappa \]
with \(\kappa\) a random value in the \([0, 2]\) interval and \(\|U\|_{file}\) the velocity defined in the CSV file.
At each time-step, the zonal and meridonal velocities are defined as follows:
\[ U = \kappa' \varepsilon \|U\| \]
\[ V = \varepsilon' \sqrt{\|U\|^2 - U^2} \]
with \(\kappa'\) a random value between 0 and 1, and \(\varepsilon\) and \(\varepsilon'\) random values either equal to 1 or -1.
6.8 Wave drift
Ichthyop can take into account the effects of waves on the particles trajectories, following the Stokes drift equations of Stokes (2009):
\[ U_{S} = w \times k \times a^2 \times \exp\left(2 \times k \times z\right) \]
In Ichthyop, the user provides zonal and meridional wave stokes drift and the wave periods. The horizontal displacement of particles is computed as follows:
\[ \|U_{wave}\| = U_{wave}^2 + V_{wave}^2 \]
\[ \lambda_{wave} = \|U_{wave}\| \times T_{wave} \]
\[ k_{wave} = \dfrac{2 \pi}{\lambda_{wave}} \]
\[ \Delta X = F \times U_{cur} \times \Delta t \times \exp\left(2 \times k_{wave} \times z\right) \]
\[ \Delta Y = F \times V_{cur} \times \Delta t \times \exp\left(2 \times k_{wave} \times z\right) \]
with \(U_{wave}\) and \(V_{wave}\) the zonal and meridional stokes components, \(T_{wave}\) the wave period, \(U_{cur}\) and \(V_{cur}\) the zonal and meridional ocean current components, \(z\) the depth and \(F\) a multiplication factor provided by the user.
6.9 Coastal behaviour
6.9.1 Bouncing
In the bouncing mode, the particle will bounce on the coast. First, whether the bouncing occurs on a meridional or a zonal coastline is determined.
In case of a meridional coastline, as shown in Figure 6.1, the calculation of the new position is performed as follows.
Let’s assume that the particle is at the position \((x, y)\) and is moved at the postion \((x + \Delta x, y + \Delta y)\). We suppose that
\[ \Delta y = \Delta y_1 + \Delta y_2, \tag{6.1}\]
where \(\Delta y_1\) is the meridional distance between the particle and the coastline, and \(\Delta y_2\) is the distance that the particle will spend on land.
In the bouncing mode, the position increment can be written as
\[ \Delta_{cor} y = \Delta y_1 - \Delta y_2 \tag{6.2}\]
By replacing \(\Delta y_2\) using Equation 6.1, we can write:
\[ \Delta_{cor} y = \Delta y_1 - (\Delta y - \Delta y_1) \]
\[ \Delta_{cor} y = 2 \Delta y_1 - \Delta y \]
6.10 Orientation
Active swimming has been implemented in Ichthyop following the work of Romain Chaput. Three active swimming behaviours have been implemented: the rheotaxis orientation (i.e. against the current), the cardinal orientation (i.e toward a given direction) and the reef orientation, i.e. orientation toward points of interests.
These three implementations involve the computation of a swimming velocity and direction. The former is common to all three methods, but the directions depend on the method considered.
6.10.1 Swimming velocity
The orientation processes all share common features. They both depend on a swimming velocity and random directions. The methods described below differ on the way the random directions are drafted.
Swimming velocity is computed following Staaterman, Paris, and Helgers (2012):
#|echo: false \[ V = V_{hatch} + (V_{settle} - V_{hatch}) ^ {\log(age) / \log(PLD)} \]
with \(V_{hatch}\) and \(V_{settle}\) the larval velocity at hatching and settle, \(A\) the age of the larva and \(PLD\) the transport duraction.
6.10.2 Von Mises distributions
Von Mises distribution is used in all three methods.The Von Mises distribution is given by:
\[ f(\theta, \mu, \kappa) = \dfrac {\exp(\kappa \cos(\theta - \mu))} {2 \pi I_{0}(\kappa)} \]
where \(I_{0}(\kappa)\) is the modified Bessel function of the first kind of order 0, \(\mu\) is angle where the distribution is centerred and \(\kappa\) is the concentration parameter. The distribution is as follows:
For computation purposes, all the Von Mises drafts performed in Ichthyop are done by using \(mu = 0\). The angles are thus centerred around 0. Then, the \(mu\) value is added.
\(\theta = 0\) is eastward, \(\theta = \frac{\pi}{2}\) is northward, etc.
6.10.3 Computation of displacement
Given a swimming velocity \(V\) and a direction \(\theta\), the larva displacement (in \(m\)) is computed as follows:
\[ \Delta X = V \times \cos(\theta) \times \Delta t \]
\[ \Delta Y = V \times \sin(\theta) \times \Delta t \]
with \(\Delta t\) the time step. Next, the corresponding change in longitude (\(\lambda\)) and latitude (\(\varphi\)) is computed as follows:
\[ \Delta \lambda = \dfrac{\Delta X}{111138 \times \cos{\varphi}} \]
\[ \Delta \varphi = \dfrac{\Delta Y}{111138 } \]
6.10.4 Cardinal orientation
In cardinal orientation, the user provides a fixed heading \(\theta_{card}\) and a fixed \(\kappa\) parameter. Then, at each time step, a new angle is randomly drafted following a Von Misses distribution \(f(\theta, \theta_{card}, \kappa)\).
import xarray as xr
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os
from glob import glob
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
= 'card'
config
if os.path.isdir('ichthyop_output'):
= os.path.join('ichthyop_output', config, '*nc')
pattern else :
= os.path.join('..', '..', 'ichthyop_output', config, '*nc')
pattern
pattern
= glob(pattern)
filelist
filelist
= xr.open_mfdataset(filelist, decode_times=False)
data
data
# +
= data['lon'].values
lon = data['lat'].values
lat = data['mortality'].values
mort
= np.ma.masked_where(mort > 0, lon)
lon = np.ma.masked_where(mort > 0, lat)
lat = lon.shape
ntime, ndrifters
# +
= np.arange(ntime)
time = np.arange(ndrifters)
drifters
= np.meshgrid(drifters, time)
d2d, t2d
# +
plt.figure()
= ccrs.PlateCarree()
projin = ccrs.PlateCarree()
projout
= plt.axes(projection = projout)
ax =t2d, marker='.', transform=projin, s=0.5, cmap=mpl.colormaps['jet'])
ax.scatter(lon[:, :], lat[:, :], c= ax.add_feature(cfeature.LAND)
feat = ax.add_feature(cfeature.COASTLINE)
feat 49, 55.25, -13.13, -9.45], crs=projin) ax.set_extent([
6.10.5 Rheotaxis orientation
In the rheotaxis orientation method, the particles swim against the current. The user only provides a kappa parameter.
First, the angle of the current is computed as follows:
\[ \theta_{current} = \arctan2(V_{current}, U_{current}) \]
Then, the angle that the particle must follow is given by adding \(\pi\):
\[ \theta_{direction} = \theta_{current} + \pi \]
Finally, a random angle is picked up following a Von Mises distribution \(f(\theta, \theta_{direction}, \kappa_{reef})\)
(ref-rheo)=
import xarray as xr
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os
from glob import glob
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
= 'rheo'
config
if os.path.isdir('ichthyop_output'):
= os.path.join('ichthyop_output', config, '*nc')
pattern else :
= os.path.join('..', '..', 'ichthyop_output', config, '*nc')
pattern
pattern
= glob(pattern)
filelist
filelist
= xr.open_mfdataset(filelist, decode_times=False)
data
data
# +
= data['lon'].values
lon = data['lat'].values
lat = data['mortality'].values
mort
= np.ma.masked_where(mort > 0, lon)
lon = np.ma.masked_where(mort > 0, lat)
lat = lon.shape
ntime, ndrifters
# +
= np.arange(ntime)
time = np.arange(ndrifters)
drifters
= np.meshgrid(drifters, time)
d2d, t2d
# +
plt.figure()
= ccrs.PlateCarree()
projin = ccrs.PlateCarree()
projout
= plt.axes(projection = projout)
ax =t2d, marker='.', transform=projin, s=0.5, cmap=mpl.colormaps['jet'])
ax.scatter(lon[:, :], lat[:, :], c= ax.add_feature(cfeature.LAND)
feat = ax.add_feature(cfeature.COASTLINE)
feat 49, 55.25, -13.13, -9.45], crs=projin) ax.set_extent([
6.10.6 Reef orientation
In the reef orientation method, the larva will target the closest target area (for instance coral reef). These areas are defined in an XML
zone file by a polygon and a zone-specific \(\kappa\) parameter. The user also provides the sensory detection threshold of the larva (maximum detection distance \(\beta\)).
If the distance between the particle and the barycenter of the closest reef (\(D\)) is below the detection thereshold \(\beta\), the larva will swim in the direction of the reef.
(ref-orientation)=
import numpy as np
import matplotlib.pyplot as plt
'font.size'] = 15
plt.rcParams[
= (10, 10))
plt.figure(figsize
= 0
xnew = 0
ynew
#draw point at orgin
= 'red', marker = 'o')
plt.plot(xnew, ynew, color '$P_{t}$', xy=(0 + 0.05, 0 - 0.07), xycoords='data', color='red')
plt.gca().annotate(
#draw circle
= 1.5
r = np.linspace(0 * np.pi, 2 * np.pi, 100 )
angles = r * np.cos(angles)
xs = r * np.sin(angles)
ys = 'k', ls='--', lw=0.5)
plt.plot(xs, ys, color
= np.pi / 6.
angle_old = 1.
rold = rold * np.cos(angle_old)
xold = rold * np.sin(angle_old)
yold
='o', color='blue')
plt.plot(xold, yold, marker'$P_{t - 1}$', xy=(xold + 0.05, yold), xycoords='data', color='blue')
plt.gca().annotate(
= np.pi + np.pi / 3.
angle_reef print(np.rad2deg(angle_reef))
= 1
rreef = rreef * np.cos(angle_reef)
xreef = rreef * np.sin(angle_reef)
yreef print(yreef)
='o', color='orange')
plt.plot(xreef, yreef, marker'$R$', xy=(xreef + 0.1, yreef), xycoords='data', color='orange')
plt.gca().annotate(
r'$D$', xy=(0.5*(xreef) + 0.02, 0.5*yreef), xycoords='data', color='orange', ha='left')
plt.gca().annotate(
='k', ls='--', lw=0.5)
plt.axvline(xnew, color='k', ls='--', lw=0.5)
plt.axhline(ynew, color
= np.polyfit([xold, xnew], [yold, ynew], deg=1)
p = np.linspace(xold, xold -2, 100)
xtemp ='black', ls='--')
plt.plot(xtemp, np.polyval(p, xtemp), color
= angle_old + np.pi
angle_current = np.linspace(0, angle_current, 100)
tmp_angle = 0.2
rtmp 0 + rtmp * np.cos(tmp_angle), 0 + rtmp * np.sin(tmp_angle), color='k', ls='--')
plt.plot(r'$\theta_{actual}$', xy=(xnew + 0.2, ynew+0.05), xycoords='data', color='k')
plt.gca().annotate(
='orange', ls='--')
plt.plot([xnew, xreef], [ynew, yreef], color= angle_reef
angle_current = np.linspace(0, angle_current, 100)
tmp_angle = 0.3
rtmp 0 + rtmp * np.cos(tmp_angle), 0 + rtmp * np.sin(tmp_angle), color='orange', ls='--')
plt.plot(r'$\theta_{reef}$', xy=(xnew - 0.3, ynew+0.1), xycoords='data', color='orange',ha='right')
plt.gca().annotate(
= angle_old + np.pi
angle_current = np.linspace(angle_current, angle_reef, 100)
tmp_angle = 0.4
rtmp 0 + rtmp * np.cos(tmp_angle), 0 + rtmp * np.sin(tmp_angle), color='plum', ls='--')
plt.plot(r'$\theta_{turning}$', xy=(-0.43, -0.34), xycoords='data', color='plum')
plt.gca().annotate(
= 0.05
off - off, xold + off)
plt.xlim(xreef - off, yold + off)
plt.ylim(yreef 'equal')
plt.gca().set_aspect('off')
plt.axis( plt.show()
239.99999999999997
-0.8660254037844384
First, the angle of the current trajectory, \(\theta_{actual}\), is computed by using the particle position at the previous time step (blue point) and the actual position (red point).
\[ \Delta_X = (X_{t - 1} - X_{t}) \]
\[ \Delta_Y = (Y_{t - 1} - Y_{t}) \]
\[ \theta_{actual} = \arctan2(\Delta Y, \Delta X) + \pi \]
The direction toward the reef, \(\theta_{reef}\) is also computed.
\[ \Delta_X = (X_{reef} - X_{t}) \]
\[ \Delta_Y = (Y_{reef} - Y_{t}) \]
\[ \theta_{reef} = \arctan2(\Delta Y, \Delta X) \]
The angles are computed in the \((X, Y)\) space. Therefore, longitude and latitude coordinates are converted in \((X, Y)\) using the latlon2xy
Dataset methods.
The turning angle \(\theta_{turning}\) is given by:
\[ \theta_{turning} = \theta_{reef} - \theta_{actual} \]
The turning angle is then ponderated by the ratio of the distance from the reef to the detection threshold as follows:
\[ \theta_{ponderated} = \left(1 - \dfrac{D}{\beta}\right) \theta_{turning} \]
\[ \theta_{ponderated} = \left(1 - \dfrac{D}{\beta}\right) \left(\theta_{reef} - \theta_{actual}\right) \]
Therefore, the closest to the reef, the strongest the turning angle.
Then, a random angle is picked up following a Von Mises distribution \(f(\theta, \theta_{ponderated}, \kappa_{reef})\)
An example of a trajectory is provided below. In this case, two target destinations are provided (black boxes). The same \(\kappa\) value was used for both ares (1.2) and the \(\beta\) parameter has been set equal to 3 km.
(ref-orientation-2)=
import xarray as xr
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os
from glob import glob
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
= 'reef'
config
if os.path.isdir('ichthyop_output'):
= os.path.join('ichthyop_output', config, '*nc')
pattern else :
= os.path.join('..', '..', 'ichthyop_output', config, '*nc')
pattern
pattern
= glob(pattern)
filelist
filelist
= xr.open_mfdataset(filelist, decode_times=False)
data
data
# +
= data['lon'].values
lon = data['lat'].values
lat = data['mortality'].values
mort
= np.ma.masked_where(mort > 0, lon)
lon = np.ma.masked_where(mort > 0, lat)
lat = lon.shape
ntime, ndrifters
# +
= np.arange(ntime)
time = np.arange(ndrifters)
drifters
= np.meshgrid(drifters, time)
d2d, t2d
# +
plt.figure()
= ccrs.PlateCarree()
projin = ccrs.PlateCarree()
projout
= plt.axes(projection = projout)
ax =t2d, marker='.', transform=projin, s=0.5, cmap=mpl.colormaps['jet'])
ax.scatter(lon[:, :], lat[:, :], c= ax.add_feature(cfeature.LAND)
feat = ax.add_feature(cfeature.COASTLINE)
feat = np.array([51.625, 52.625, 52.625,51.625, 51.625])
xp = np.array([-1.179878E1, -1.179878E1, -1.079878E1, -1.079878E1, -1.179878E1])
yp =projin, color='k', ls='--')
ax.plot(xp, yp, transform= np.array([51.625, 52.625 - 0.5, 52.625 - 0.5,51.625, 51.625]) - 1
xp = np.array([-1.179878E1 + 0.5, -1.179878E1 + 0.5, -1.079878E1, -1.079878E1, -1.179878E1 + 0.5]) - 0.25
yp =projin, color='k', ls='--')
ax.plot(xp, yp, transform49, 55.25, -13.13, -9.45], crs=projin) ax.set_extent([