def create_parcelsfig_axis(spherical, land=True, projection=None, central_longitude=0): try: import matplotlib.pyplot as plt except: logger.info("Visualisation is not possible. Matplotlib not found.") return None, None, None, None # creating axes was not possible if projection is not None and not spherical: raise RuntimeError('projection not accepted when Field doesn''t have geographic coordinates') if spherical: try: import cartopy except: logger.info("Visualisation of field with geographic coordinates is not possible. Cartopy not found.") return None, None, None, None # creating axes was not possible projection = cartopy.crs.PlateCarree(central_longitude) if projection is None else projection fig, ax = plt.subplots(1, 1, subplot_kw={'projection': projection}) try: # gridlines not supported for all projections gl = ax.gridlines(crs=projection, draw_labels=True) gl.xlabels_top, gl.ylabels_right = (False, False) gl.xformatter = cartopy.mpl.gridliner.LONGITUDE_FORMATTER gl.yformatter = cartopy.mpl.gridliner.LATITUDE_FORMATTER except: pass if land: ax.coastlines() else: cartopy = None fig, ax = plt.subplots(1, 1) ax.grid() return plt, fig, ax, cartopy
def lib(self, compiler=GNUCompiler()): if self._lib is None: with open(self.src_file, 'w') as f: f.write(self.ccode) compiler.compile(self.src_file, self.lib_file, self.log_file) logger.info("Compiled %s ==> %s" % ("random", self.lib_file)) self._lib = npct.load_library(self.lib_file, '.') return self._lib
def __init__(self, fieldset, ptype, pyfunc=None, funcname=None, funccode=None, py_ast=None, funcvars=None): self.fieldset = fieldset self.ptype = ptype # Derive meta information from pyfunc, if not given self.funcname = funcname or pyfunc.__name__ if pyfunc is AdvectionRK4_3D: logger.info('Note that positive vertical velocity is assumed DOWNWARD by AdvectionRK4_3D') if funcvars is not None: self.funcvars = funcvars elif hasattr(pyfunc, '__code__'): self.funcvars = list(pyfunc.__code__.co_varnames) else: self.funcvars = None self.funccode = funccode or inspect.getsource(pyfunc.__code__) # Parse AST if it is not provided explicitly self.py_ast = py_ast or parse(fix_indentation(self.funccode)).body[0] if pyfunc is None: # Extract user context by inspecting the call stack stack = inspect.stack() try: user_ctx = stack[-1][0].f_globals user_ctx['math'] = globals()['math'] user_ctx['random'] = globals()['random'] user_ctx['ErrorCode'] = globals()['ErrorCode'] except: logger.warning("Could not access user context when merging kernels") user_ctx = globals() finally: del stack # Remove cyclic references # Compile and generate Python function from AST py_mod = Module(body=[self.py_ast]) exec(compile(py_mod, "<ast>", "exec"), user_ctx) self.pyfunc = user_ctx[self.funcname] else: self.pyfunc = pyfunc self.name = "%s%s" % (ptype.name, self.funcname) # Generate the kernel function and add the outer loop if self.ptype.uses_jit: kernelgen = KernelGenerator(fieldset, ptype) self.field_args = kernelgen.field_args kernel_ccode = kernelgen.generate(deepcopy(self.py_ast), self.funcvars) self.field_args = kernelgen.field_args self.const_args = kernelgen.const_args loopgen = LoopGenerator(fieldset, ptype) self.ccode = loopgen.generate(self.funcname, self.field_args, self.const_args, kernel_ccode) basename = path.join(get_cache_dir(), self._cache_key) self.src_file = "%s.c" % basename self.lib_file = "%s.%s" % (basename, 'dll' if platform == 'win32' else 'so') self.log_file = "%s.log" % basename self._lib = None
def write(self, filename): """Write FieldSet to NetCDF file using NEMO convention :param filename: Basename of the output fileset""" logger.info("Generating NEMO FieldSet output with basename: %s" % filename) self.U.write(filename, varname='vozocrtx') self.V.write(filename, varname='vomecrty') for v in self.fields: if (v.name is not 'U') and (v.name is not 'V'): v.write(filename)
def compile(self, compiler): """ Writes kernel code to file and compiles it.""" with open(self.src_file, 'w') as f: f.write(self.ccode) compiler.compile(self.src_file, self.lib_file, self.log_file) logger.info("Compiled %s ==> %s" % (self.name, self.lib_file))
def show(self, with_particles=False, animation=False, show_time=0, vmin=None, vmax=None): """Method to 'show' a :class:`Field` using matplotlib :param with_particles: Boolean whether particles are also plotted on Field :param animation: Boolean whether result is a single plot, or an animation :param show_time: Time at which to show the Field (only in single-plot mode) :param vmin: minimum colour scale (only in single-plot mode) :param vmax: maximum colour scale (only in single-plot mode) """ try: import matplotlib.pyplot as plt import matplotlib.animation as animation_plt from matplotlib import rc except: logger.info("Visualisation is not possible. Matplotlib not found.") return if with_particles or (not animation): idx = self.time_index(show_time) if self.time.size > 1: data = np.squeeze( self.temporal_interpolate_fullfield(idx, show_time)) else: data = np.squeeze(self.data) vmin = data.min() if vmin is None else vmin vmax = data.max() if vmax is None else vmax cs = plt.contourf(self.lon, self.lat, data, levels=np.linspace(vmin, vmax, 256)) cs.cmap.set_over('k') cs.cmap.set_under('w') cs.set_clim(vmin, vmax) plt.colorbar(cs) if not with_particles: plt.show() else: fig = plt.figure() ax = plt.axes(xlim=(self.lon[0], self.lon[-1]), ylim=(self.lat[0], self.lat[-1])) def animate(i): data = np.squeeze(self.data[i, :, :]) cont = ax.contourf(self.lon, self.lat, data, levels=np.linspace(data.min(), data.max(), 256)) return cont rc('animation', html='html5') anim = animation_plt.FuncAnimation(fig, animate, frames=np.arange( 1, self.data.shape[0]), interval=100, blit=False) plt.close() return anim
def compute_curvilinearGrid_rotationAngles(mesh_filename, rotation_angles_filename, variables=None, dimensions=None): """Function that computes and writes in a netcdf file the rotation angles for vector fields written in curvilinear C grids to zonal/meridional directions. It follows the NEMO standards. The angles are not directly computed since it is unnecessary and more expensive, but the cosine and sine of the angles are given for both the U and the V grid. Two important comments must be pointed out: * The rotation file is only computed if it does not exist or if it is older than the mesh file. Otherwise, this function will be skipped, even if variables and dimensions arguments are modified. * Since the rotation angles for a node are computed using the position of the node and its neighbouring nodes, the grid of the rotation angles is smaller than the original mesh grid. First row and line of mesh grid do not exist in rotation file. :param mesh_filename: path to the mesh file which contains the coordinates of the U, V and F grids :param rotation_angles_filename: path of the rotation angles file to write :param variables: optional dictionary of the names for the `cosU`, `sinU`, `cosV` and `sinV` variables in the rotation ncfile. :param dimensions: optional dictionary of dictionaries. The main dictionary contains the keys `U`, `V` and `F`. In each subdictionary, the keys `lon` and `lat` give the name of the dimensions in the mesh ncfile. """ if path.isfile(rotation_angles_filename) and path.getmtime( rotation_angles_filename) > path.getmtime(mesh_filename): logger.info( "file '%s' not generated since it is newer than '%s'.\n If you want to re-generate it, please remove existing file first." % (rotation_angles_filename, mesh_filename)) return logger.info("Generating rotation angles fields in file: %s" % rotation_angles_filename) if variables is None: variables = { 'cosU': 'cosU', 'sinU': 'sinU', 'cosV': 'cosV', 'sinV': 'sinV' } if dimensions is None: dimensions = { 'U': { 'lon': 'glamu', 'lat': 'gphiu' }, 'V': { 'lon': 'glamv', 'lat': 'gphiv' }, 'F': { 'lon': 'glamf', 'lat': 'gphif' } } dataset = xr.open_dataset(mesh_filename, decode_times=False) lonU = np.squeeze(getattr(dataset, dimensions['U']['lon']).values) latU = np.squeeze(getattr(dataset, dimensions['U']['lat']).values) lonV = np.squeeze(getattr(dataset, dimensions['V']['lon']).values) latV = np.squeeze(getattr(dataset, dimensions['V']['lat']).values) lonF = np.squeeze(getattr(dataset, dimensions['F']['lon']).values) latF = np.squeeze(getattr(dataset, dimensions['F']['lat']).values) dataset.close() rad = np.pi / 180. rpi = np.pi # The following code is the direct python transcription of the Fortran code of NEMO. # http://forge.ipsl.jussieu.fr/nemo/browser/branches/2015/nemo_v3_6_STABLE/NEMOGCM/NEMO/OPA_SRC/SBC/geo2ocean.F90 zxnpu = -2. * np.cos( rad * lonU[1:, 1:]) * np.tan(rpi / 4. - rad * latU[1:, 1:] / 2.) zynpu = -2. * np.sin( rad * lonU[1:, 1:]) * np.tan(rpi / 4. - rad * latU[1:, 1:] / 2.) znnpu = zxnpu * zxnpu + zynpu * zynpu zxnpv = -2. * np.cos( rad * lonV[1:, 1:]) * np.tan(rpi / 4. - rad * latV[1:, 1:] / 2.) zynpv = -2. * np.sin( rad * lonV[1:, 1:]) * np.tan(rpi / 4. - rad * latV[1:, 1:] / 2.) znnpv = zxnpv * zxnpv + zynpv * zynpv zxffu = 2. * np.cos(rad*lonF[1:, 1:]) * np.tan(rpi/4. - rad*latF[1:, 1:]/2.) \ - 2. * np.cos(rad*lonF[:-1, 1:]) * np.tan(rpi/4. - rad*latF[:-1, 1:]/2.) zyffu = 2. * np.sin(rad*lonF[1:, 1:]) * np.tan(rpi/4. - rad*latF[1:, 1:]/2.) \ - 2. * np.sin(rad*lonF[:-1, 1:]) * np.tan(rpi/4. - rad*latF[:-1, 1:]/2.) znffu = np.sqrt(znnpu * (zxffu * zxffu + zyffu * zyffu)) znffu = np.maximum(znffu, 1.e-14) zxffv = 2. * np.cos(rad*lonF[1:, 1:]) * np.tan(rpi/4. - rad*latF[1:, 1:]/2.) \ - 2. * np.cos(rad*lonF[1:, :-1]) * np.tan(rpi/4. - rad*latF[1:, :-1]/2.) zyffv = 2. * np.sin(rad*lonF[1:, 1:]) * np.tan(rpi/4. - rad*latF[1:, 1:]/2.) \ - 2. * np.sin(rad*lonF[1:, :-1]) * np.tan(rpi/4. - rad*latF[1:, :-1]/2.) znffv = np.sqrt(znnpv * (zxffv * zxffv + zyffv * zyffv)) znffv = np.maximum(znffv, 1.e-14) gsinu = (zxnpu * zyffu - zynpu * zxffu) / znffu gcosu = (zxnpu * zxffu + zynpu * zyffu) / znffu gsinv = (zxnpv * zxffv + zynpv * zyffv) / znffv gcosv = -(zxnpv * zyffv - zynpv * zxffv) / znffv # ** netCDF4 writing, since xArray is bugged ** lonU = lonU[1:, 1:] latU = latU[1:, 1:] lonV = lonV[1:, 1:] latV = latV[1:, 1:] subDataset = Dataset(rotation_angles_filename, 'w', format='NETCDF4') subDataset.source = 'parcels_compute_curvilinearGrid_rotationAngles' subDataset.createDimension('x', lonU.shape[1]) subDataset.createDimension('y', lonU.shape[0]) lonUVar = subDataset.createVariable(dimensions['U']['lon'], 'f8', ( 'y', 'x', )) latUVar = subDataset.createVariable(dimensions['U']['lat'], 'f8', ( 'y', 'x', )) lonUVar.valid_min = np.min(lonU) lonUVar.valid_max = np.max(lonU) lonUVar[:] = lonU latUVar[:] = latU lonVVar = subDataset.createVariable(dimensions['V']['lon'], 'f8', ( 'y', 'x', )) latVVar = subDataset.createVariable(dimensions['V']['lat'], 'f8', ( 'y', 'x', )) lonVVar.valid_min = np.min(lonV) lonVVar.valid_max = np.max(lonV) lonVVar[:] = lonV latVVar[:] = latV cosUVar = subDataset.createVariable(variables['cosU'], 'f8', ( 'y', 'x', )) cosUVar[:] = gcosu sinUVar = subDataset.createVariable(variables['sinU'], 'f8', ( 'y', 'x', )) sinUVar[:] = gsinu cosVVar = subDataset.createVariable(variables['cosV'], 'f8', ( 'y', 'x', )) cosVVar[:] = gcosv sinVVar = subDataset.createVariable(variables['sinV'], 'f8', ( 'y', 'x', )) sinVVar[:] = gsinv subDataset.close()
def show(self, particles=True, show_time=None, field=True, domain=None, land=False, vmin=None, vmax=None, savefile=None): """Method to 'show' a Parcels ParticleSet :param particles: Boolean whether to show particles :param show_time: Time at which to show the ParticleSet :param field: Field to plot under particles (either True, a Field object, or 'vector') :param domain: Four-vector (latN, latS, lonE, lonW) defining domain to show :param land: Boolean whether to show land (in field='vector' mode only) :param vmin: minimum colour scale (only in single-plot mode) :param vmax: maximum colour scale (only in single-plot mode) :param savefile: Name of a file to save the plot to """ try: import matplotlib.pyplot as plt except: logger.info("Visualisation is not possible. Matplotlib not found.") return try: from mpl_toolkits.basemap import Basemap except: Basemap = None plon = np.array([p.lon for p in self]) plat = np.array([p.lat for p in self]) show_time = self[0].time if show_time is None else show_time if isinstance(show_time, datetime): show_time = (show_time - self.fieldset.U.time_origin).total_seconds() if isinstance(show_time, delta): show_time = show_time.total_seconds() if domain is not None: latN = nearest_index(self.fieldset.U.lat, domain[0]) latS = nearest_index(self.fieldset.U.lat, domain[1]) lonE = nearest_index(self.fieldset.U.lon, domain[2]) lonW = nearest_index(self.fieldset.U.lon, domain[3]) else: latN, latS, lonE, lonW = (-1, 0, -1, 0) if field is not 'vector': plt.ion() plt.clf() if particles: plt.plot(np.transpose(plon), np.transpose(plat), 'ko') if field is True: axes = plt.gca() axes.set_xlim([self.fieldset.U.lon[lonW], self.fieldset.U.lon[lonE]]) axes.set_ylim([self.fieldset.U.lat[latS], self.fieldset.U.lat[latN]]) namestr = '' time_origin = self.fieldset.U.time_origin else: if not isinstance(field, Field): field = getattr(self.fieldset, field) field.show(with_particles=True, show_time=show_time, vmin=vmin, vmax=vmax) namestr = field.name time_origin = field.time_origin if time_origin is 0: timestr = ' after ' + str(delta(seconds=show_time)) + ' hours' else: timestr = ' on ' + str(time_origin + delta(seconds=show_time)) xlbl = 'Zonal distance [m]' if type(self.fieldset.U.units) is UnitConverter else 'Longitude [degrees]' ylbl = 'Meridional distance [m]' if type(self.fieldset.U.units) is UnitConverter else 'Latitude [degrees]' plt.xlabel(xlbl) plt.ylabel(ylbl) elif Basemap is None: logger.info("Visualisation is not possible. Basemap not found.") time_origin = self.fieldset.U.time_origin else: time_origin = self.fieldset.U.time_origin (idx, periods) = self.fieldset.U.time_index(show_time) show_time -= periods*(self.fieldset.U.time[-1]-self.fieldset.U.time[0]) U = np.array(self.fieldset.U.temporal_interpolate_fullfield(idx, show_time)) V = np.array(self.fieldset.V.temporal_interpolate_fullfield(idx, show_time)) lon = self.fieldset.U.lon lat = self.fieldset.U.lat lon = lon[lonW:lonE] lat = lat[latS:latN] U = U[latS:latN, lonW:lonE] V = V[latS:latN, lonW:lonE] # configuring plot lat_median = np.median(lat) lon_median = np.median(lon) plt.figure() m = Basemap(projection='merc', lat_0=lat_median, lon_0=lon_median, resolution='h', area_thresh=100, llcrnrlon=lon[0], llcrnrlat=lat[0], urcrnrlon=lon[-1], urcrnrlat=lat[-1]) if land: m.drawcoastlines() m.fillcontinents(color='burlywood') parallels = np.arange(lat[0], lat[-1], abs(lat[0]-lat[-1])/5) parallels = np.around(parallels, 2) m.drawparallels(parallels, labels=[1, 0, 0, 0]) meridians = np.arange(lon[0], lon[-1], abs(lon[0]-lon[-1])/5) meridians = np.around(meridians, 2) m.drawmeridians(meridians, labels=[0, 0, 0, 1]) # formating velocity data for quiver plotting U = np.array([U[y, x] for x in range(len(lon)) for y in range(len(lat))]) V = np.array([V[y, x] for x in range(len(lon)) for y in range(len(lat))]) speed = np.sqrt(U**2 + V**2) normU = U/speed normV = V/speed x = np.repeat(lon, len(lat)) y = np.tile(lat, len(lon)) # plotting velocity vector field vecs = m.quiver(x, y, normU, normV, speed, cmap=plt.cm.gist_ncar, clim=[vmin, vmax], scale=50, latlon=True) m.colorbar(vecs, "right", size="5%", pad="2%") # plotting particle data if particles: xs, ys = m(plon, plat) m.scatter(xs, ys, color='black') if time_origin is 0: timestr = ' after ' + str(delta(seconds=show_time)) + ' hours' else: timestr = ' on ' + str(time_origin + delta(seconds=show_time)) if particles: if field: plt.title('Particles' + timestr) elif field is 'vector': plt.title('Particles and velocity field' + timestr) else: plt.title('Particles and '+namestr + timestr) else: if field is 'vector': plt.title('Velocity field' + timestr) else: plt.title(namestr + timestr) if savefile is None: plt.show() plt.pause(0.0001) else: plt.savefig(savefile) logger.info('Plot saved to '+savefile+'.png') plt.close()
def plotfield(field, show_time=None, domain=None, projection=None, land=True, vmin=None, vmax=None, savefile=None, **kwargs): """Function to plot a Parcels Field :param show_time: Time at which to show the Field :param domain: Four-vector (latN, latS, lonE, lonW) defining domain to show :param projection: type of cartopy projection to use (default PlateCarree) :param land: Boolean whether to show land. This is ignored for flat meshes :param vmin: minimum colour scale (only in single-plot mode) :param vmax: maximum colour scale (only in single-plot mode) :param savefile: Name of a file to save the plot to :param animation: Boolean whether result is a single plot, or an animation """ if type(field) is VectorField: spherical = True if field.U.grid.mesh == 'spherical' else False field = [field.U, field.V] plottype = 'vector' elif type(field) is Field: spherical = True if field.grid.mesh == 'spherical' else False field = [field] plottype = 'scalar' else: raise RuntimeError('field needs to be a Field or VectorField object') plt, fig, ax, cartopy = create_parcelsfig_axis(spherical, land, projection=projection) if plt is None: return None, None, None, None # creating axes was not possible data = {} plotlon = {} plotlat = {} for i, fld in enumerate(field): show_time = fld.grid.time[0] if show_time is None else show_time if fld.grid.defer_load: fld.fieldset.computeTimeChunk(show_time, 1) (idx, periods) = fld.time_index(show_time) show_time -= periods * (fld.grid.time[-1] - fld.grid.time[0]) latN, latS, lonE, lonW = parsedomain(domain, fld) if isinstance(fld.grid, CurvilinearGrid): plotlon[i] = fld.grid.lon[latS:latN, lonW:lonE] plotlat[i] = fld.grid.lat[latS:latN, lonW:lonE] else: plotlon[i] = fld.grid.lon[lonW:lonE] plotlat[i] = fld.grid.lat[latS:latN] if i > 0 and not np.allclose(plotlon[i], plotlon[0]): raise RuntimeError('VectorField needs to be on an A-grid for plotting') if fld.grid.time.size > 1: data[i] = np.squeeze(fld.temporal_interpolate_fullfield(idx, show_time))[latS:latN, lonW:lonE] else: data[i] = np.squeeze(fld.data)[latS:latN, lonW:lonE] if plottype is 'vector': spd = data[0] ** 2 + data[1] ** 2 speed = np.sqrt(spd, where=spd > 0) vmin = speed.min() if vmin is None else vmin vmax = speed.max() if vmax is None else vmax if isinstance(field[0].grid, CurvilinearGrid): x, y = plotlon[0], plotlat[0] else: x, y = np.meshgrid(plotlon[0], plotlat[0]) nonzerospd = speed != 0 u, v = (np.zeros_like(data[0]) * np.nan, np.zeros_like(data[1]) * np.nan) np.place(u, nonzerospd, data[0][nonzerospd] / speed[nonzerospd]) np.place(v, nonzerospd, data[1][nonzerospd] / speed[nonzerospd]) if cartopy: cs = ax.quiver(x, y, u, v, speed, cmap=plt.cm.gist_ncar, clim=[vmin, vmax], scale=50, transform=cartopy.crs.PlateCarree()) else: cs = ax.quiver(x, y, u, v, speed, cmap=plt.cm.gist_ncar, clim=[vmin, vmax], scale=50) else: vmin = data[0].min() if vmin is None else vmin vmax = data[0].max() if vmax is None else vmax if cartopy: cs = ax.pcolormesh(plotlon[0], plotlat[0], data[0], transform=cartopy.crs.PlateCarree()) else: cs = ax.pcolormesh(plotlon[0], plotlat[0], data[0]) if cartopy is None or projection is None: ax.set_xlim(np.nanmin(plotlon[0]), np.nanmax(plotlon[0])) ax.set_ylim(np.nanmin(plotlat[0]), np.nanmax(plotlat[0])) elif domain is not None: ax.set_extent([np.nanmin(plotlon[0]), np.nanmax(plotlon[0]), np.nanmin(plotlat[0]), np.nanmax(plotlat[0])]) cs.cmap.set_over('k') cs.cmap.set_under('w') cs.set_clim(vmin, vmax) cartopy_colorbar(cs, plt, fig, ax) timestr = parsetimestr(field[0].grid.time_origin, show_time) titlestr = kwargs.pop('titlestr', '') if plottype is 'vector': ax.set_title(titlestr + 'Velocity field' + timestr) else: ax.set_title(titlestr + field[0].name + timestr) if not spherical: ax.set_xlabel('Zonal distance [m]') ax.set_ylabel('Meridional distance [m]') plt.draw() if savefile: plt.savefig(savefile) logger.info('Plot saved to ' + savefile + '.png') plt.close() return plt, fig, ax, cartopy
def plotparticles(particles, with_particles=True, show_time=None, field=None, domain=None, projection=None, land=True, vmin=None, vmax=None, savefile=None, animation=False): """Function to plot a Parcels ParticleSet :param show_time: Time at which to show the ParticleSet :param with_particles: Boolean whether particles are also plotted on Field :param field: Field to plot under particles (either None, a Field object, or 'vector') :param domain: Four-vector (latN, latS, lonE, lonW) defining domain to show :param projection: type of cartopy projection to use (default PlateCarree) :param land: Boolean whether to show land. This is ignored for flat meshes :param vmin: minimum colour scale (only in single-plot mode) :param vmax: maximum colour scale (only in single-plot mode) :param savefile: Name of a file to save the plot to :param animation: Boolean whether result is a single plot, or an animation """ show_time = particles[0].time if show_time is None else show_time if isinstance(show_time, datetime): show_time = np.datetime64(show_time) if isinstance(show_time, np.datetime64): if not particles.time_origin: raise NotImplementedError( 'If fieldset.time_origin is not a date, showtime cannot be a date in particleset.show()') show_time = (show_time - particles.time_origin) / np.timedelta64(1, 's') if isinstance(show_time, delta): show_time = show_time.total_seconds() if np.isnan(show_time): show_time, _ = particles.fieldset.gridset.dimrange('time_full') if field is None: spherical = True if particles.fieldset.U.grid.mesh == 'spherical' else False plt, fig, ax, cartopy = create_parcelsfig_axis(spherical, land, projection) if plt is None: return # creating axes was not possible ax.set_title('Particles' + parsetimestr(particles.fieldset.U.grid.time_origin, show_time)) latN, latS, lonE, lonW = parsedomain(domain, particles.fieldset.U) if cartopy is None or projection is None: if domain is not None: if isinstance(particles.fieldset.U.grid, CurvilinearGrid): ax.set_xlim(particles.fieldset.U.grid.lon[latS, lonW], particles.fieldset.U.grid.lon[latN, lonE]) ax.set_ylim(particles.fieldset.U.grid.lat[latS, lonW], particles.fieldset.U.grid.lat[latN, lonE]) else: ax.set_xlim(particles.fieldset.U.grid.lon[lonW], particles.fieldset.U.grid.lon[lonE]) ax.set_ylim(particles.fieldset.U.grid.lat[latS], particles.fieldset.U.grid.lat[latN]) else: ax.set_xlim(np.nanmin(particles.fieldset.U.grid.lon), np.nanmax(particles.fieldset.U.grid.lon)) ax.set_ylim(np.nanmin(particles.fieldset.U.grid.lat), np.nanmax(particles.fieldset.U.grid.lat)) elif domain is not None: if isinstance(particles.fieldset.U.grid, CurvilinearGrid): ax.set_extent([particles.fieldset.U.grid.lon[latS, lonW], particles.fieldset.U.grid.lon[latN, lonE], particles.fieldset.U.grid.lat[latS, lonW], particles.fieldset.U.grid.lat[latN, lonE]]) else: ax.set_extent([particles.fieldset.U.grid.lon[lonW], particles.fieldset.U.grid.lon[lonE], particles.fieldset.U.grid.lat[latS], particles.fieldset.U.grid.lat[latN]]) else: if field is 'vector': field = particles.fieldset.UV elif not isinstance(field, Field): field = getattr(particles.fieldset, field) plt, fig, ax, cartopy = plotfield(field=field, animation=animation, show_time=show_time, domain=domain, projection=projection, land=land, vmin=vmin, vmax=vmax, savefile=None, titlestr='Particles and ') if plt is None: return # creating axes was not possible if with_particles: plon = np.array([p.lon for p in particles]) plat = np.array([p.lat for p in particles]) if cartopy: ax.scatter(plon, plat, s=20, color='black', zorder=20, transform=cartopy.crs.PlateCarree()) else: ax.scatter(plon, plat, s=20, color='black', zorder=20) if animation: plt.draw() plt.pause(0.0001) elif savefile is None: plt.show() else: plt.savefig(savefile) logger.info('Plot saved to ' + savefile + '.png') plt.close()