def plot_data(self,**kargs): prjtype=kargs.pop('prjtype','nice') from matplotlib.cm import gist_earth_r if 0: j=np.ceil(np.median(range(len(self)))).astype('i') ht=ticks.loose_label_n(self[j].h.min(),self[j].h.max(),7) else: ht=ticks.loose_label_n(self[0].h.min(),self[0].h.max(),7) if len(self)>1: ht2=ticks.loose_label_n(ht[0],ht[1]/2,5)[1:] ht=np.concatenate(([ht[0]],ht2,ht[1:])) ht=np.unique(np.sort(ht)) ht=kargs.get('field__cvals',ht) ht=kargs.get('cvals',ht) # contour colors: from matplotlib import rcParams colors=rcParams['axes.prop_cycle']() for c,i in enumerate(self): h=np.ma.masked_where(i.mask==0,i.h) b=vis.Data(x=i.lon,y=i.lat,v=h) b.set_param(field__plot='contourf',field__cvals=ht,field__cmap=gist_earth_r) b.set_param(**kargs) if c==0: a=b else: a.extra+=[b] c=vis.Data(x=i.lon,y=i.lat,v=h) c.set_param(field__plot='contour',field__cvals=ht,field__cmap=next(colors)['color'], field__linewidths=0.5,plot__zorder=2) # show above everything else c.label='bathy' a.extra+=[c] # show all borders: for i in self: xb,yb=i.border() c=vis.Data(x=xb,v=yb) c.set_param(d1_line__options=dict(lw=0.5,color='k',ls='-')) a.extra+=[c] # about projection: if prjtype=='original' and self[0].proj_info['basemap_opts0']: d=self[0].proj_info['basemap_opts0'] elif prjtype=='nice': d=self[0].proj_info['basemap_opts'] a.set_projection(d) return vis.MData([a])
def plot(self,**kargs): from okean import vis from matplotlib.cm import gist_earth_r h=np.ma.masked_where(self.mask==0,self.h) a=vis.Data(x=self.lon,y=self.lat,v=h) ht=ticks.loose_label_n(self.h.min(),self.h.max(),7) a.set_param(field__plot='contourf',field__cvals=ht,field__cmap=gist_earth_r) a.set_param(**kargs) # also show domain boundary: xb,yb=self.border() b=vis.Data(x=xb,v=yb) b.set_param(d1_line__options=dict(lw=0.5,color='k',ls='-')) a.extra=[b] a.plot(labels=0) return a
def plot_data(self,**kargs): prjtype=kargs.pop('prjtype','nice') from matplotlib.cm import gist_earth_r h=np.ma.masked_where(self.mask==0,self.h) a=vis.Data(x=self.lon,y=self.lat,v=h) ht=ticks.loose_label_n(self.h.min(),self.h.max(),7) a.set_param(field__plot='contourf',field__cvals=ht,field__cmap=gist_earth_r) a.set_param(**kargs) # also show domain boundary: xb,yb=self.border() b=vis.Data(x=xb,v=yb) b.set_param(d1_line__options=dict(lw=0.5,color='k',ls='-')) a.extra=[b] # about projection: if prjtype=='original' and self.proj_info['basemap_opts0']: d=self.proj_info['basemap_opts0'] elif prjtype=='nice': d=self.proj_info['basemap_opts'] a.set_projection(d) return a
def slicek(self,varname,ind,time=0,**opts): coords=opts.get('coords',self._default_coords('slicek')).split(',') out=vis.Data() out.label='slicek' out.msg=self.check_slice(varname,t=time,k=ind) if out.msg: return out v=self.use(varname,SEARCHtime=time,s_SEARCH=ind) # add mask if not masked: if not np.ma.isMA(v): ### m=self.grid.vars(ruvp=self.var_at(varname)[0])[-1] m=self.grid.vars(ruvp=self.vloc(varname)[0])[-1] v=np.ma.masked_where(m==0,v) out.v=v out.info['v']['name']=varname ##### if self.hasz(varname): out.info['v']['slice']='k=%d'%ind if 'z' in self.vaxes(varname): out.info['v']['slice']='k=%d'%ind try: out.info['v']['units']=netcdf.vatt(self.nc,varname,'units') except: pass # coords: if 'z' in coords and 'z' in self.vaxes(varname): out.z=self.s_levels(time,k=ind,loc=self.vloc(varname)) out.info['z']=dict(name='Depth',units='m') if any([i in coords for i in 'xy']): x,y,h,m=self.grid.vars(ruvp=self.vloc(varname)[0]) if 'x' in coords: if self.grid.spherical: out.x=x out.info['x']=dict(name='Longitude',units=r'$\^o$E') else: out.x=x/1000. out.info['x']=dict(name='Distance',units='km') if 'y' in coords: if self.grid.spherical: out.y=y out.info['y']=dict(name='Latitude',units=r'$\^o$N') else: out.y=y/1000. out.info['y']=dict(name='Distance',units='km') if 't' in coords and 't' in self.vaxes(varname): out.t=self.time[time] if out.v.ndim==2: # always? out.extra=[vis.Data()] if 'x' in coords: out.extra[0].x=out.x if 'y' in coords: out.extra[0].y=out.y out.extra[0].v=h if h.max()>1000: cvals=200.,1000. elif h.max()>200: cvals=50.,100.,200. else: cvals=3 out.extra[0].config['field.plot']='contour' out.extra[0].config['field.cvals']=cvals out.extra[0].config['field.cmap']='k' out.extra[0].label='bathy' out.coordsReq=','.join(sorted(coords)) # about projection: out.set_projection(self.grid.proj_info['basemap_opts']) return out
def slicej(self,varname,ind,time=0,**opts): coords=opts.get('coords',self._default_coords('slicej')).split(',') out=vis.Data() out.label='slicej' out.msg=self.check_slice(varname,t=time,j=ind) if out.msg: return out v=self.use(varname,SEARCHtime=time,eta_SEARCH=ind) # add mask if not masked: if not np.ma.isMA(v): ### m=self.grid.vars(ruvp=self.var_at(varname)[0],j=ind)[-1] m=self.grid.vars(ruvp=self.vloc(varname)[0],j=ind)[-1] if v.ndim==2: m=np.tile(m,(v.shape[0],1)) v=np.ma.masked_where(m==0,v) out.v=v out.info['v']['name']=varname out.info['v']['slice']='j=%d'%ind try: out.info['v']['units']=netcdf.vatt(self.name,varname,'units') except: pass # coords: if 'z' in coords and v.ndim==2: ###### out.z=self.s_levels(time=time,ruvpw=self.var_at(varname),j=ind) ### out.z=self.s_levels(time=time,loc=self.var_at(varname),j=ind) out.z=self.s_levels(time=time,loc=self.vloc(varname),j=ind) out.info['z']=dict(name='Depth',units='m') if any([i in coords for i in 'xyd']): ### x,y,h,m=self.grid.vars(ruvp=self.var_at(varname)[0],j=ind) x,y,h,m=self.grid.vars(ruvp=self.vloc(varname)[0],j=ind) if 'd' in coords: d=calc.distance(x,y) if d[-1]-d[0]>1e4: d=d/1000. dunits='km' else: dunits='m' if v.ndim==2: d=np.tile(d,(v.shape[0],1)) out.d=d out.info['d']=dict(name='Distance',units=dunits) if 'x' in coords: if v.ndim==2: x=np.tile(x,(v.shape[0],1)) out.x=x out.info['x']=dict(name='Longitude',units=r'$\^o$E') if 'y' in coords: if v.ndim==2: y=np.tile(y,(v.shape[0],1)) out.y=y out.info['y']=dict(name='Latitude',units=r'$\^o$N') ######## if 't' in coords and self.hast(varname): out.t=self.time[time] if 't' in coords and 't' in self.vaxes(varname): out.t=self.time[time] if v.ndim==2: out.extra=[vis.Data()] if 'd' in coords: out.extra[0].x=out.d[0] if 'x' in coords: out.extra[0].y=out.x[0] if 'y' in coords: out.extra[0].x=out.y[0] out.extra[0].v=-h out.extra[0].config['d1.plot']='fill_between' out.extra[0].config['d1.y0']=-h.max()-(h.max()-h.min())/20. out.extra[0].label='bottom' out.coordsReq=','.join(sorted(coords)) return out
def _slice(self,slc,varname,ind,it0,**opts): ## border=opts.get('border',1) if slc=='ll': x,y=ind o=[] It,twarn=self.tinds(it0) for c,i in enumerate(self): meth=getattr(i,'slice'+slc) if slc=='uv': o+=[meth(ind,It[c],**opts)] elif slc=='ll':o+=[meth(varname,x,y,It[c],**opts)] else: o+=[meth(varname,ind,It[c],**opts)] #if msg[c]: # if o[-1].msg: o[-1].msg+='\n' # o[-1].msg+=msg[c] # add filled border for next domain: if slc in ['i','j','k','z','iso']:#,'uv']: for i in range(len(self)-1): xb,yb=self[i+1].grid.border() o[i].extra+=[vis.Data(xb,yb)] o[i].extra[-1].label='border grid %d'%(i+1) #if i>0: o[i].extra[-1].config['d1.plot']='fill' o[i].extra[-1].config['d1_fill.options']['lw']=0 o[i].extra[-1].config['d1_fill.options']['facecolor']='w' #o[i].extra[-1].config['plot.zorder']=1 #else: # o[i].extra[-1].config['d1.plot']='plot' elif slc=='uv': # for uv, better to mask arrows inside child domains if not hasattr(self.grid,'ingrd'): self.grid._set_ingrid('p') from functools import reduce for c,i in enumerate(o): if not i.v is None: if len(self.grid[c].ingrd_p): mask=reduce(lambda i,j: i&j,self.grid[c].ingrd_p) mask=mask&(~i.v.mask) i.nmask=mask # nested domains mask i.v.mask=i.v.mask|i.nmask else: i.nmask=None # vfield settings: if not o[0].v is None: vfield=o[0].get_param('vfield') if not vfield['options']['scale']: vfield['options']={'units':'width','scale':10} # use same vfield settings for all slices: for i in o: for k in vfield: i.config['vfield.'+k]=vfield[k] # no key for slices[1:]: for i in o[1:]: i.config['vfield.key_XYU']=0,0,0 # field settings: if not o[0].v is None: field=o[0].get_param('field') if field['clim'] is False: if np.iscomplexobj(o[0].v): field['clim']=np.abs(o[0].v).min(),np.abs(o[0].v).max() else: field['clim']=o[0].v.min(),o[0].v.max() if field['clim'][0]==field['clim'][1]: field['clim']=field['clim'][0]-1,field['clim'][0]+1 if field['cvals'] is False: tk=ticks.loose_label_n(field['clim'][0],field['clim'][1],7) field['cvals']=tk # use same field settings for all slices: for i in o: for k in field: i.config['field.'+k]=field[k] # also same settings for extras like bathy contours: Lab=['bathy'] for lab in Lab: for e in o[0].extra: if e.label==lab and not e.v is None: field=e.get_param('field') if field['clim'] is False: field['clim']=e.v.min(),e.v.max() if field['cvals'] is False or not calc.isiterable(field['cvals']): # the isiterable here is because it can be an integer (nof fixed set) tk=ticks.loose_label_n(field['clim'][0],field['clim'][1],3) field['cvals']=tk for i in o: for e in i.extra: if e.label==lab: for k in field: e.config['field.'+k]=field[k] # add border for all domains: # xb,yb=self.grid.border() # o[0].extra+=[vis.Data(x=xb,v=yb)] borders=[] for i in self: xb,yb=i.grid.border() c=vis.Data(x=xb,v=yb) c.set_param(d1_line__options=dict(lw=0.5,color='k',ls='-')) borders+=[c] o[-1].extra+=borders # set zorder: c=1 for i in o: c+=0.1 i.config['plot.zorder']=c for j in i.extra: c+=0.1 j.config['plot.zorder']=c # place some stuff above continents: z=o[0].config['proj.continents']['zorder'] z+=0.1 # vfield: if slc=='uv': for i in o: i.config['plot.zorder']=z # place borders above continents: for b in borders: b.config['plot.zorder']=z return vis.MData(o,warnings=twarn)
def time_series(self,varname,x,y,times=None,depth=None,**opts): coords=opts.get('coords',self._default_coords('time_series')).split(',') if times is None: times=range(0,self.time.size) # depth or s_level: check if is float or if is negative! isDepth=False if not depth is None: if calc.isiterable(depth): depth=np.asarray(depth) if calc.isarray(depth): isDepth=np.any(depth<0) or depth.kind!='i' else: isDepth=depth<0 or np.asarray(depth).dtype.kind!='i' out=vis.Data() out.label='time_series' if not depth is None and not isDepth: out.msg=self.check_slice(varname,t=np.max(times),k=depth) else: out.msg=self.check_slice(varname,t=np.max(times)) if out.msg: return out # find nearest point: ### lon,lat,hr,mr=self.grid.vars(ruvp=self.var_at(varname)) lon,lat,hr,mr=self.grid.vars(ruvp=self.vloc(varname)) dist=(lon-x)**2+(lat-y)**2 i,j=np.where(dist==dist.min()) i,j=i[0],j[0] if not depth is None and not isDepth: arg={'s_SEARCH':depth} else: arg={} v=self.use(varname,xiSEARCH=j,etaSEARCH=i,SEARCHtime=times,**arg).T # calculate depths: ### if self.hasz(varname): if 'z' in self.vaxes(varname): h=self.grid.h[i,j] zeta=self.use('zeta',xiSEARCH=j,etaSEARCH=i,SEARCHtime=times) h=h+0*zeta #### z=rt.s_levels(h,zeta,self.s_params,rw=varname) ### z=rt.s_levels(h,zeta,self.s_params,rw=self.var_at(varname)[1]) z=rt.s_levels(h,zeta,self.s_params,rw=self.vloc(varname)[1]) z=np.squeeze(z) # depth slice: ### if isDepth and self.hasz(varname): if isDepth and 'z' in self.vaxes(varname): if v.ndim==2: # could use calc.griddata, but better use slicez cos data at # different times may be independent! if 0: from matplotlib.dates import date2num t=np.tile(date2num(self.time[times]),(v.shape[0],1)) v=calc.griddata(t,z,v,t[0],depth+np.zeros(t[0].shape), extrap=opts.get('extrap',False),norm_xy=opts.get('norm_xy',False)) # norm_xy True may be needed! # extrap also may be needed cos the 1st and last value may be masked! else: nt=len(times) land_mask=np.ones((nt,1),dtype=v.dtype) # needed for slicez... not used here! v=rt.slicez(v[...,np.newaxis],land_mask, self.grid.h[i,j]*np.ones((nt,1),dtype=v.dtype), # bottom depth zeta[:,np.newaxis],self.s_params,depth, surface_masked=opts.get('surf_mask',True), spline=opts.get('spline',True))[...,0] else: # one time only v=np.interp(depth,z,v,left=np.nan,right=np.nan) v=np.ma.masked_where(np.isnan(v),v) out.v=v out.info['v']['name']=varname out.info['v']['slice']='time series' try: out.info['v']['units']=netcdf.vatt(self.nc,varname,'units') except: pass # coords ######### if 't' in coords and self.hast(varname): if 't' in coords and 't' in self.vaxes(varname): if v.ndim==2: out.t=np.tile(self.time[times],(v.shape[0],1)) from matplotlib.dates import date2num out.tnum=np.tile(date2num(self.time[times]),(v.shape[0],1)) else: out.t=self.time[times] out.info['t']['name']='Time' out.info['tnum']=dict(name='Time',units=self.var_as['time']['units']) ### if 'z' in coords and self.hasz(varname): if 'z' in coords and 'z' in self.vaxes(varname): if not depth is None: if not isDepth: out.z=z[depth,...] else: out.z=depth+0*v else: out.z=z out.info['z']=dict(name='Depth',units='m') if 'x' in coords: out.x=lon[i,j] if self.grid.spherical: out.info['x']=dict(name='Longitude',units=r'$\^o$E') else: out.x=x/1000. out.info['x']=dict(name='X-position',units='km') if 'y' in coords: out.y=lat[i,j] if self.grid.spherical: out.info['y']=dict(name='Latitude',units=r'$\^o$N') else: out.y=y/1000. out.info['y']=dict(name='Y-position',units='km') out.coordsReq=','.join(sorted(coords)) return out
def sliceiso(self,varname,iso,time,**opts): ''' Depths where variable, increasing/dec with depth, has some value. Output is masked where surface is higher/lower than value or where all water column is lower/higher than value. ''' coords=opts.get('coords',self._default_coords('sliceiso')).split(',') out=vis.Data() out.label='slice_iso' out.msg=self.check_slice(varname,t=time) if out.msg: return out ### if not self.hasz(varname): if not 'z' in self.vaxes(varname): out.msg='a depth dependent variable is needed for sliceiso!' return out v=self.use(varname,SEARCHtime=time) #### z=self.s_levels(time=time,ruvpw=self.var_at(varname)) ### z=self.s_levels(time=time,loc=self.var_at(varname)) z=self.s_levels(time=time,loc=self.vloc(varname)) v=rt.depthof(v,z,iso) out.v=v #out.info['v']['name']=varname out.info['v']['name']='depth' if isinstance(iso,np.ndarray) and iso.ndim==2: siso='2d' else: siso=str(iso) try: out.info['v']['slice']='%s (%s) iso=%s'%(varname,netcdf.vatt(self.nc,varname,'units'),siso) except: out.info['v']['slice']='%s iso=%s'%(varname,siso) out.info['v']['units']='metre' # coords: if any([i in coords for i in 'xy']): ### x,y,h=self.grid.vars(ruvp=self.var_at(varname))[:3] x,y,h=self.grid.vars(ruvp=self.vloc(varname))[:3] if 'x' in coords: if self.grid.spherical: out.x=x out.info['x']=dict(name='Longitude',units=r'$\^o$E') else: out.x=x/1000. out.info['x']=dict(name='Distance',units='km') if 'y' in coords: if self.grid.spherical: out.y=y out.info['y']=dict(name='Latitude',units=r'$\^o$N') else: out.y=y/1000. out.info['y']=dict(name='Distance',units='km') if 'z' in coords: # makes no sense... v is the depth! coords.remove('z') if 't' in coords and 't' in self.vaxes(varname): out.t=self.time[time] if out.v.ndim==2: out.extra=[vis.Data()] if 'x' in coords: out.extra[0].x=out.x if 'y' in coords: out.extra[0].y=out.y out.extra[0].v=h if h.max()>1000: cvals=200.,1000. elif h.max()>200: cvals=50.,100.,200. else: cvals=3 out.extra[0].config['field.plot']='contour' out.extra[0].config['field.cvals']=cvals out.extra[0].config['field.cmap']='k' out.extra[0].label='bathy' out.coordsReq=','.join(sorted(coords)) # about projection: out.set_projection(self.grid.proj_info['basemap_opts']) return out
def slicell(self,varname,X,Y,time=0,**opts): coords=opts.get('coords',self._default_coords('slicell')).split(',') data = opts.get('data',False) extrap = opts.get('extrap',False) maskLimit = opts.get('lmask',0.5) # points where interpolated mask are above # this value are considered as mask! # Most strict value is 0 out=vis.Data() out.label='slicell' out.msg=self.check_slice(varname,t=time) if out.msg: return out#None,aux X=np.asarray(X) Y=np.asarray(Y) if X.ndim>1: X=np.squeeze(X) if Y.ndim>1: Y=np.squeeze(X) ### x,y,h,m=self.grid.vars(ruvp=self.var_at(varname)[0]) x,y,h,m=self.grid.vars(ruvp=self.vloc(varname)[0]) if True: # extrat only portion of data needed: i0,i1,j0,j1=calc.ij_limits(x, y, (X.min(),X.max()),(Y.min(),Y.max()), margin=1) xi='%d:%d'%(i0,i1) eta='%d:%d'%(j0,j1) if data is False: V=self.use(varname,SEARCHtime=time,xi_SEARCH=xi,eta_SEARCH=eta) else: v=data[...,j0:j1,i0:i1] x=x[j0:j1,i0:i1] y=y[j0:j1,i0:i1] h=h[j0:j1,i0:i1] m=m[j0:j1,i0:i1] else: if data is False: V=self.use(varname,SEARCHtime=time) else: v=data if V.ndim==3: v=calc.griddata(x,y,V,X,Y,extrap=extrap,mask2d=m==0, keepMaskVal=maskLimit) elif V.ndim==2: v=calc.griddata(x,y,np.ma.masked_where(m==0,V),X,Y,extrap=extrap, keepMaskVal=maskLimit) out.v=v out.info['v']['name']=varname out.info['v']['slice']='path npts=%d'%X.size try: out.info['v']['units']=netcdf.vatt(self.nc,varname,'units') except: pass # coords: if 'z' in coords and V.ndim==3: inds=dict(xi=(i0,i1),eta=(j0,j1)) ######### out.z=self.path_s_levels(time,X,Y,rw=varname[0],inds=inds) ### out.z=self.path_s_levels(time,X,Y,rw=self.var_at(varname)[1],inds=inds) ### ####### out.z,zw=self.path_s_levels(time,X,Y,rw=False,inds=inds) ####### if self.vloc(varname)[1]=='w': out.z=zw out.z=self.path_s_levels(time,X,Y,rw=self.vloc(varname)[1],inds=inds) out.info['z']=dict(name='Depth',units='m') if 'd' in coords: d=calc.distance(X,Y) if d[-1]-d[0]>1e4: d=d/1000. dunits='km' else: dunits='m' if v.ndim==2: d=np.tile(d,(v.shape[0],1)) out.d=d out.info['d']=dict(name='Distance',units=dunits) if 'x' in coords: if v.ndim==2: X=np.tile(X,(v.shape[0],1)) out.x=X out.info['x']=dict(name='Longitude',units=r'$\^o$E') if 'y' in coords: if v.ndim==2: Y=np.tile(Y,(v.shape[0],1)) out.y=Y out.info['y']=dict(name='Latitude',units=r'$\^o$N') ####### if 't' in coords and self.hast(varname): out.t=self.time[time] if 't' in coords and 't' in self.vaxes(varname): out.t=self.time[time] if v.ndim==2: ################3 and not out.z is None: # zeta and bottom already calculated out.extra=[vis.Data()] if 'd' in coords: out.extra[0].x=out.d[0] if 'x' in coords: out.extra[0].y=out.x[0] if 'y' in coords: out.extra[0].x=out.y[0] #### #h=-zw[0] h = calc.griddata(x,y,h,X,Y,extrap=False) out.extra[0].v=-h # bottom out.extra[0].config['d1.plot']='fill_between' out.extra[0].config['d1.y0']=-h.max()-(h.max()-h.min())/20. out.extra[0].label='bottom' out.coordsReq=','.join(sorted(coords)) return out
def slicez(self,varname,ind,time=0,**opts): surf_mask=opts.get('surf_mask',True) spline=opts.get('spline',True) coords=opts.get('coords',self._default_coords('slicez')).split(',') out=vis.Data() out.label='slicez' out.msg=self.check_slice(varname,t=time) if out.msg: return out if not 'z' in self.vaxes(varname): return self.slicek(varname,ind,time,**opts) v=self.use(varname,SEARCHtime=time) ### x,y,h,m=self.grid.vars(ruvp=self.var_at(varname)[0]) x,y,h,m=self.grid.vars(ruvp=self.vloc(varname)[0]) zeta=self.use('zeta',SEARCHtime=time) zeta=rt.rho2uvp(zeta,varname) out.v=rt.slicez(v,m,h,zeta,self.s_params,ind,surf_mask,spline) out.info['v']['name']=varname if calc.isarray(ind): out.info['v']['slice']='z= array %.2f to %.2f'%(ind.min(),ind.max()) else: out.info['v']['slice']='z=%d'%ind try: out.info['v']['units']=netcdf.vatt(self.nc,varname,'units') except: pass # coords: if 'x' in coords: if self.grid.spherical: out.x=x out.info['x']=dict(name='Longitude',units=r'$\^o$E') else: out.x=x/1000. out.info['x']=dict(name='Distance',units='km') if 'y' in coords: if self.grid.spherical: out.y=y out.info['y']=dict(name='Latitude',units=r'$\^o$N') else: out.y=y/1000. out.info['y']=dict(name='Distance',units='km') if 'z' in coords: out.z=ind+np.zeros(out.v.shape) out.info['z']=dict(name='Depth',units='m') if 't' in coords and 't' in self.vaxes(varname): out.t=self.time[time] if out.v.ndim==2: out.extra=[vis.Data()] if 'x' in coords: out.extra[0].x=out.x if 'y' in coords: out.extra[0].y=out.y out.extra[0].v=h if h.max()>1000: cvals=200.,1000. elif h.max()>200: cvals=50.,100.,200. else: cvals=3 out.extra[0].config['field.plot']='contour' out.extra[0].config['field.cvals']=cvals out.extra[0].config['field.cmap']='k' out.extra[0].label='bathy' out.coordsReq=','.join(sorted(coords)) # about projection: out.set_projection(self.grid.proj_info['basemap_opts']) return out