OB.DAAC Logo
NASA Logo
Ocean Color Science Software

ocssw V2022
mapgen_overlay.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 import argparse
3 from pathlib import Path
4 import sys
5 import os
6 import numpy as np
7 import matplotlib.pyplot as plt
8 import matplotlib as mpl
9 import matplotlib.ticker as mticker
10 import cartopy.crs as ccrs
11 from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter, LatitudeLocator
12 
13 def get_palette(palfile):
14 
15  palpath = None
16  if os.getenv("OCDATAROOT"):
17  dataroot = Path(os.getenv("OCDATAROOT"))
18 
19  palpath = dataroot / 'common' / 'palette' / palfile
20  print(palpath)
21  if not Path.exists(palpath):
22  err_msg = "Cannot find palette %s" % palfile
23  sys.exit("Cannot find palette {palfile}".format(palfile=str(palfile)))
24  palpath = str(palpath)
25  else:
26  sys.exit("$OCDATAROOT needs to be defined")
27  colors = []
28 
29  fp = open(palpath, "r")
30  for line in fp:
31  r,g,b = line.split()
32  colors.append((int(r)/255.,int(g)/255.,int(b)/255.))
33 
34 
35  cmap = mpl.colors.LinearSegmentedColormap.from_list("palfile",colors, gamma=1.0)
36  return cmap
37 
38 def get_crs_from_proj(projStr):
39  crs = None
40  #proj=+proj=aea +ellps=WGS84 +datum=WGS84 +lon_0=-80.000000 +lat_0=27.500000 +lat_1=15.000000 +lat_2=40.000000
41 
42 
43  if 'EPSG' in projStr:
44  print("Since I have to trust querying https://epsg.io/ (and need an internet connection to do so)...things might go wrong...but we'll give it a try :)")
45  key,epsgcode = projStr.strip().split(':')
46  crs = ccrs.epsg(epsgcode)
47  else:
48  proj={}
49  proj['utmSouth'] = False
50  for element in projStr.split():
51  if '+south' in element:
52  proj['utmSouth'] = True
53  if '=' in element:
54  key, value = element.split('=')
55  proj[key[1:]] = value
56  if 'proj' in proj:
57  if proj['proj'] == 'eqc':
58  crs = ccrs.PlateCarree()
59  if proj['proj'] == 'aea':
60  crs = ccrs.AlbersEqualArea(central_longitude=float(proj['lon_0']), central_latitude=float(proj['lat_0']), standard_parallels=(float(proj['lat_1']), float(proj['lat_2'])))
61  if proj['proj'] == 'moll':
62  crs = ccrs.Mollweide(central_longitude=float(proj['lon_0']))
63  if proj['proj'] == 'lcc':
64  crs = ccrs.LambertConformal(central_longitude=float(proj['lon_0']), central_latitude=float(proj['lat_0']), standard_parallels=(float(proj['lat_1']), float(proj['lat_2'])))
65  if proj['proj'] == 'merc':
66  crs = ccrs.Mercator(central_longitude=float(proj['lon_0']))
67  if proj['proj'] == 'tmerc':
68  crs = ccrs.TransverseMercator(central_longitude=float(proj['lon_0']),central_latitude=float(proj['lat_0']), approx=False)
69  if proj['proj'] == 'utm':
70  crs = ccrs.UTM(proj['zone'], southern_hemisphere=proj['utmSouth'])
71  if proj['proj'] == 'stere':
72  crs = ccrs.Stereographic(central_longitude=float(proj['lon_0']), central_latitude=float(proj['lat_0']), true_scale_latitude=float(proj['lat_ts']))
73  if proj['proj'] == 'ortho':
74  crs = ccrs.Orthographic(central_longitude=float(proj['lon_0']), central_latitude=float(proj['lat_0']))
75 
76  return crs
77 
78 def normalize_lon(lons):
79  idx = np.where(lons - 180 < -360)
80  if idx:
81  lons[idx] += 360
82  idx = np.where(lons + 180 > 360)
83  if idx:
84  lons[idx] -= 360
85  return lons
86 
87 def calculate_gridline_increments(minval,maxval):
88  if maxval < minval:
89  maxval += 360
90 
91  if ((maxval-minval) < 15): # If you're small, just fix it at an increment of 10
92  grid = np.round(np.linspace(np.floor(minval-0.5),np.ceil(maxval+0.5),10),decimals=1)
93  else:
94  increment = int(np.floor((maxval-minval)/4)+1)
95  grid = np.round(np.linspace(np.floor(minval-increment/2),np.ceil(maxval+increment/2),increment),decimals=0)
96 
97  return(grid)
98 
99 if __name__ == "__main__":
100 
101  parser = argparse.ArgumentParser(description="add overlays to mapped PNG images output from mapgen (or l3mapgen)")
102  parser.add_argument('ifile', type=str, help='input mapped image file')
103  parser.add_argument('--projinfo', '-p', type=str, default=None, help='input projection info file; default: <ifile>.projtxt')
104  parser.add_argument('--ofile', '-o', type=str, default=None, help='output filename; default: <ifile basename>.overlay.png')
105  parser.add_argument('--coastline', '-c', action='store_true', help='add coastline overlay')
106  parser.add_argument('--gridline', '-g', action='store_true', help='add gridline overlay')
107  parser.add_argument('--label', type=str, default=None, help='add label string (e.g. source file for the image)')
108  parser.add_argument('--nogridlabels', action='store_true', help='do not lable gridlines')
109  parser.add_argument('--colorbar', action='store_true',help='add a colorbar; requires datamin, datamax; optionally: scaletype, palfile, cbartitle')
110  parser.add_argument('--datamin', type=float, default=None, help='minimum value for colorbar scale')
111  parser.add_argument('--datamax', type=float, default=None, help='maximum value for colorbar scale')
112  parser.add_argument('--scale_type', choices=['linear','log'], default=None, help='colorbar scaling method; default: linear')
113  parser.add_argument('--palfile', type=str, default='default-black0.pal', help='colorbar palette name; see $OCDATAROOT/common/palette/')
114  parser.add_argument('--cbartitle', type=str,default=None, help='colorbar title string')
115  parser.add_argument('--minX', type=float, help='minimum longitude extent in meters')
116  parser.add_argument('--maxX', type=float, help='maximum longitude extent in meters')
117  parser.add_argument('--minY', type=float, help='minimum latitude extent in meters')
118  parser.add_argument('--maxY', type=float, help='maximum latitude extent in meters')
119  parser.add_argument('--north', '-n', type=float, help='maximum latitude extent in degrees')
120  parser.add_argument('--south', '-s', type=float, help='minimum latitude extent in degrees')
121  parser.add_argument('--west', '-w', type=float, help='minimum longitude extent in degrees')
122  parser.add_argument('--east', '-e', type=float, help='maximum longitude extent in degrees')
123  parser.add_argument('--proj', type=str, help='PRØJ-formatted projection string')
124  parser.add_argument('--use_transparency',action='store_true', help='output transparent image')
125 
126  args = parser.parse_args()
127 
128  ofile = args.ofile
129  if not ofile:
130  ofilePath = Path(args.ifile)
131  ofile = "{dir}/{stem}.{ext}".format(dir=ofilePath.parent,stem=ofilePath.stem, ext='overlay.png')
132 
133  pinfofile = args.projinfo
134  if not pinfofile:
135  pinfofile = "{ifile}.projtxt".format(ifile=args.ifile)
136 
137  pinfo = {}
138  asString = ['proj','scale_type']
139  with open(pinfofile,'r') as pj:
140  for line in pj:
141  if line.startswith('#'):
142  continue
143  key, value = line.split('=',1)
144  if key in asString:
145  pinfo[key] = value.rstrip()
146  else:
147  pinfo[key] = float(value.rstrip())
148 
149  pinfokeys = ['minX','maxX','minY','maxY','north','south','east','west','datamin','datamax','scale_type','proj']
150  for key,value in (vars(args)).items():
151  if key in pinfokeys and value:
152  pinfo[key] = value
153 
154  img_extent_meters = (pinfo['minX'],pinfo['maxX'],pinfo['minY'],pinfo['maxY'])
155  img_extent = (pinfo['west'],pinfo['east'],pinfo['south'],pinfo['north'])
156 
157  img = plt.imread(args.ifile)
158 
159  # set the figure with the size from the input image dimensions
160  dpi = 72
161 
162  # get image size in inches and create figure with buffer around the image
163  imgwidth = img.shape[1]/float(dpi)
164  imgheight = img.shape[0]/float(dpi)
165  # add padding
166  padheight = np.ceil(imgwidth*0.3)
167  padwidth = np.ceil(imgheight*0.3)
168 
169  figwidth = np.round(imgwidth + padwidth, decimals=2)
170  figheight = np.round(imgheight + padheight, decimals=2)
171  figrows = int(np.ceil(4*figheight))
172 
173  fig = plt.figure(figsize=(figwidth, figheight), dpi=dpi)
174 
175  # set the plot projection
176  crs = get_crs_from_proj(pinfo['proj'])
177  if not crs:
178  sys.exit("Projection not supported: {proj}".format(proj=pinfo['proj']))
179  bottom = int(np.floor(0.025*figrows))
180  ax = plt.subplot2grid((figrows,7),(0,0),rowspan=figrows-bottom, colspan=7, fig=fig,projection=crs)
181 
182  # Image Extent
183  ax.set_extent(img_extent_meters,crs=crs)
184 
185  # Create the image
186  ax.imshow(img, extent=img_extent_meters, origin='upper', transform=crs)
187 
188  # Add image name
189  if args.label:
190  ax.set_title(args.label, fontsize=18)
191 
192  # Add coastlines
193  if args.coastline:
194  ax.coastlines(resolution='50m', color='white', linewidth=1)
195 
196  # Add gridlines
197  labelsize = 14 * (figheight / 14.0)
198  if figwidth < 8:
199  labelsize = 10
200 
201  labelgrid = True
202  if args.nogridlabels:
203  labelgrid = False
204 
205  gl = ax.gridlines(draw_labels=labelgrid, dms=False,
206  x_inline=False, y_inline=False,
207  linewidth=2, color='gray', alpha=0.5)
208  gl.bottom_labels = False
209  if not args.gridline:
210  gl.xlines = False
211  gl.ylines = False
212  gl.xlabel_style = {'size': labelsize}
213  gl.ylabel_style = {'size': labelsize}
214 
215  lons = calculate_gridline_increments(pinfo['west'],pinfo['east'])
216  lats = calculate_gridline_increments(pinfo['south'],pinfo['north'])
217 
218  gl.xlocator = mticker.FixedLocator(normalize_lon(lons))
219  gl.ylocator = mticker.FixedLocator(lats)
220 
221  # add colorbar
222  if args.colorbar:
223  cbax = plt.subplot2grid((figrows,7),(figrows-bottom,2),fig=fig,rowspan=bottom,colspan=3)
224 
225  cmap = get_palette(args.palfile)
226 
227  norm = mpl.colors.Normalize(vmin=pinfo['datamin'], vmax=pinfo['datamax'])
228  formatter = None
229 
230  if (pinfo['scale_type']).lower() == 'log':
231  if pinfo['datamin'] <= 0:
232  sys.exit("Log scales don't like zero (or less than zero)")
233  formatter = mpl.ticker.LogFormatter(10, labelOnlyBase=False)
234  norm = mpl.colors.LogNorm(vmin=pinfo['datamin'], vmax=pinfo['datamax'])
235 
236  cb = mpl.colorbar.ColorbarBase(cbax, cmap=cmap,
237  norm=norm,
238  orientation='horizontal',
239  format=formatter)
240 
241  if args.cbartitle:
242  cb.set_label(args.cbartitle,size=labelsize)
243  cbax.tick_params(labelsize=labelsize)
244  cbax.minorticks_on()
245 
246  plt.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9)
247  # Save the image
248  plt.savefig(ofile, dpi=dpi, bbox_inches='tight', pad_inches=0.2,transparent=args.use_transparency)
def normalize_lon(lons)
def get_palette(palfile)
def calculate_gridline_increments(minval, maxval)
def get_crs_from_proj(projStr)