From 1462fc1a4066081962165035c6dc2a1d24ac7dd1 Mon Sep 17 00:00:00 2001 From: tsutterley Date: Tue, 7 Nov 2023 16:12:02 -0800 Subject: [PATCH] feat: add basic displays for widgets feat: add full screen control option for leaflet map feat: add static method for default mosaic fields fix: replace deprecated `pkg_resources` with `importlib` refactor: leaflet scatter as accessor refactor: transect plot as accessor refactor: can enter atl03 fields as transect dataframe refactor: add prior geodata function back with deprecation warning --- clients/python/sliderule/ipysliderule.py | 559 ++++++++++++++++++++++- clients/python/sliderule/raster.py | 4 +- clients/python/sliderule/version.py | 16 +- 3 files changed, 555 insertions(+), 24 deletions(-) diff --git a/clients/python/sliderule/ipysliderule.py b/clients/python/sliderule/ipysliderule.py index 2caab93f9..4a73778b2 100644 --- a/clients/python/sliderule/ipysliderule.py +++ b/clients/python/sliderule/ipysliderule.py @@ -78,6 +78,9 @@ def __init__(self, **kwargs): kwargs.setdefault('style', {}) # set style self.style = copy.copy(kwargs['style']) + # pass through some ipywidgets objects + self.HBox = ipywidgets.HBox + self.VBox = ipywidgets.VBox # dropdown menu for setting asset self.asset = ipywidgets.Dropdown( @@ -91,7 +94,7 @@ def __init__(self, **kwargs): # dropdown menu for ICESat-2 product self.product = ipywidgets.Dropdown( - options=['ATL03','ATL06','ATL08'], + options=['ATL03', 'ATL06', 'ATL08'], value='ATL03', description='Product:', description_tooltip=("Product: ICESat-2 data product " @@ -127,7 +130,7 @@ def __init__(self, **kwargs): ) # multiple select for photon classification - class_options = ['atl03','quality','atl08','yapc'] + class_options = ['atl03', 'quality', 'atl08', 'yapc'] self.classification = ipywidgets.SelectMultiple( options=class_options, value=['atl03','atl08'], @@ -723,6 +726,63 @@ def set_atl06_defaults(self): self.file = copy.copy(self.atl06_filename) self.savelabel.value = self.file + def atl03(self, **kwargs): + """returns a list of widgets for SlideRule ATL03 requests + """ + kwargs.setdefault('display', 'advanced') + assert str(kwargs['display']).lower() in ['advanced','basic'] + if (str(kwargs['display']).lower() == 'basic'): + return [ + self.start_date, + self.end_date, + self.surface_type, + ] + else: + return [ + self.start_date, + self.end_date, + self.classification, + self.surface_type, + self.confidence, + self.quality, + self.land_class, + self.yapc_knn, + self.yapc_win_h, + self.yapc_win_x, + self.yapc_min_ph, + ] + + def atl06(self, **kwargs): + """returns a list of widgets for SlideRule ATL06 requests + """ + kwargs.setdefault('display', 'advanced') + assert str(kwargs['display']).lower() in ['advanced','basic'] + if (str(kwargs['display']).lower() == 'basic'): + return [ + self.surface_type, + self.length, + ] + else: + return [ + self.classification, + self.surface_type, + self.confidence, + self.quality, + self.land_class, + self.yapc_knn, + self.yapc_win_h, + self.yapc_win_x, + self.yapc_min_ph, + self.yapc_weight, + self.length, + self.step, + self.iteration, + self.spread, + self.count, + self.window, + self.sigma, + ] + @property def time_start(self): """start time in ISO format @@ -1074,6 +1134,12 @@ def set_values(self, parms): # update values return self + @property + def column_name(self): + """Column name from variable + """ + return self.variable.value + @property def RGT(self): """extract and verify Reference Ground Tracks (RGTs) @@ -1135,6 +1201,13 @@ def orbital_cycle(self): logging.critical(f"Cycle {self.cycle.value} is outside available range") return "0" + @property + def plot_kwargs(self): + """return the plot keywords + """ + return dict(column_name=self.column_name, cycle=self.orbital_cycle, + RGT=self.RGT, GT=self.GT, LR=self.LR, PT=self.PT) + def plot(self, gdf=None, **kwargs): """Creates plots of SlideRule outputs @@ -1632,25 +1705,29 @@ class leaflet: def __init__(self, projection, **kwargs): # set default keyword arguments kwargs.setdefault('map',None) - kwargs.setdefault('prefer_canvas',False) - kwargs.setdefault('attribution',False) - kwargs.setdefault('zoom_control',False) - kwargs.setdefault('scale_control',False) - kwargs.setdefault('cursor_control',True) - kwargs.setdefault('layer_control',True) - kwargs.setdefault('center',(39,-108)) - kwargs.setdefault('color','green') + kwargs.setdefault('prefer_canvas', False) + kwargs.setdefault('attribution', False) + kwargs.setdefault('zoom_control', False) + kwargs.setdefault('scale_control', False) + kwargs.setdefault('full_screen_control', False) + kwargs.setdefault('cursor_control', True) + kwargs.setdefault('layer_control', True) + kwargs.setdefault('color', 'green') # create basemap in projection if (projection == 'Global'): + kwargs.setdefault('center', (39,-108)) + kwargs.setdefault('zoom', 9) self.map = ipyleaflet.Map(center=kwargs['center'], - zoom=9, max_zoom=15, world_copy_jump=True, + zoom=kwargs['zoom'], max_zoom=15, world_copy_jump=True, prefer_canvas=kwargs['prefer_canvas'], attribution_control=kwargs['attribution'], basemap=ipyleaflet.basemaps.Esri.WorldTopoMap) self.crs = 'EPSG:3857' elif (projection == 'North'): - self.map = ipyleaflet.Map(center=(90,0), - zoom=5, max_zoom=24, + kwargs.setdefault('center', (90,0)) + kwargs.setdefault('zoom', 5) + self.map = ipyleaflet.Map(center=kwargs['center'], + zoom=kwargs['zoom'], max_zoom=24, prefer_canvas=kwargs['prefer_canvas'], attribution_control=kwargs['attribution'], basemap=basemaps.Esri.ArcticOceanBase, @@ -1660,8 +1737,10 @@ def __init__(self, projection, **kwargs): self.map.add(ipyleaflet.basemap_to_tiles(reference)) self.crs = 'EPSG:5936' elif (projection == 'South'): - self.map = ipyleaflet.Map(center=(-90,0), - zoom=2, max_zoom=9, + kwargs.setdefault('center', (-90,0)) + kwargs.setdefault('zoom', 2) + self.map = ipyleaflet.Map(center=kwargs['center'], + zoom=kwargs['zoom'], max_zoom=9, prefer_canvas=kwargs['prefer_canvas'], attribution_control=kwargs['attribution'], basemap=basemaps.Esri.AntarcticBasemap, @@ -1669,8 +1748,13 @@ def __init__(self, projection, **kwargs): self.crs = 'EPSG:3031' else: # use a predefined ipyleaflet map + assert kwargs['map'], 'Leaflet map needs to be defined' self.map = kwargs['map'] self.crs = self.map.crs['name'] + # add control for full screen + if kwargs['full_screen_control']: + self.full_screen_control = ipyleaflet.FullScreenControl() + self.map.add(self.full_screen_control) # add control for layers if kwargs['layer_control']: self.layer_control = ipyleaflet.LayersControl(position='topleft') @@ -1715,8 +1799,9 @@ def __init__(self, projection, **kwargs): self.colorbar = None # initialize hover control self.hover_control = None - # initialize selected feature + # initialize feature callbacks self.selected_callback = None + self.region_callback = None # add sliderule regions to map def add_region(self, regions, **kwargs): @@ -1895,6 +1980,8 @@ def handle_draw(self, obj, action, geo_json): self.regions.append(region) elif (action == 'deleted'): self.regions.remove(region) + if self.region_callback is not None: + self.region_callback(action) # remove any prior instances of a data layer if (action == 'deleted') and self.geojson is not None: self.map.remove(self.geojson) @@ -1940,6 +2027,8 @@ def GeoData(self, gdf, **kwargs): kwargs.setdefault('fields', self.default_atl06_fields()) kwargs.setdefault('colorbar', True) kwargs.setdefault('position', 'topright') + # add warning that function is deprecated + logging.critical(f"Deprecated. Will be removed in a future release") # remove any prior instances of a data layer if self.geojson is not None: self.map.remove(self.geojson) @@ -2049,6 +2138,11 @@ def add_selected_callback(self, callback): """ self.selected_callback = callback + def add_region_callback(self, callback): + """set callback for handling region actions + """ + self.region_callback = callback + # add colorbar widget to leaflet map def add_colorbar(self, **kwargs): """Creates colorbars on leaflet maps @@ -2110,3 +2204,436 @@ def default_atl06_fields(): """ return ['cycle', 'dh_fit_dx', 'gt', 'h_mean', 'h_sigma', 'rgt', 'rms_misfit', 'w_surface_window_final'] + + @staticmethod + def default_mosaic_fields(**kwargs): + kwargs.setdefault('with_flags', False) + kwargs.setdefault('zonal_stats', False) + """List of mosaic tooltip fields + """ + columns = ['time','value'] + if kwargs['with_flags']: + columns += ['flags'] + if kwargs['zonal_stats']: + columns += ['count','min','max','mean','median','stdev','mad'] + return [f'mosaic.{c}' for c in columns] + +@gpd.pd.api.extensions.register_dataframe_accessor("leaflet") +class LeafletMap: + """A geopandas GeoDataFrame extension for interactive map plotting, + based on ipyleaflet + """ + + def __init__(self, gdf): + # initialize map + self.map = None + self.crs = None + # initialize geodataframe + self._gdf = gdf + # initialize data and colorbars + self.geojson = None + self.tooltip = None + self.fields = [] + self.colorbar = None + # initialize hover control + self.hover_control = None + # initialize selected feature + self.selected_callback = None + + # add geodataframe data to leaflet map + def GeoData(self, m, **kwargs): + """Creates scatter plots of GeoDataFrames on leaflet maps + + Parameters + ---------- + m : obj, leaflet object + column_name : str, GeoDataFrame column to plot + cmap : str, matplotlib colormap + vmin : float, minimum value for normalization + vmax : float, maximum value for normalization + norm : obj, matplotlib color normalization object + radius : float, radius of scatter plot markers + fillOpacity : float, opacity of scatter plot markers + weight : float, weight of scatter plot markers + stride : int, number between successive array elements + max_plot_points : int, total number of plot markers to render + tooltip : bool, show hover tooltips + fields : list, GeoDataFrame fields to show in hover tooltips + colorbar : bool, show colorbar for rendered variable + position : str, position of colorbar on leaflet map + """ + kwargs.setdefault('column_name', 'h_mean') + kwargs.setdefault('cmap', 'viridis') + kwargs.setdefault('vmin', None) + kwargs.setdefault('vmax', None) + kwargs.setdefault('norm', None) + kwargs.setdefault('radius', 1.0) + kwargs.setdefault('fillOpacity', 0.5) + kwargs.setdefault('weight', 3.0) + kwargs.setdefault('stride', None) + kwargs.setdefault('max_plot_points', 10000) + kwargs.setdefault('tooltip', True) + kwargs.setdefault('fields', []) + kwargs.setdefault('colorbar', True) + kwargs.setdefault('position', 'topright') + # set map and map coordinate reference system + self.map = m.map + self.crs = m.crs + # remove any prior instances of a data layer + if self.geojson is not None: + self.map.remove(self.geojson) + if kwargs['stride'] is not None: + stride = np.copy(kwargs['stride']) + elif (self._gdf.shape[0] > kwargs['max_plot_points']): + stride = int(self._gdf.shape[0]//kwargs['max_plot_points']) + else: + stride = 1 + # sliced geodataframe for plotting + geodataframe = self._gdf[slice(None,None,stride)] + self.column_name = copy.copy(kwargs['column_name']) + geodataframe['data'] = geodataframe[self.column_name] + # set colorbar limits to 2-98 percentile + # if not using a defined plot range + clim = geodataframe['data'].quantile((0.02, 0.98)).values + if kwargs['vmin'] is None: + vmin = clim[0] + else: + vmin = np.copy(kwargs['vmin']) + if kwargs['vmax'] is None: + vmax = clim[-1] + else: + vmax = np.copy(kwargs['vmax']) + # create matplotlib normalization + if kwargs['norm'] is None: + norm = colors.Normalize(vmin=vmin, vmax=vmax, clip=True) + else: + norm = copy.copy(kwargs['norm']) + # normalize data to be within vmin and vmax + normalized = norm(geodataframe['data']) + # create HEX colors for each point in the dataframe + geodataframe["color"] = np.apply_along_axis(colors.to_hex, 1, + cm.get_cmap(kwargs['cmap'], 256)(normalized)) + # leaflet map point style + point_style = {key:kwargs[key] for key in ['radius','fillOpacity','weight']} + # convert to GeoJSON object + self.geojson = ipyleaflet.GeoJSON(data=geodataframe.__geo_interface__, + point_style=point_style, style_callback=self.style_callback) + # add GeoJSON object to map + self.map.add(self.geojson) + # fields for tooltip views + if kwargs['fields'] is None: + self.fields = geodataframe.columns.drop( + [geodataframe.geometry.name, "data", "color"]) + else: + self.fields = copy.copy(kwargs['fields']) + # add hover tooltips + if kwargs['tooltip']: + self.tooltip = ipywidgets.HTML() + self.tooltip.layout.margin = "0px 20px 20px 20px" + self.tooltip.layout.visibility = 'hidden' + # create widget for hover tooltips + self.hover_control = ipyleaflet.WidgetControl( + widget=self.tooltip, + position='bottomright') + self.geojson.on_hover(self.handle_hover) + self.geojson.on_msg(self.handle_mouseout) + self.geojson.on_click(self.handle_click) + # add colorbar + if kwargs['colorbar']: + self.add_colorbar(column_name=self.column_name, + cmap=kwargs['cmap'], norm=norm, + position=kwargs['position']) + + # functional call for setting colors of each point + def style_callback(self, feature): + """callback for setting marker colors + """ + return { + "fillColor": feature["properties"]["color"], + "color": feature["properties"]["color"], + } + + # functional calls for hover events + def handle_hover(self, feature, **kwargs): + """callback for creating hover tooltips + """ + # combine html strings for hover tooltip + self.tooltip.value = '{0}: {1}
'.format('id',feature['id']) + self.tooltip.value += '
'.join(['{0}: {1}'.format(field, + feature["properties"][field]) for field in self.fields]) + self.tooltip.layout.width = "220px" + self.tooltip.layout.height = "300px" + self.tooltip.layout.visibility = 'visible' + self.map.add(self.hover_control) + + def handle_mouseout(self, _, content, buffers): + """callback for removing hover tooltips upon mouseout + """ + event_type = content.get('type', '') + if event_type == 'mouseout': + self.tooltip.value = '' + self.tooltip.layout.width = "0px" + self.tooltip.layout.height = "0px" + self.tooltip.layout.visibility = 'hidden' + self.map.remove(self.hover_control) + + # functional calls for click events + def handle_click(self, feature, **kwargs): + """callback for handling mouse clicks + """ + if self.selected_callback != None: + self.selected_callback(feature) + + def add_selected_callback(self, callback): + """set callback for handling mouse clicks + """ + self.selected_callback = callback + + def handle_region(self, action, **kwargs): + """callback for handling region deletions + """ + # remove any prior instances of a data layer + if (action == 'deleted') and self.geojson is not None: + self.map.remove(self.geojson) + self.geojson = None + # remove any prior instances of a colorbar + if (action == 'deleted') and self.colorbar is not None: + self.map.remove(self.colorbar) + self.colorbar = None + + # add colorbar widget to leaflet map + def add_colorbar(self, **kwargs): + """Creates colorbars on leaflet maps + + Parameters + ---------- + column_name : str, GeoDataFrame column to plot + cmap : str, matplotlib colormap + norm : obj, matplotlib color normalization object + alpha : float, opacity of colormap + orientation : str, orientation of colorbar + position : str, position of colorbar on leaflet map + width : float, width of colorbar + height : float, height of colorbar + """ + kwargs.setdefault('column_name', 'h_mean') + kwargs.setdefault('cmap', 'viridis') + kwargs.setdefault('norm', None) + kwargs.setdefault('alpha', 1.0) + kwargs.setdefault('orientation', 'horizontal') + kwargs.setdefault('position', 'topright') + kwargs.setdefault('width', 6.0) + kwargs.setdefault('height', 0.4) + # remove any prior instances of a colorbar + if self.colorbar is not None: + self.map.remove(self.colorbar) + # colormap for colorbar + cmap = cm.get_cmap(kwargs['cmap']) + # create matplotlib colorbar + _, ax = plt.subplots(figsize=(kwargs['width'], kwargs['height'])) + cbar = matplotlib.colorbar.ColorbarBase(ax, cmap=cmap, + norm=kwargs['norm'], alpha=kwargs['alpha'], + orientation=kwargs['orientation'], + label=kwargs['column_name']) + cbar.solids.set_rasterized(True) + cbar.ax.tick_params(which='both', width=1, direction='in') + # save colorbar to in-memory png object + png = io.BytesIO() + plt.savefig(png, bbox_inches='tight', format='png') + png.seek(0) + # create output widget + output = ipywidgets.Image(value=png.getvalue(), format='png') + self.colorbar = ipyleaflet.WidgetControl(widget=output, + transparent_bg=True, position=kwargs['position']) + # add colorbar + self.map.add(self.colorbar) + plt.close() + +@gpd.pd.api.extensions.register_dataframe_accessor("transect") +class Transect: + """A geopandas GeoDataFrame extension for transect plotting + """ + + def __init__(self, gdf): + # initialize map + self.map = None + self.crs = None + # initialize geodataframe + self._gdf = gdf + # initialize data for time series plot + self._data = None + self._dist = None + self._units = None + self._longname = None + self._line = None + + def plot(self, **kwargs): + """Creates plots of SlideRule outputs + + Parameters + ---------- + ax : obj, matplotlib axes object + kind : str, kind of plot to produce + + - 'scatter' : scatter plot of along-track heights + - 'cycles' : time series plot for each orbital cycle + cmap : str, matplotlib colormap + title: str, title to use for the plot + legend: bool, title to use for the plot + legend_label: str, legend label type for 'cycles' plot + legend_frameon: bool, use a background patch for legend + column_name: str, GeoDataFrame column for 'cycles' plot + atl03: obj, ATL03 GeoDataFrame for 'scatter' plot + classification: str, ATL03 photon classification for scatter plot + + - 'atl03' : ATL03 photon confidence + - 'atl08' : ATL08 photon-level land classification + - 'yapc' : Yet Another Photon Classification photon-density + - 'none' : no classification of photons + cycle_start: int, beginning cycle for 'cycles' plot + """ + # default keyword arguments + kwargs.setdefault('ax', None) + kwargs.setdefault('kind', 'cycles') + kwargs.setdefault('cmap', 'viridis') + kwargs.setdefault('title', None) + kwargs.setdefault('legend', False) + kwargs.setdefault('legend_label','date') + kwargs.setdefault('legend_frameon',True) + kwargs.setdefault('column_name', 'h_mean') + kwargs.setdefault('atl03', None) + kwargs.setdefault('classification', None) + kwargs.setdefault('segments', True) + kwargs.setdefault('cycle_start', 3) + kwargs.setdefault('cycle', 0) + kwargs.setdefault('RGT', 0) + kwargs.setdefault('GT', 0) + # variable to plot + column = kwargs['column_name'] + RGT = int(kwargs['RGT']) + GT = int(kwargs['GT']) + # skip plot creation if no values are entered + if (RGT == 0) or (GT == 0): + return + # create figure axis + if kwargs['ax'] is None: + fig,ax = plt.subplots(num=1, figsize=(8,6)) + fig.set_facecolor('white') + fig.canvas.header_visible = False + else: + ax = kwargs['ax'] + # list of legend elements + legend_elements = [] + # different plot types + # cycles: along-track plot showing all available cycles + # scatter: plot showing a single cycle possibly with ATL03 + if (kwargs['kind'] == 'cycles'): + # for each unique cycles + for cycle in self._gdf['cycle'].unique(): + # skip cycles with significant off pointing + if (cycle < kwargs['cycle_start']): + continue + # reduce data frame to RGT, ground track and cycle + geodataframe = self._gdf[ + (self._gdf['rgt'] == RGT) & + (self._gdf['gt'] == GT) & + (self._gdf['cycle'] == cycle)] + if not any(geodataframe[column].values): + continue + # plot reduced data frame + l, = ax.plot(geodataframe['x_atc'].values, + geodataframe[column].values, + marker='.', lw=0, ms=1.5) + # create legend element for cycle + if (kwargs['legend_label'] == 'date'): + label = geodataframe.index[0].strftime('%Y-%m-%d') + elif (kwargs['legend_label'] == 'cycle'): + label = f'Cycle {cycle:0.0f}' + legend_elements.append(matplotlib.lines.Line2D([0], [0], + color=l.get_color(), lw=6, label=label)) + # add axes labels + ax.set_xlabel('Along-Track Distance [m]') + ax.set_ylabel(f'SlideRule {column}') + elif (kwargs['kind'] == 'scatter'): + # extract pair track parameters + LR = int(kwargs['LR']) + PT = int(kwargs['PT']) + # extract orbital cycle parameters + cycle = int(kwargs['cycle']) + if (kwargs['atl03'] == 'dataframe'): + # reduce entered data frame to RGT, ground track and cycle + atl03 = self._gdf[(self._gdf['rgt'] == RGT) & + (self._gdf['track'] == PT) & + (self._gdf['pair'] == LR) & + (self._gdf['cycle'] == cycle)] + elif (kwargs['atl03'] is not None): + # reduce ATL03 data frame to RGT, ground track and cycle + atl03 = kwargs['atl03'][(kwargs['atl03']['rgt'] == RGT) & + (kwargs['atl03']['track'] == PT) & + (kwargs['atl03']['pair'] == LR) & + (kwargs['atl03']['cycle'] == cycle)] + if (kwargs['classification'] == 'atl08'): + # noise, ground, canopy, top of canopy, unclassified + colormap = np.array(['c','b','g','g','y']) + classes = ['noise','ground','canopy','toc','unclassified'] + sc = ax.scatter(atl03.index.values, atl03["height"].values, + c=colormap[atl03["atl08_class"].values.astype('i')], + s=1.5, rasterized=True) + for i,lab in enumerate(classes): + element = matplotlib.lines.Line2D([0], [0], + color=colormap[i], lw=6, label=lab) + legend_elements.append(element) + elif (kwargs['classification'] == 'yapc'): + sc = ax.scatter(atl03.index.values, + atl03["height"].values, + c=atl03["yapc_score"], + cmap=kwargs['cmap'], + s=1.5, rasterized=True) + plt.colorbar(sc) + elif (kwargs['classification'] == 'atl03'): + # background, buffer, low, medium, high + colormap = np.array(['y','c','b','g','m']) + confidences = ['background','buffer','low','medium','high'] + # reduce data frame to photon classified for surface + atl03 = atl03[atl03["atl03_cnf"] >= 0] + sc = ax.scatter(atl03.index.values, atl03["height"].values, + c=colormap[atl03["atl03_cnf"].values.astype('i')], + s=1.5, rasterized=True) + for i,lab in enumerate(confidences): + element = matplotlib.lines.Line2D([0], [0], + color=colormap[i], lw=6, label=lab) + legend_elements.append(element) + elif (kwargs['atl03'] is not None): + # plot all available ATL03 points as gray + sc = ax.scatter(atl03.index.values, atl03["height"].values, + c='0.4', s=0.5, rasterized=True) + legend_elements.append(matplotlib.lines.Line2D([0], [0], + color='0.4', lw=6, label='ATL03')) + if kwargs['segments']: + geodataframe = self._gdf[ + (self._gdf['rgt'] == RGT) & + (self._gdf['gt'] == GT) & + (self._gdf['cycle'] == cycle)] + # plot reduced data frame + sc = ax.scatter(geodataframe.index.values, + geodataframe["h_mean"].values, + c='red', s=2.5, rasterized=True) + legend_elements.append(matplotlib.lines.Line2D([0], [0], + color='red', lw=6, label='ATL06-SR')) + # add axes labels + ax.set_xlabel('UTC') + ax.set_ylabel('Height (m)') + # add title + if kwargs['title']: + ax.set_title(kwargs['title']) + # create legend + if kwargs['legend']: + lgd = ax.legend(handles=legend_elements, loc=3, + frameon=kwargs['legend_frameon']) + # set legend frame to solid white + if kwargs['legend'] and kwargs['legend_frameon']: + lgd.get_frame().set_alpha(1.0) + lgd.get_frame().set_edgecolor('white') + if kwargs['ax'] is None: + # show the figure + plt.tight_layout() diff --git a/clients/python/sliderule/raster.py b/clients/python/sliderule/raster.py index 99be37397..d0057f780 100644 --- a/clients/python/sliderule/raster.py +++ b/clients/python/sliderule/raster.py @@ -87,7 +87,7 @@ def sample(asset, coordinates, parms={}): ''' # Massage Arguments if type(coordinates[0]) != list: - coorindates = [coorindates] + coordinates = [coordinates] # Perform Request rqst = {"samples": {"asset": asset, **parms}, "coordinates": coordinates} @@ -194,7 +194,7 @@ def subset(asset, extents, parms={}): ''' # Massage Arguments if type(extents[0]) != list: - coorindates = [coorindates] + extents = [extents] # Perform Request rqst = {"samples": {"asset": asset, **parms}, "extents": extents} diff --git a/clients/python/sliderule/version.py b/clients/python/sliderule/version.py index c33abca29..fa063eee7 100644 --- a/clients/python/sliderule/version.py +++ b/clients/python/sliderule/version.py @@ -1,11 +1,15 @@ #!/usr/bin/env python u""" -version.py (04/2021) -Gets semantic version number and commit hash from setuptools-scm +version.py (11/2023) +Gets version number of a package """ -from pkg_resources import get_distribution +import importlib.metadata -# get semantic version from setuptools-scm -version = get_distribution("sliderule").version +# package metadata +metadata = importlib.metadata.metadata("sliderule") +# get version +version = metadata["version"] # append "v" before the version -full_version = "v{0}".format(version) +full_version = f"v{version}" +# get project name +project_name = metadata["Name"]