From f988c1a56d72fe5b21f147fea37c9b8c8dd83e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 19:16:46 +0200 Subject: [PATCH 01/19] Fix URLs in examples using broken addcyclic --- examples/fcstmaps.py | 12 +++++------- examples/fcstmaps_axesgrid.py | 13 +++++-------- examples/plothighsandlows.py | 12 ++++-------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/examples/fcstmaps.py b/examples/fcstmaps.py index dcfe6802d..e907c299d 100644 --- a/examples/fcstmaps.py +++ b/examples/fcstmaps.py @@ -3,27 +3,25 @@ from __future__ import unicode_literals # this example reads today's numerical weather forecasts # from the NOAA OpenDAP servers and makes a multi-panel plot. +import datetime as dt import numpy as np import matplotlib.pyplot as plt import sys import numpy.ma as ma -import datetime from mpl_toolkits.basemap import Basemap, addcyclic from netCDF4 import Dataset as NetCDFFile, num2date # today's date is default. if len(sys.argv) > 1: - YYYYMMDD = sys.argv[1] + date = dt.datetime.strptime(sys.argv[1], "%Y%m%d") else: - YYYYMMDD = datetime.datetime.today().strftime('%Y%m%d') + date = dt.datetime.today() # set OpenDAP server URL. try: - URLbase="http://nomads.ncep.noaa.gov:9090/dods/gfs/gfs" - URL=URLbase+YYYYMMDD+'/gfs_00z' - print(URL) - data = NetCDFFile(URL) + urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" + data = NetCDFFile(date.strftime(urlbase)) except: msg = """ opendap server not providing the requested data. diff --git a/examples/fcstmaps_axesgrid.py b/examples/fcstmaps_axesgrid.py index d4d3d44df..9c7b673af 100644 --- a/examples/fcstmaps_axesgrid.py +++ b/examples/fcstmaps_axesgrid.py @@ -4,11 +4,11 @@ # this example reads today's numerical weather forecasts # from the NOAA OpenDAP servers and makes a multi-panel plot. # This version demonstrates the use of the AxesGrid toolkit. +import datetime as dt import numpy as np import matplotlib.pyplot as plt import sys import numpy.ma as ma -import datetime from mpl_toolkits.basemap import Basemap, addcyclic from mpl_toolkits.axes_grid1 import AxesGrid from netCDF4 import Dataset as NetCDFFile, num2date @@ -16,23 +16,20 @@ # today's date is default. if len(sys.argv) > 1: - YYYYMMDD = sys.argv[1] + date = dt.datetime.strptime(sys.argv[1], "%Y%m%d") else: - YYYYMMDD = datetime.datetime.today().strftime('%Y%m%d') + date = dt.datetime.today() # set OpenDAP server URL. try: - URLbase="http://nomads.ncep.noaa.gov:9090/dods/gfs/gfs" - URL=URLbase+YYYYMMDD+'/gfs_00z' - print(URL) - data = NetCDFFile(URL) + urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" + data = NetCDFFile(date.strftime(urlbase)) except: msg = """ opendap server not providing the requested data. Try another date by providing YYYYMMDD on command line.""" raise IOError(msg) - # read lats,lons,times. print(data.variables.keys()) diff --git a/examples/plothighsandlows.py b/examples/plothighsandlows.py index 9159e13b0..a0ea1c34e 100644 --- a/examples/plothighsandlows.py +++ b/examples/plothighsandlows.py @@ -4,9 +4,9 @@ plot H's and L's on a sea-level pressure map (uses scipy.ndimage.filters and netcdf4-python) """ +import datetime as dt import numpy as np import matplotlib.pyplot as plt -from datetime import datetime from mpl_toolkits.basemap import Basemap, addcyclic from scipy.ndimage.filters import minimum_filter, maximum_filter from netCDF4 import Dataset @@ -22,15 +22,11 @@ def extrema(mat,mode='wrap',window=10): return np.nonzero(mat == mn), np.nonzero(mat == mx) # plot 00 UTC today. -date = datetime.now().strftime('%Y%m%d')+'00' +urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" +date = dt.datetime.now() # open OpenDAP dataset. -#data=Dataset("http://nomads.ncep.noaa.gov:9090/dods/gfs/gfs/%s/gfs_%sz_anl" %\ -# (date[0:8],date[8:10])) -data=Dataset("http://nomads.ncep.noaa.gov:9090/dods/gfs_hd/gfs_hd%s/gfs_hd_%sz"%\ - (date[0:8],date[8:10])) - - +data = Dataset(date.strftime(urlbase)) # read lats,lons. lats = data.variables['lat'][:] From 3313c180ac7bf1cffe7fa14cc4e23cce299d3365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 19:26:35 +0200 Subject: [PATCH 02/19] Fix addcyclic array indexing with list instead of tuple --- packages/basemap/src/mpl_toolkits/basemap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index bdacd1c96..653813f34 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -5127,7 +5127,7 @@ def _addcyclic(a): except IndexError: raise ValueError('The specified axis does not correspond to an ' 'array dimension.') - return npsel.concatenate((a,a[slicer]),axis=axis) + return npsel.concatenate((a,a[tuple(slicer)]),axis=axis) def _addcyclic_lon(a): """addcyclic function for a single longitude array""" # select the right numpy functions From 4824629169fcdb4eb74791649c38e09dcb1bbcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 19:35:29 +0200 Subject: [PATCH 03/19] Add bugfix for issue 555 to CHANGELOG --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6e9b9e7..1f4415f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ https://keepachangelog.com/en/1.0.0/ https://semver.org/spec/v2.0.0.html +## [1.3.5] + +### Fixed +- Fix broken array slicing inside `addcyclic` (PR [#559], solves issue + [#555], thanks to @fragkoul). + ## [1.3.4] - 2022-08-10 ### Changed @@ -924,6 +930,10 @@ https://semver.org/spec/v2.0.0.html - Fix glitches in drawing of parallels and meridians. +[#559]: +https://github.com/matplotlib/basemap/pull/559 +[#555]: +https://github.com/matplotlib/basemap/issues/555 [#548]: https://github.com/matplotlib/basemap/pull/548 [#547]: From 62e94a2a1b77597976ec8cefb6ad7b16144f3a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 20:09:47 +0200 Subject: [PATCH 04/19] Cleanse examples/fcstmaps.py --- examples/fcstmaps.py | 197 ++++++++++++++++++++++++------------------- 1 file changed, 110 insertions(+), 87 deletions(-) diff --git a/examples/fcstmaps.py b/examples/fcstmaps.py index e907c299d..c942c339b 100644 --- a/examples/fcstmaps.py +++ b/examples/fcstmaps.py @@ -1,90 +1,113 @@ -from __future__ import (absolute_import, division, print_function) +"""Make a multi-panel plot from numerical weather forecast in NOAA OPeNDAP.""" +from __future__ import print_function -from __future__ import unicode_literals -# this example reads today's numerical weather forecasts -# from the NOAA OpenDAP servers and makes a multi-panel plot. -import datetime as dt +import netCDF4 import numpy as np import matplotlib.pyplot as plt -import sys -import numpy.ma as ma -from mpl_toolkits.basemap import Basemap, addcyclic -from netCDF4 import Dataset as NetCDFFile, num2date - - -# today's date is default. -if len(sys.argv) > 1: - date = dt.datetime.strptime(sys.argv[1], "%Y%m%d") -else: - date = dt.datetime.today() - -# set OpenDAP server URL. -try: - urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" - data = NetCDFFile(date.strftime(urlbase)) -except: - msg = """ -opendap server not providing the requested data. -Try another date by providing YYYYMMDD on command line.""" - raise IOError(msg) - - -# read lats,lons,times. - -print(data.variables.keys()) -latitudes = data.variables['lat'] -longitudes = data.variables['lon'] -fcsttimes = data.variables['time'] -times = fcsttimes[0:6] # first 6 forecast times. -ntimes = len(times) -# convert times for datetime instances. -fdates = num2date(times,units=fcsttimes.units,calendar='standard') -# make a list of YYYYMMDDHH strings. -verifdates = [fdate.strftime('%Y%m%d%H') for fdate in fdates] -# convert times to forecast hours. -fcsthrs = [] -for fdate in fdates: - fdiff = fdate-fdates[0] - fcsthrs.append(fdiff.days*24. + fdiff.seconds/3600.) -print(fcsthrs) -print(verifdates) -lats = latitudes[:] -nlats = len(lats) -lons1 = longitudes[:] -nlons = len(lons1) - -# unpack 2-meter temp forecast data. - -t2mvar = data.variables['tmp2m'] -t2m = np.zeros((ntimes,nlats,nlons+1),np.float32) -# create Basemap instance for Orthographic projection. -m = Basemap(lon_0=-90,lat_0=60,projection='ortho') -# add wrap-around point in longitude. -for nt in range(ntimes): - t2m[nt,:,:], lons = addcyclic(t2mvar[nt,:,:], lons1) -# convert to celsius. -t2m = t2m-273.15 -# contour levels -clevs = np.arange(-30,30.1,2.) -lons, lats = np.meshgrid(lons, lats) -x, y = m(lons, lats) -# create figure. -fig=plt.figure(figsize=(6,8)) -# make subplots. -for nt,fcsthr in enumerate(fcsthrs): - ax = fig.add_subplot(321+nt) - cs = m.contourf(x,y,t2m[nt,:,:],clevs,cmap=plt.cm.jet,extend='both') - m.drawcoastlines(linewidth=0.5) - m.drawcountries() - m.drawparallels(np.arange(-80,81,20)) - m.drawmeridians(np.arange(0,360,20)) - # panel title - plt.title('%d-h forecast valid '%fcsthr+verifdates[nt],fontsize=9) -# figure title -plt.figtext(0.5,0.95, - "2-m temp (\N{DEGREE SIGN}C) forecasts from %s"%verifdates[0], - horizontalalignment='center',fontsize=14) -# a single colorbar. -cax = plt.axes([0.1, 0.05, 0.8, 0.025]) -plt.colorbar(cax=cax, orientation='horizontal') -plt.show() +from mpl_toolkits.basemap import Basemap +from mpl_toolkits.basemap import addcyclic + + +def main(date, verbose=True): + """Main function.""" + + # Open dataset from OPeNDAP URL. + url = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" + try: + data = netCDF4.Dataset(date.strftime(url), "r") + if verbose: + print("Data variables:") + print(sorted(data.variables)) + except OSError as err: + err.args = (err.args[0], "date not found in OPeNDAP server") + raise + + # Read lats, lons, and times. + latitudes = data.variables["lat"] + longitudes = data.variables["lon"] + fcsttimes = data.variables["time"] + times = fcsttimes[0:6] # First 6 forecast times. + ntimes = len(times) + + # Convert times for datetime instances. + fdates = netCDF4.num2date( + times, units=fcsttimes.units, calendar="standard") + + # Make a list of YYYYMMDDHH strings. + verifdates = [fdate.strftime("%Y%m%d%H") for fdate in fdates] + if verbose: + print("Forecast datetime strings:") + print(verifdates) + + # Convert times to forecast hours. + fcsthrs = [] + for fdate in fdates: + fdiff = fdate - fdates[0] + fcsthrs.append(fdiff.days * 24. + fdiff.seconds / 3600.) + if verbose: + print("Forecast datetime hours:") + print(fcsthrs) + + # Unpack 2-meter temp forecast data. + lats = latitudes[:] + nlats = len(lats) + lons1 = longitudes[:] + nlons = len(lons1) + t2mvar = data.variables["tmp2m"] + + # Create Basemap instance for orthographic projection. + bmap = Basemap(lon_0=-90, lat_0=60, projection="ortho") + + # Add wrap-around point in longitude. + t2m = np.zeros((ntimes, nlats, nlons + 1), np.float32) + for nt in range(ntimes): + t2m[nt, :, :], lons = addcyclic(t2mvar[nt, :, :], lons1) + + # Convert to Celsius. + t2m = t2m - 273.15 + + # Define contour levels. + clevs = np.arange(-30, 30.1, 2.0) + lons, lats = np.meshgrid(lons, lats) + x, y = bmap(lons, lats) + + # Create figure. + fig = plt.figure(figsize=(6, 8)) + + # Make subplots. + for nt, fcsthr in enumerate(fcsthrs): + fig.add_subplot(321 + nt) + cs = bmap.contourf(x, y, t2m[nt, :, :], clevs, + cmap=plt.cm.jet, extend="both") + bmap.drawcoastlines(linewidth=0.5) + bmap.drawcountries() + bmap.drawparallels(np.arange(-80, 81, 20)) + bmap.drawmeridians(np.arange(0, 360, 20)) + # Set panel title. + plt.title( + "%d-h forecast valid " % fcsthr + verifdates[nt], fontsize=9) + + # Set figure title. + plt.figtext( + 0.5, 0.95, + "2-m temp (\N{DEGREE SIGN}C) forecasts from %s" % verifdates[0], + horizontalalignment="center", fontsize=14) + + # Draw a single colorbar. + cax = plt.axes([0.1, 0.05, 0.8, 0.025]) + plt.colorbar(cs, cax=cax, orientation="horizontal") + plt.show() + + +if __name__ == "__main__": + + import sys + import datetime as dt + + # Parse input date (default: today). + if len(sys.argv) > 1: + dateobj = dt.datetime.strptime(sys.argv[1], "%Y%m%d") + else: + dateobj = dt.datetime.today() + + sys.exit(main(dateobj, verbose=True)) From 34d83cede083ddf86fd763f712e3e48523ebc613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 20:32:29 +0200 Subject: [PATCH 05/19] Cleanse examples/fcstmaps_axesgrid.py --- examples/fcstmaps_axesgrid.py | 221 +++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 98 deletions(-) diff --git a/examples/fcstmaps_axesgrid.py b/examples/fcstmaps_axesgrid.py index 9c7b673af..2748a1bac 100644 --- a/examples/fcstmaps_axesgrid.py +++ b/examples/fcstmaps_axesgrid.py @@ -1,102 +1,127 @@ -from __future__ import (absolute_import, division, print_function) +"""Make a multi-panel plot from numerical weather forecast in NOAA OPeNDAP. -from __future__ import unicode_literals -# this example reads today's numerical weather forecasts -# from the NOAA OpenDAP servers and makes a multi-panel plot. -# This version demonstrates the use of the AxesGrid toolkit. -import datetime as dt +This version demonstrates the use of the AxesGrid toolkit. +""" +from __future__ import print_function + +import netCDF4 import numpy as np import matplotlib.pyplot as plt -import sys -import numpy.ma as ma -from mpl_toolkits.basemap import Basemap, addcyclic +from mpl_toolkits.basemap import Basemap +from mpl_toolkits.basemap import addcyclic from mpl_toolkits.axes_grid1 import AxesGrid -from netCDF4 import Dataset as NetCDFFile, num2date - - -# today's date is default. -if len(sys.argv) > 1: - date = dt.datetime.strptime(sys.argv[1], "%Y%m%d") -else: - date = dt.datetime.today() - -# set OpenDAP server URL. -try: - urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" - data = NetCDFFile(date.strftime(urlbase)) -except: - msg = """ -opendap server not providing the requested data. -Try another date by providing YYYYMMDD on command line.""" - raise IOError(msg) - -# read lats,lons,times. - -print(data.variables.keys()) -latitudes = data.variables['lat'] -longitudes = data.variables['lon'] -fcsttimes = data.variables['time'] -times = fcsttimes[0:6] # first 6 forecast times. -ntimes = len(times) -# convert times for datetime instances. -fdates = num2date(times,units=fcsttimes.units,calendar='standard') -# make a list of YYYYMMDDHH strings. -verifdates = [fdate.strftime('%Y%m%d%H') for fdate in fdates] -# convert times to forecast hours. -fcsthrs = [] -for fdate in fdates: - fdiff = fdate-fdates[0] - fcsthrs.append(fdiff.days*24. + fdiff.seconds/3600.) -print(fcsthrs) -print(verifdates) -lats = latitudes[:] -nlats = len(lats) -lons1 = longitudes[:] -nlons = len(lons1) - -# unpack 2-meter temp forecast data. - -t2mvar = data.variables['tmp2m'] - -# create figure, set up AxesGrid. -fig=plt.figure(figsize=(6,8)) -grid = AxesGrid(fig, [0.05,0.01,0.9,0.9], - nrows_ncols=(3, 2), - axes_pad=0.25, - cbar_mode='single', - cbar_pad=0.3, - cbar_size=0.1, - cbar_location='top', - share_all=True, - ) - -# create Basemap instance for Orthographic projection. -m = Basemap(lon_0=-90,lat_0=60,projection='ortho') -# add wrap-around point in longitude. -t2m = np.zeros((ntimes,nlats,nlons+1),np.float32) -for nt in range(ntimes): - t2m[nt,:,:], lons = addcyclic(t2mvar[nt,:,:], lons1) -# convert to celsius. -t2m = t2m-273.15 -# contour levels -clevs = np.arange(-30,30.1,2.) -lons, lats = np.meshgrid(lons, lats) -x, y = m(lons, lats) -# make subplots. -for nt,fcsthr in enumerate(fcsthrs): - ax = grid[nt] - m.ax = ax - cs = m.contourf(x,y,t2m[nt,:,:],clevs,cmap=plt.cm.jet,extend='both') - m.drawcoastlines(linewidth=0.5) - m.drawcountries() - m.drawparallels(np.arange(-80,81,20)) - m.drawmeridians(np.arange(0,360,20)) - # panel title - ax.set_title('%d-h forecast valid '%fcsthr+verifdates[nt],fontsize=9) -# figure title -plt.figtext(0.5,0.95, - "2-m temp (\N{DEGREE SIGN}C) forecasts from %s"%verifdates[0], - horizontalalignment='center',fontsize=14) -# a single colorbar. -cbar = fig.colorbar(cs, cax=grid.cbar_axes[0], orientation='horizontal') -plt.show() + + +def main(date, verbose=True): + """Main function.""" + + # Open dataset from OPeNDAP URL. + url = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" + try: + data = netCDF4.Dataset(date.strftime(url), "r") + if verbose: + print("Data variables:") + print(sorted(data.variables)) + except OSError as err: + err.args = (err.args[0], "date not found in OPeNDAP server") + raise + + # Read lats, lons, and times. + latitudes = data.variables["lat"] + longitudes = data.variables["lon"] + fcsttimes = data.variables["time"] + times = fcsttimes[0:6] # First 6 forecast times. + ntimes = len(times) + + # Convert times for datetime instances. + fdates = netCDF4.num2date( + times, units=fcsttimes.units, calendar="standard") + + # Make a list of YYYYMMDDHH strings. + verifdates = [fdate.strftime("%Y%m%d%H") for fdate in fdates] + if verbose: + print("Forecast datetime strings:") + print(verifdates) + + # Convert times to forecast hours. + fcsthrs = [] + for fdate in fdates: + fdiff = fdate - fdates[0] + fcsthrs.append(fdiff.days * 24. + fdiff.seconds / 3600.) + if verbose: + print("Forecast datetime hours:") + print(fcsthrs) + + # Unpack 2-meter temp forecast data. + lats = latitudes[:] + nlats = len(lats) + lons1 = longitudes[:] + nlons = len(lons1) + t2mvar = data.variables["tmp2m"] + + # Create Basemap instance for orthographic projection. + bmap = Basemap(lon_0=-90, lat_0=60, projection="ortho") + + # Add wrap-around point in longitude. + t2m = np.zeros((ntimes, nlats, nlons + 1), np.float32) + for nt in range(ntimes): + t2m[nt, :, :], lons = addcyclic(t2mvar[nt, :, :], lons1) + + # Convert to Celsius. + t2m = t2m - 273.15 + + # Define contour levels. + clevs = np.arange(-30, 30.1, 2.0) + lons, lats = np.meshgrid(lons, lats) + x, y = bmap(lons, lats) + + # Create figure and AxesGrid instance. + fig = plt.figure(figsize=(6, 8)) + grid = AxesGrid( + fig, + [0.05, 0.01, 0.9, 0.9], + nrows_ncols=(3, 2), + axes_pad=0.5, + cbar_mode="single", + cbar_pad=0.75, + cbar_size=0.1, + cbar_location="top", + share_all=True) + + # Make subplots. + for nt, fcsthr in enumerate(fcsthrs): + bmap.ax = grid[nt] + cs = bmap.contourf(x, y, t2m[nt, :, :], clevs, + cmap=plt.cm.jet, extend="both") + bmap.drawcoastlines(linewidth=0.5) + bmap.drawcountries() + bmap.drawparallels(np.arange(-80, 81, 20)) + bmap.drawmeridians(np.arange(0, 360, 20)) + # Set panel title. + bmap.ax.set_title( + "%d-h forecast valid " % fcsthr + verifdates[nt], fontsize=9) + + # Set figure title. + plt.figtext( + 0.5, 0.95, + "2-m temp (\N{DEGREE SIGN}C) forecasts from %s" % verifdates[0], + horizontalalignment="center", fontsize=14) + + # Draw a single colorbar. + cax = grid.cbar_axes[0] + fig.colorbar(cs, cax=cax, orientation="horizontal") + plt.show() + + +if __name__ == "__main__": + + import sys + import datetime as dt + + # Parse input date (default: today). + if len(sys.argv) > 1: + dateobj = dt.datetime.strptime(sys.argv[1], "%Y%m%d") + else: + dateobj = dt.datetime.today() + + sys.exit(main(dateobj, verbose=True)) From 2cc0d9f388cc9a8322c3c42847e93a567a5eb119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 20:50:21 +0200 Subject: [PATCH 06/19] Cleanse examples/plothighsandlows.py --- examples/plothighsandlows.py | 192 +++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 86 deletions(-) diff --git a/examples/plothighsandlows.py b/examples/plothighsandlows.py index a0ea1c34e..edacc6875 100644 --- a/examples/plothighsandlows.py +++ b/examples/plothighsandlows.py @@ -1,90 +1,110 @@ -from __future__ import (absolute_import, division, print_function) +"""Plot H's and L's on a sea-level pressure map.""" +from __future__ import print_function -""" -plot H's and L's on a sea-level pressure map -(uses scipy.ndimage.filters and netcdf4-python) -""" import datetime as dt +import netCDF4 import numpy as np import matplotlib.pyplot as plt -from mpl_toolkits.basemap import Basemap, addcyclic -from scipy.ndimage.filters import minimum_filter, maximum_filter -from netCDF4 import Dataset - -def extrema(mat,mode='wrap',window=10): - """find the indices of local extrema (min and max) - in the input array.""" - mn = minimum_filter(mat, size=window, mode=mode) - mx = maximum_filter(mat, size=window, mode=mode) - # (mat == mx) true if pixel is equal to the local max - # (mat == mn) true if pixel is equal to the local in - # Return the indices of the maxima, minima - return np.nonzero(mat == mn), np.nonzero(mat == mx) - -# plot 00 UTC today. -urlbase = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" -date = dt.datetime.now() - -# open OpenDAP dataset. -data = Dataset(date.strftime(urlbase)) - -# read lats,lons. -lats = data.variables['lat'][:] -lons1 = data.variables['lon'][:] -nlats = len(lats) -nlons = len(lons1) -# read prmsl, convert to hPa (mb). -prmsl = 0.01*data.variables['prmslmsl'][0] -# the window parameter controls the number of highs and lows detected. -# (higher value, fewer highs and lows) -local_min, local_max = extrema(prmsl, mode='wrap', window=50) -# create Basemap instance. -m =\ -Basemap(llcrnrlon=0,llcrnrlat=-80,urcrnrlon=360,urcrnrlat=80,projection='mill') -# add wrap-around point in longitude. -prmsl, lons = addcyclic(prmsl, lons1) -# contour levels -clevs = np.arange(900,1100.,5.) -# find x,y of map projection grid. -lons, lats = np.meshgrid(lons, lats) -x, y = m(lons, lats) -# create figure. -fig=plt.figure(figsize=(8,4.5)) -ax = fig.add_axes([0.05,0.05,0.9,0.85]) -cs = m.contour(x,y,prmsl,clevs,colors='k',linewidths=1.) -m.drawcoastlines(linewidth=1.25) -m.fillcontinents(color='0.8') -m.drawparallels(np.arange(-80,81,20),labels=[1,1,0,0]) -m.drawmeridians(np.arange(0,360,60),labels=[0,0,0,1]) -xlows = x[local_min]; xhighs = x[local_max] -ylows = y[local_min]; yhighs = y[local_max] -lowvals = prmsl[local_min]; highvals = prmsl[local_max] -# plot lows as blue L's, with min pressure value underneath. -xyplotted = [] -# don't plot if there is already a L or H within dmin meters. -yoffset = 0.022*(m.ymax-m.ymin) -dmin = yoffset -for x,y,p in zip(xlows, ylows, lowvals): - if x < m.xmax and x > m.xmin and y < m.ymax and y > m.ymin: - dist = [np.sqrt((x-x0)**2+(y-y0)**2) for x0,y0 in xyplotted] - if not dist or min(dist) > dmin: - plt.text(x,y,'L',fontsize=14,fontweight='bold', - ha='center',va='center',color='b') - plt.text(x,y-yoffset,repr(int(p)),fontsize=9, - ha='center',va='top',color='b', - bbox = dict(boxstyle="square",ec='None',fc=(1,1,1,0.5))) - xyplotted.append((x,y)) -# plot highs as red H's, with max pressure value underneath. -xyplotted = [] -for x,y,p in zip(xhighs, yhighs, highvals): - if x < m.xmax and x > m.xmin and y < m.ymax and y > m.ymin: - dist = [np.sqrt((x-x0)**2+(y-y0)**2) for x0,y0 in xyplotted] - if not dist or min(dist) > dmin: - plt.text(x,y,'H',fontsize=14,fontweight='bold', - ha='center',va='center',color='r') - plt.text(x,y-yoffset,repr(int(p)),fontsize=9, - ha='center',va='top',color='r', - bbox = dict(boxstyle="square",ec='None',fc=(1,1,1,0.5))) - xyplotted.append((x,y)) -plt.title('Mean Sea-Level Pressure (with Highs and Lows) %s' % date) -plt.show() +from mpl_toolkits.basemap import Basemap +from mpl_toolkits.basemap import addcyclic +from scipy.ndimage import minimum_filter +from scipy.ndimage import maximum_filter + + +def extrema(mat, mode="wrap", window=10): + """Find the indices of local extrema (min and max) in the input array.""" + + minimum = minimum_filter(mat, size=window, mode=mode) + maximum = maximum_filter(mat, size=window, mode=mode) + + # Return the indices of the maxima, minima. + # (mat == maximum) true if pixel is equal to the local max. + # (mat == minimum) true if pixel is equal to the local in. + return np.nonzero(mat == minimum), np.nonzero(mat == maximum) + + +def main(): + """Main function.""" + + # Plot 00 UTC today. + url = "http://nomads.ncep.noaa.gov/dods/gfs_0p25/gfs%Y%m%d/gfs_0p25_00z" + date = dt.datetime.now() + + # Open OPeNDAP dataset. + data = netCDF4.Dataset(date.strftime(url)) + + # Read lats and lons. + lats = data.variables["lat"][:] + lons1 = data.variables["lon"][:] + + # Read prmsl and convert to hPa (mbar). + prmsl = 0.01 * data.variables["prmslmsl"][0] + + # The window parameter controls the number of highs and lows detected + # (higher value, fewer highs and lows). + local_min, local_max = extrema(prmsl, mode="wrap", window=50) + + # Create Basemap instance. + bmap = Basemap(projection="mill", + llcrnrlon=0, llcrnrlat=-80, + urcrnrlon=360, urcrnrlat=80) + + # Add wrap-around point in longitude. + prmsl, lons = addcyclic(prmsl, lons1) + + # Define contour levels. + clevs = np.arange(900, 1100., 5.) + + # Find x, y of map projection grid. + lons, lats = np.meshgrid(lons, lats) + x, y = bmap(lons, lats) + + # Create figure. + fig = plt.figure(figsize=(8, 4.5)) + fig.add_axes([0.05, 0.05, 0.9, 0.85]) + bmap.contour(x, y, prmsl, clevs, colors="k", linewidths=1.0) + bmap.drawcoastlines(linewidth=1.25) + bmap.fillcontinents(color="0.8") + bmap.drawparallels(np.arange(-80, 81, 20), labels=[1, 1, 0, 0]) + bmap.drawmeridians(np.arange(0, 360, 60), labels=[0, 0, 0, 1]) + xlows, xhighs = x[local_min], x[local_max] + ylows, yhighs = y[local_min], y[local_max] + lowvals, highvals = prmsl[local_min], prmsl[local_max] + + # Plot lows as blue L's, with min pressure value underneath. + # Do not plot if there is already a L or H within dmin meters. + xyplotted = [] + yoffset = 0.022 * (bmap.ymax - bmap.ymin) + dmin = yoffset + for x, y, p in zip(xlows, ylows, lowvals): + if bmap.xmin < x < bmap.xmax and bmap.ymin < y < bmap.ymax: + dist = [np.sqrt((x - x0)**2 + (y - y0)**2) for x0, y0 in xyplotted] + if not dist or min(dist) > dmin: + bbox = dict(boxstyle="square", ec="None", fc=(1, 1, 1, 0.5)) + plt.text(x, y, "L", fontsize=14, fontweight="bold", + ha="center", va="center", color="b") + plt.text(x, y - yoffset, repr(int(p)), fontsize=9, + ha="center", va="top", color="b", bbox=bbox) + xyplotted.append((x, y)) + # Plot highs as red H's, with max pressure value underneath. + xyplotted = [] + for x, y, p in zip(xhighs, yhighs, highvals): + if bmap.xmin < x < bmap.xmax and bmap.ymin < y < bmap.ymax: + dist = [np.sqrt((x - x0)**2 + (y - y0)**2) for x0, y0 in xyplotted] + if not dist or min(dist) > dmin: + bbox = dict(boxstyle="square", ec="None", fc=(1, 1, 1, 0.5)) + plt.text(x, y, "H", fontsize=14, fontweight="bold", + ha="center", va="center", color="r") + plt.text(x, y - yoffset, repr(int(p)), fontsize=9, + ha="center", va="top", color="r", bbox=bbox) + xyplotted.append((x, y)) + + # Set plot title and show. + plt.title("Mean Sea-Level Pressure (with Highs and Lows) %s" % date) + plt.show() + + +if __name__ == "__main__": + + import sys + sys.exit(main()) From 2e2544c3915a01852504e4f2ccfb3399bb7fc823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Wed, 19 Oct 2022 20:55:32 +0200 Subject: [PATCH 07/19] Simplify main call in some of the examples --- examples/fcstmaps.py | 3 +-- examples/fcstmaps_axesgrid.py | 3 +-- examples/plothighsandlows.py | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/fcstmaps.py b/examples/fcstmaps.py index c942c339b..12796fcae 100644 --- a/examples/fcstmaps.py +++ b/examples/fcstmaps.py @@ -109,5 +109,4 @@ def main(date, verbose=True): dateobj = dt.datetime.strptime(sys.argv[1], "%Y%m%d") else: dateobj = dt.datetime.today() - - sys.exit(main(dateobj, verbose=True)) + main(dateobj, verbose=True) diff --git a/examples/fcstmaps_axesgrid.py b/examples/fcstmaps_axesgrid.py index 2748a1bac..7e2f226cf 100644 --- a/examples/fcstmaps_axesgrid.py +++ b/examples/fcstmaps_axesgrid.py @@ -123,5 +123,4 @@ def main(date, verbose=True): dateobj = dt.datetime.strptime(sys.argv[1], "%Y%m%d") else: dateobj = dt.datetime.today() - - sys.exit(main(dateobj, verbose=True)) + main(dateobj, verbose=True) diff --git a/examples/plothighsandlows.py b/examples/plothighsandlows.py index edacc6875..f86fddda6 100644 --- a/examples/plothighsandlows.py +++ b/examples/plothighsandlows.py @@ -105,6 +105,4 @@ def main(): if __name__ == "__main__": - - import sys - sys.exit(main()) + main() From 9ce91e3421a5db35bb5aa9e1af267be6c9136abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 14:54:59 +0200 Subject: [PATCH 08/19] Fix GeosLibrary wrapper to work with GEOS >= 3.8.0 --- packages/basemap/utils/GeosLibrary.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index c7e9460e0..7ee5f64a1 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -138,13 +138,24 @@ def extract(self, overwrite=True): os.chmod(path, 0o755) # Patch CMakeLists so that libgeos_c.so does not depend on libgeos.so. - cmakefile = os.path.join(zipfold, "capi", "CMakeLists.txt") + if self.version_tuple < (3, 8, 0): + cmakefile = os.path.join(zipfold, "capi", "CMakeLists.txt") + oldtext = "target_link_libraries(geos_c geos)" + newtext = "target_link_libraries(geos_c geos-static)" + else: + cmakefile = os.path.join(zipfold, "CMakeLists.txt") + oldtext = 'add_library(geos "")' + newtext = 'add_library(geos STATIC "")' with io.open(cmakefile, "r", encoding="utf-8") as fd: lines = fd.readlines() with io.open(cmakefile, "wb") as fd: - oldtext = "target_link_libraries(geos_c geos)" - newtext = "target_link_libraries(geos_c geos-static)" + found_sharedline = False + shared_oldtext = "if(BUILD_SHARED_LIBS)" + shared_newtext = "if(FALSE)" for line in lines: + if not found_sharedline and shared_oldtext in line: + line = line.replace(shared_oldtext, shared_newtext) + found_sharedline = True fd.write(line.replace(oldtext, newtext).encode()) # Apply specific patches for GEOS < 3.6.0. @@ -184,9 +195,12 @@ def build(self, installdir=None, njobs=1): # Define configure options. config_opts = [ "-DCMAKE_INSTALL_PREFIX={0}".format(installdir), - "-DGEOS_ENABLE_TESTS=OFF", "-DCMAKE_BUILD_TYPE=Release", ] + if self.version_tuple < (3, 8, 0): + config_opts += ["-DGEOS_ENABLE_TESTS=OFF"] + else: + config_opts += ["-DBUILD_TESTING=OFF"] if os.name == "nt" and self.version_tuple < (3, 6, 0): config_opts = ["-G", "NMake Makefiles"] + config_opts From ee4d68b4ee682b110535333f6faf1396bd750588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 15:02:09 +0200 Subject: [PATCH 09/19] Reformat config/build options in GeosLibrary.build --- packages/basemap/utils/GeosLibrary.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index 7ee5f64a1..909974bfb 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -183,37 +183,33 @@ def build(self, installdir=None, njobs=1): # Download and extract zip file if not present. zipfold = os.path.join(self.root, "geos-{0}".format(self.version)) self.extract(overwrite=True) + version = self.version_tuple - # Define build directory. + # Define build and install directory. builddir = os.path.join(zipfold, "build") - - # Define installation directory. if installdir is None: installdir = os.path.expanduser("~/.local/share/libgeos") installdir = os.path.abspath(installdir) - # Define configure options. + # Define generic configure and build options. config_opts = [ - "-DCMAKE_INSTALL_PREFIX={0}".format(installdir), "-DCMAKE_BUILD_TYPE=Release", + "-DCMAKE_INSTALL_PREFIX={0}".format(installdir), + "-D{0}=OFF".format("GEOS_ENABLE_TESTS" if version < (3, 8, 0) + else "BUILD_TESTING") ] - if self.version_tuple < (3, 8, 0): - config_opts += ["-DGEOS_ENABLE_TESTS=OFF"] - else: - config_opts += ["-DBUILD_TESTING=OFF"] - if os.name == "nt" and self.version_tuple < (3, 6, 0): - config_opts = ["-G", "NMake Makefiles"] + config_opts - - # Define build options. - build_env = os.environ.copy() build_opts = [ "--config", "Release", "--target", "install", ] + build_env = os.environ.copy() + + # Define custom configure and build options. if os.name != "nt": build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) - elif self.version_tuple < (3, 6, 0): + elif version < (3, 6, 0): win64 = (8 * struct.calcsize("P") == 64) + config_opts = ["-G", "NMake Makefiles"] + config_opts build_opts.extend([ "--", "WIN64={0}".format("YES" if win64 else "NO"), From df26b51000d5d669f3c022a04a1875454802004d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 15:04:10 +0200 Subject: [PATCH 10/19] Simplify subprocess blocks in GeosLibrary.build --- packages/basemap/utils/GeosLibrary.py | 33 +++++++++++---------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index 909974bfb..7bf6e867c 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -218,24 +218,17 @@ def build(self, installdir=None, njobs=1): else: build_opts = ["-j", "{0:d}".format(njobs)] + build_opts - # Now move to the GEOS source code folder and build with CMake. - cwd = os.getcwd() + # Call cmake configure after ensuring that the build directory exists. try: - # Ensure that the build directory exists. - try: - os.makedirs(builddir) - except OSError: - pass - os.chdir(builddir) - # Call cmake configure. - subprocess.call(["cmake", ".."] + config_opts) - # Ensure that the install directory exists. - try: - os.makedirs(installdir) - except OSError: - pass - # Call cmake build and install. - subprocess.call(["cmake", "--build", "."] + build_opts, - env=build_env) - finally: - os.chdir(cwd) + os.makedirs(builddir) + except OSError: + pass + subprocess.call(["cmake", ".."] + config_opts, cwd=builddir) + + # Call cmake build after ensuring that the install directory exists. + try: + os.makedirs(installdir) + except OSError: + pass + subprocess.call(["cmake", "--build", "."] + build_opts, + cwd=builddir, env=build_env) From 8a0d01e2e0a6ff0ea9a84a177a762f97ddf9764f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 21:46:40 +0200 Subject: [PATCH 11/19] Fix Antarctica polygon workaround for GEOS >= 3.9.0 --- packages/basemap/src/mpl_toolkits/basemap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index 653813f34..e89bd9504 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -1336,7 +1336,7 @@ def _readboundarydata(self,name,as_polygons=False): b2 = b.copy() # fix Antartica. if name == 'gshhs' and south < -89: - b = b[4:,:] + b = b[3:,:] b2 = b.copy() poly = Shape(b) # if map boundary polygon is a valid one in lat/lon From 5b879d4df8a30a623f9914c6e08fc32ed8daedcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 22:36:07 +0200 Subject: [PATCH 12/19] Fix GeosLibrary to work with GEOS 3.7.x/3.8.x --- packages/basemap/utils/GeosLibrary.py | 78 ++++++++++++++++----------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index 7bf6e867c..b969413b1 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -137,7 +137,26 @@ def extract(self, overwrite=True): for path in sorted(glob.glob(os.path.join(zipfold, "tools", "*.sh"))): os.chmod(path, 0o755) - # Patch CMakeLists so that libgeos_c.so does not depend on libgeos.so. + # Apply specific patches for GEOS < 3.6.0. + if self.version_tuple < (3, 6, 0): + # The SVN revision file is not created on the fly before 3.6.0. + svn_hfile = os.path.join(zipfold, "geos_svn_revision.h") + if not os.path.exists(svn_hfile): + with io.open(svn_hfile, "wb") as fd: + text = "#define GEOS_SVN_REVISION 0" + fd.write(text.encode()) + # Reduce warnings when compiling with `nmake` on Windows. + cmakefile = os.path.join(zipfold, "CMakeLists.txt") + if os.path.exists(cmakefile): + with io.open(cmakefile, "r", encoding="utf-8") as fd: + lines = fd.readlines() + with io.open(cmakefile, "wb") as fd: + oldtext = 'string(REGEX REPLACE "/W[0-9]" "/W4"' + newtext = oldtext.replace("W4", "W1") + for line in lines: + fd.write(line.replace(oldtext, newtext).encode()) + + # Patch CMakeLists to link shared geos_c with static geos. if self.version_tuple < (3, 8, 0): cmakefile = os.path.join(zipfold, "capi", "CMakeLists.txt") oldtext = "target_link_libraries(geos_c geos)" @@ -158,24 +177,20 @@ def extract(self, overwrite=True): found_sharedline = True fd.write(line.replace(oldtext, newtext).encode()) - # Apply specific patches for GEOS < 3.6.0. - if self.version_tuple < (3, 6, 0): - # The SVN revision file is not created on the fly before 3.6.0. - svn_hfile = os.path.join(zipfold, "geos_svn_revision.h") - if not os.path.exists(svn_hfile): - with io.open(svn_hfile, "wb") as fd: - text = "#define GEOS_SVN_REVISION 0" - fd.write(text.encode()) - # Reduce warnings when compiling with `nmake` on Windows. - cmakefile = os.path.join(zipfold, "CMakeLists.txt") - if os.path.exists(cmakefile): - with io.open(cmakefile, "r", encoding="utf-8") as fd: - lines = fd.readlines() - with io.open(cmakefile, "wb") as fd: - oldtext = 'string(REGEX REPLACE "/W[0-9]" "/W4"' - newtext = oldtext.replace("W4", "W1") - for line in lines: - fd.write(line.replace(oldtext, newtext).encode()) + # Patch doc CMakeLists in GEOS 3.8.x series. + if (3, 8, 0) <= self.version_tuple < (3, 9, 0): + cmakefile = os.path.join(zipfold, "doc", "CMakeLists.txt") + oldtext1 = "target_include_directories(test_geos_unit\n" + newtext1 = "if(BUILD_TESTING)\n {0}".format(oldtext1) + oldtext2 = "$)\n" + newtext2 = "{0}endif()\n".format(oldtext2) + with io.open(cmakefile, "r", encoding="utf-8") as fd: + lines = fd.readlines() + with io.open(cmakefile, "wb") as fd: + for line in lines: + line = line.replace(oldtext1, newtext1) + line = line.replace(oldtext2, newtext2) + fd.write(line.encode()) def build(self, installdir=None, njobs=1): """Build and install GEOS from source.""" @@ -205,18 +220,21 @@ def build(self, installdir=None, njobs=1): build_env = os.environ.copy() # Define custom configure and build options. - if os.name != "nt": - build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) - elif version < (3, 6, 0): - win64 = (8 * struct.calcsize("P") == 64) - config_opts = ["-G", "NMake Makefiles"] + config_opts - build_opts.extend([ - "--", - "WIN64={0}".format("YES" if win64 else "NO"), - "BUILD_BATCH={0}".format("YES" if njobs > 1 else "NO"), - ]) + if os.name == "nt": + if version < (3, 6, 0): + win64 = (8 * struct.calcsize("P") == 64) + config_opts = ["-G", "NMake Makefiles"] + config_opts + build_opts.extend([ + "--", + "WIN64={0}".format("YES" if win64 else "NO"), + "BUILD_BATCH={0}".format("YES" if njobs > 1 else "NO"), + ]) + else: + build_opts = ["-j", "{0:d}".format(njobs)] + build_opts else: - build_opts = ["-j", "{0:d}".format(njobs)] + build_opts + build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) + if version >= (3, 7, 0): + config_opts += ["-DCMAKE_CXX_FLAGS=-fPIC"] # Call cmake configure after ensuring that the build directory exists. try: From 1124d08ea18bd9686a048cccbef498a7bf704985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Sun, 23 Oct 2022 23:40:51 +0200 Subject: [PATCH 13/19] Backport extra patches in GeosLibrary from develop branch --- packages/basemap/utils/GeosLibrary.py | 32 ++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index b969413b1..7cdaab265 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -20,6 +20,7 @@ import io import os +import sys import ssl import glob import shutil @@ -126,8 +127,7 @@ def extract(self, overwrite=True): if os.path.exists(zipfold): if not overwrite: raise OSError("folder '{0}' already exists".format(zipfold)) - else: - shutil.rmtree(zipfold) + shutil.rmtree(zipfold) # Decompress zip file. with contextlib.closing(ZipFile(zippath, "r")) as fd: @@ -156,6 +156,27 @@ def extract(self, overwrite=True): for line in lines: fd.write(line.replace(oldtext, newtext).encode()) + # Apply specific patches for 3.6.0 <= GEOS < 3.7.0 on Windows. + if (3, 6, 0) <= self.version_tuple < (3, 7, 0) and os.name == "nt": + autogen_file = os.path.join(zipfold, "autogen.bat") + subprocess.call([autogen_file], cwd=zipfold) + cppfile = os.path.join(zipfold, "src", "geomgraph", "DirectedEdgeStar.cpp") + with io.open(cppfile, "r", encoding="utf-8") as fd: + lines = fd.readlines() + with io.open(cppfile, "wb") as fd: + oldtext = "DirectedEdgeStar::print() const" + newtext = oldtext.replace(" const", "") + for line in lines: + fd.write(line.replace(oldtext, newtext).encode()) + hfile = os.path.join(zipfold, "include", "geos", "geomgraph", "DirectedEdgeStar.h") + with io.open(hfile, "r", encoding="utf-8") as fd: + lines = fd.readlines() + with io.open(hfile, "wb") as fd: + oldtext = "virtual std::string print() const;" + newtext = oldtext.replace(" const", "") + for line in lines: + fd.write(line.replace(oldtext, newtext).encode()) + # Patch CMakeLists to link shared geos_c with static geos. if self.version_tuple < (3, 8, 0): cmakefile = os.path.join(zipfold, "capi", "CMakeLists.txt") @@ -221,16 +242,17 @@ def build(self, installdir=None, njobs=1): # Define custom configure and build options. if os.name == "nt": - if version < (3, 6, 0): + if version >= (3, 6, 0) and sys.version_info[:2] >= (3, 3): + build_opts = ["-j", "{0:d}".format(njobs)] + build_opts + else: win64 = (8 * struct.calcsize("P") == 64) config_opts = ["-G", "NMake Makefiles"] + config_opts build_opts.extend([ "--", "WIN64={0}".format("YES" if win64 else "NO"), "BUILD_BATCH={0}".format("YES" if njobs > 1 else "NO"), + "MSVC_VER=1400", ]) - else: - build_opts = ["-j", "{0:d}".format(njobs)] + build_opts else: build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) if version >= (3, 7, 0): From b3be0218bc75680058b3e68627300996d6f483d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 00:01:13 +0200 Subject: [PATCH 14/19] Make GEOS CMake-based compilation on Windows quiet --- packages/basemap/utils/GeosLibrary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index 7cdaab265..57c26d033 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -242,6 +242,7 @@ def build(self, installdir=None, njobs=1): # Define custom configure and build options. if os.name == "nt": + config_opts += ["-DCMAKE_CXX_FLAGS='/wd4251 /wd4458 /wd4530'"] if version >= (3, 6, 0) and sys.version_info[:2] >= (3, 3): build_opts = ["-j", "{0:d}".format(njobs)] + build_opts else: @@ -256,7 +257,7 @@ def build(self, installdir=None, njobs=1): else: build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) if version >= (3, 7, 0): - config_opts += ["-DCMAKE_CXX_FLAGS=-fPIC"] + config_opts += ["-DCMAKE_CXX_FLAGS='-fPIC'"] # Call cmake configure after ensuring that the build directory exists. try: From 21d16446d2a26855bc850a5fe92159c4c23dff1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 00:15:05 +0200 Subject: [PATCH 15/19] Fix wrong value for MSVC_VER in GeosLibrary --- packages/basemap/utils/GeosLibrary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/basemap/utils/GeosLibrary.py b/packages/basemap/utils/GeosLibrary.py index 57c26d033..84eff4e51 100644 --- a/packages/basemap/utils/GeosLibrary.py +++ b/packages/basemap/utils/GeosLibrary.py @@ -252,8 +252,9 @@ def build(self, installdir=None, njobs=1): "--", "WIN64={0}".format("YES" if win64 else "NO"), "BUILD_BATCH={0}".format("YES" if njobs > 1 else "NO"), - "MSVC_VER=1400", ]) + if sys.version_info[:2] < (3, 3): + build_opts += ["MSVC_VER=1500"] else: build_env["MAKEFLAGS"] = "-j {0:d}".format(njobs) if version >= (3, 7, 0): From 3bb1da45966465ebc356ea9b30a133110eb5f68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 17:29:55 +0200 Subject: [PATCH 16/19] Update CHANGELOG with bugfix for GEOS issue --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4415f45..688197773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ https://semver.org/spec/v2.0.0.html ### Fixed - Fix broken array slicing inside `addcyclic` (PR [#559], solves issue [#555], thanks to @fragkoul). +- Fix wrong Antarctica coastline boundary with GEOS >= 3.9.0 (PR [#560], + solves issue [#522]). ## [1.3.4] - 2022-08-10 @@ -930,6 +932,8 @@ https://semver.org/spec/v2.0.0.html - Fix glitches in drawing of parallels and meridians. +[#560]: +https://github.com/matplotlib/basemap/pull/560 [#559]: https://github.com/matplotlib/basemap/pull/559 [#555]: @@ -966,6 +970,8 @@ https://github.com/matplotlib/basemap/issues/527 https://github.com/matplotlib/basemap/issues/526 [#525]: https://github.com/matplotlib/basemap/issues/525 +[#522]: +https://github.com/matplotlib/basemap/issues/522 [#521]: https://github.com/matplotlib/basemap/issues/521 [#518]: From 0576e3c51174efbae24756d7b10cd61a8ff4ca1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 17:31:54 +0200 Subject: [PATCH 17/19] Add increase in GeosLibrary GEOS version support to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 688197773..947fe9c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ https://semver.org/spec/v2.0.0.html ### Fixed - Fix broken array slicing inside `addcyclic` (PR [#559], solves issue [#555], thanks to @fragkoul). +- Fix `GeosLibrary` wrapper to also work with GEOS >= 3.7.0 on Windows + and GNU/Linux. - Fix wrong Antarctica coastline boundary with GEOS >= 3.9.0 (PR [#560], solves issue [#522]). From c20a6a5bf90eacd3f9ba0a76cd12929c3843811d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 19:42:55 +0200 Subject: [PATCH 18/19] Backport GitHub workflow upgrade from develop branch --- .github/workflows/basemap-data-hires.yml | 4 ++- .github/workflows/basemap-data.yml | 4 ++- .github/workflows/basemap-for-manylinux.yml | 4 ++- .github/workflows/basemap-for-windows.yml | 39 ++++++++------------- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/.github/workflows/basemap-data-hires.yml b/.github/workflows/basemap-data-hires.yml index 6f6c53687..fbd1a9eb5 100644 --- a/.github/workflows/basemap-data-hires.yml +++ b/.github/workflows/basemap-data-hires.yml @@ -2,6 +2,8 @@ name: basemap-data-hires env: PKGDIR: "packages/basemap_data_hires" + PYTHONWARNINGS: "ignore:DEPRECATION" + PIP_DISABLE_PIP_VERSION_CHECK: "1" on: push: @@ -21,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Upload checkout uses: actions/upload-artifact@v1 diff --git a/.github/workflows/basemap-data.yml b/.github/workflows/basemap-data.yml index 51e0aed96..c63c9a5d8 100644 --- a/.github/workflows/basemap-data.yml +++ b/.github/workflows/basemap-data.yml @@ -2,6 +2,8 @@ name: basemap-data env: PKGDIR: "packages/basemap_data" + PYTHONWARNINGS: "ignore:DEPRECATION" + PIP_DISABLE_PIP_VERSION_CHECK: "1" on: push: @@ -21,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Upload checkout uses: actions/upload-artifact@v1 diff --git a/.github/workflows/basemap-for-manylinux.yml b/.github/workflows/basemap-for-manylinux.yml index f66b31b5e..c7e0d83f9 100644 --- a/.github/workflows/basemap-for-manylinux.yml +++ b/.github/workflows/basemap-for-manylinux.yml @@ -2,6 +2,8 @@ name: basemap-for-manylinux env: PKGDIR: "packages/basemap" + PYTHONWARNINGS: "ignore:DEPRECATION" + PIP_DISABLE_PIP_VERSION_CHECK: "1" on: push: @@ -21,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Upload checkout uses: actions/upload-artifact@v1 diff --git a/.github/workflows/basemap-for-windows.yml b/.github/workflows/basemap-for-windows.yml index c6acc57a6..d9e68ce25 100644 --- a/.github/workflows/basemap-for-windows.yml +++ b/.github/workflows/basemap-for-windows.yml @@ -2,6 +2,8 @@ name: basemap-for-windows env: PKGDIR: "packages/basemap" + PYTHONWARNINGS: "ignore:DEPRECATION" + PIP_DISABLE_PIP_VERSION_CHECK: "1" on: push: @@ -21,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Upload checkout uses: actions/upload-artifact@v1 @@ -47,7 +49,7 @@ jobs: path: . - name: Set Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: architecture: ${{ matrix.arch }} python-version: ${{ matrix.python-version }} @@ -85,23 +87,10 @@ jobs: build-geos: strategy: matrix: - include: - - - arch: "x64" - msvc-toolset: "14.16" - cmake-version: "3.14.7" - - - arch: "x86" - msvc-toolset: "14.16" - cmake-version: "3.13.2" - - - arch: "x64" - msvc-toolset: "9.0" - cmake-version: "3.14.7" - - - arch: "x86" - msvc-toolset: "9.0" - cmake-version: "3.13.2" + arch: + ["x64", "x86"] + msvc-toolset: + ["9.0", "14.16"] max-parallel: 4 fail-fast: false needs: lint @@ -121,12 +110,12 @@ jobs: version: ${{ matrix.msvc-toolset }} - name: Set CMake - uses: jwlawson/actions-setup-cmake@v1.9 + uses: jwlawson/actions-setup-cmake@v1.13 with: - cmake-version: ${{ matrix.cmake-version }} + cmake-version: "3.14.7" - name: Set Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: architecture: ${{ matrix.arch }} python-version: "3.6" @@ -176,7 +165,7 @@ jobs: version: ${{ env.msvc-toolset }} - name: Set Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: architecture: ${{ matrix.arch }} python-version: ${{ matrix.python-version }} @@ -229,7 +218,7 @@ jobs: steps: - name: Set Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: architecture: ${{ matrix.arch }} python-version: ${{ matrix.python-version }} @@ -264,7 +253,7 @@ jobs: steps: - name: Set Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: architecture: ${{ matrix.arch }} python-version: ${{ matrix.python-version }} From a8c0e865636e965071d90b146ee7e20432c54578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Molina=20Garc=C3=ADa?= Date: Mon, 24 Oct 2022 21:58:05 +0200 Subject: [PATCH 19/19] Prepare for hotfix release 1.3.5 --- CHANGELOG.md | 6 ++++-- packages/basemap/setup.py | 2 +- packages/basemap/src/mpl_toolkits/basemap/__init__.py | 5 +++-- packages/basemap/src/mpl_toolkits/basemap/proj.py | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 947fe9c5f..2925cd18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ https://keepachangelog.com/en/1.0.0/ https://semver.org/spec/v2.0.0.html -## [1.3.5] +## [1.3.5] - 2022-10-25 ### Fixed - Fix broken array slicing inside `addcyclic` (PR [#559], solves issue @@ -1020,7 +1020,9 @@ https://github.com/matplotlib/basemap/issues/228 https://github.com/matplotlib/basemap/issues/179 [Unreleased]: -https://github.com/matplotlib/basemap/compare/v1.3.4...develop +https://github.com/matplotlib/basemap/compare/v1.3.5...develop +[1.3.5]: +https://github.com/matplotlib/basemap/compare/v1.3.4...v1.3.5 [1.3.4]: https://github.com/matplotlib/basemap/compare/v1.3.3...v1.3.4 [1.3.3]: diff --git a/packages/basemap/setup.py b/packages/basemap/setup.py index 19588efe7..88b36dd82 100644 --- a/packages/basemap/setup.py +++ b/packages/basemap/setup.py @@ -172,7 +172,7 @@ def run(self): "name": "basemap", "version": - "1.3.4", + "1.3.5", "license": "MIT", "description": diff --git a/packages/basemap/src/mpl_toolkits/basemap/__init__.py b/packages/basemap/src/mpl_toolkits/basemap/__init__.py index e89bd9504..ea37e2de0 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/__init__.py +++ b/packages/basemap/src/mpl_toolkits/basemap/__init__.py @@ -53,6 +53,9 @@ import _geoslib import functools + +__version__ = "1.3.5" + # basemap data files now installed in lib/matplotlib/toolkits/basemap/data # check to see if environment variable BASEMAPDATA set to a directory, # and if so look for the data there. @@ -64,8 +67,6 @@ from mpl_toolkits import basemap_data basemap_datadir = os.path.abspath(list(basemap_data.__path__)[0]) -__version__ = "1.3.4" - # module variable that sets the default value for the 'latlon' kwarg. # can be set to True by user so plotting functions can take lons,lats # in degrees by default, instead of x,y (map projection coords in meters). diff --git a/packages/basemap/src/mpl_toolkits/basemap/proj.py b/packages/basemap/src/mpl_toolkits/basemap/proj.py index 047b5183a..e57dce5a2 100644 --- a/packages/basemap/src/mpl_toolkits/basemap/proj.py +++ b/packages/basemap/src/mpl_toolkits/basemap/proj.py @@ -10,7 +10,9 @@ # as textwrap.dedent. from matplotlib.cbook import dedent -__version__ = "1.3.4" + +__version__ = "1.3.5" + _dg2rad = math.radians(1.) _rad2dg = math.degrees(1.)