From b7121b544238f2d61e62c826760e3940c5480fa2 Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 24 Oct 2023 09:58:51 -0700 Subject: [PATCH 01/27] Lidar subsetting + renderers --- analysis/conda-requirements.txt | 3 +- analysis/webservice/algorithms/Lidar.py | 372 +++++++++++++++++++++ analysis/webservice/algorithms/__init__.py | 1 + 3 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 analysis/webservice/algorithms/Lidar.py diff --git a/analysis/conda-requirements.txt b/analysis/conda-requirements.txt index e27bdeae..8efb9043 100644 --- a/analysis/conda-requirements.txt +++ b/analysis/conda-requirements.txt @@ -33,4 +33,5 @@ gdal==3.2.1 mock==4.0.3 importlib_metadata==4.11.4 #singledispatch==3.4.0.3 - +xarray +matplotlib diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py new file mode 100644 index 00000000..f48a9f4f --- /dev/null +++ b/analysis/webservice/algorithms/Lidar.py @@ -0,0 +1,372 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from io import BytesIO +from typing import Dict, Literal, Union, Tuple + +import matplotlib.pyplot as plt +import numpy as np +import xarray as xr +from webservice.NexusHandler import nexus_handler +from webservice.algorithms.NexusCalcHandler import NexusCalcHandler +from webservice.webmodel import NexusResults, NexusProcessingException +from datetime import datetime +from pytz import timezone +from itertools import zip_longest, chain +from functools import partial +import json +from tempfile import NamedTemporaryFile + + +logger = logging.getLogger(__name__) + + +# NOTE: Current implementation expects the data vars to be in separate collections named +# _ + + +EPOCH = timezone('UTC').localize(datetime(1970, 1, 1)) +NP_EPOCH = np.datetime64('1970-01-01T00:00:00') +ISO_8601 = '%Y-%m-%dT%H:%M:%S%z' + +@nexus_handler +class LidarVegetation(NexusCalcHandler): + name = "LIDAR Vegetation Data" + path = "/stv/lidar" + description = "Gets vegetation stats (canopy height, mean height, canopy cover)" + params = { + "ds": { + "name": "Dataset", + "type": "string", + "description": "The Dataset shortname to use in calculation. Required" + }, + "b": { + "name": "Bounding box", + "type": "comma-delimited float", + "description": "Minimum (Western) Longitude, Minimum (Southern) Latitude, " + "Maximum (Eastern) Longitude, Maximum (Northern) Latitude. Required." + }, + "startTime": { + "name": "Start Time", + "type": "string", + "description": "Starting time in format YYYY-MM-DDTHH:mm:ssZ or seconds since EPOCH. Required" + }, + "endTime": { + "name": "End Time", + "type": "string", + "description": "Ending time in format YYYY-MM-DDTHH:mm:ssZ or seconds since EPOCH. Required" + }, + } + singleton = True + + def __init__(self, tile_service_factory, **kwargs): + NexusCalcHandler.__init__(self, tile_service_factory, desired_projection='swath') + + def parse_arguments(self, request): + # Parse input arguments + + try: + ds = request.get_dataset()[0] + except: + raise NexusProcessingException(reason="'ds' argument is required", code=400) + + try: + start_time = request.get_start_datetime() + start_time = int((start_time - EPOCH).total_seconds()) + except: + raise NexusProcessingException( + reason="'startTime' argument is required. Can be int value seconds from epoch or string format YYYY-MM-DDTHH:mm:ssZ", + code=400) + try: + end_time = request.get_end_datetime() + end_time = int((end_time - EPOCH).total_seconds()) + except: + raise NexusProcessingException( + reason="'endTime' argument is required. Can be int value seconds from epoch or string format YYYY-MM-DDTHH:mm:ssZ", + code=400) + + if start_time > end_time: + raise NexusProcessingException( + reason="The starting time must be before the ending time. Received startTime: %s, endTime: %s" % ( + request.get_start_datetime().strftime(ISO_8601), request.get_end_datetime().strftime(ISO_8601)), + code=400) + + try: + bounding_polygon = request.get_bounding_polygon() + except: + raise NexusProcessingException(reason="Parameter 'b' is required. 'b' must be comma-delimited float " + "formatted as Minimum (Western) Longitude, Minimum (Southern) " + "Latitude, Maximum (Eastern) Longitude, Maximum (Northern) Latitude.") + + return ds, start_time, end_time, bounding_polygon + + def calc(self, computeOptions, **args): + dataset, start_time, end_time, bounding_polygon = self.parse_arguments(computeOptions) + + tile_service = self._get_tile_service() + + ds_zg = f'{dataset}_ZG' + ds_rh50 = f'{dataset}_RH050' + ds_rh98 = f'{dataset}_RH098' + ds_cc = f'{dataset}_CC' + + get_tiles = partial( + tile_service.find_tiles_in_polygon, + bounding_polygon=bounding_polygon, + start_time=start_time, + end_time=end_time + ) + + tiles_zg = get_tiles(ds=ds_zg) + tiles_rh50 = get_tiles(ds=ds_rh50) + tiles_rh98 = get_tiles(ds=ds_rh98) + tiles_cc = get_tiles(ds=ds_cc) + + logger.info(f'Matched tile counts by variable: ZG={len(tiles_zg):,}, RH050={len(tiles_rh50):,}, ' + f'RH098={len(tiles_rh98):,}, CC={len(tiles_cc):,}') + + points_zg, points_50, points_98, points_cc = [], [], [], [] + + for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): + if tile_zg: + logger.info(f'Processing ground height tile {tile_zg.tile_id}') + + if tile_50: + logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') + + if tile_98: + logger.info(f'Processing canopy height tile {tile_98.tile_id}') + + if tile_cc: + logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') + + for np_zg, np_50, np_98, np_cc in zip_longest( + tile_zg.nexus_point_generator(), + tile_50.nexus_point_generator(), + tile_98.nexus_point_generator(), + tile_cc.nexus_point_generator() + ): + p_zg = dict( + latitude=np_zg.latitude, + longitude=np_zg.longitude, + time=np_zg.time, + data=np_zg.data_vals + ) + + if isinstance(p_zg['data'], list): + p_zg['data'] = p_zg['data'][0] + + points_zg.append(p_zg) + + p_50 = dict( + latitude=np_50.latitude, + longitude=np_50.longitude, + time=np_50.time, + data=np_50.data_vals + ) + + if isinstance(p_50['data'], list): + p_50['data'] = p_50['data'][0] + + points_50.append(p_50) + + p_98 = dict( + latitude=np_98.latitude, + longitude=np_98.longitude, + time=np_98.time, + data=np_98.data_vals + ) + + if isinstance(p_98['data'], list): + p_98['data'] = p_98['data'][0] + + points_98.append(p_98) + + p_cc = dict( + latitude=np_cc.latitude, + longitude=np_cc.longitude, + time=np_cc.time, + data=np_cc.data_vals + ) + + if isinstance(p_cc['data'], list): + p_cc['data'] = p_cc['data'][0] + + points_cc.append(p_cc) + + lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + + vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) + + data_dict = {} + + for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): + if zg is not None: + key = (datetime.utcfromtimestamp(zg['time']), zg['latitude'], zg['longitude']) + + if key not in data_dict: + data_dict[key] = [zg['data'], None, None, None] + else: + data_dict[key][0] = zg['data'] + + if rh50 is not None: + key = (datetime.utcfromtimestamp(rh50['time']), rh50['latitude'], rh50['longitude']) + + if key not in data_dict: + data_dict[key] = [None, rh50['data'], None, None] + else: + data_dict[key][1] = rh50['data'] + + if rh98 is not None: + key = (datetime.utcfromtimestamp(rh98['time']), rh98['latitude'], rh98['longitude']) + + if key not in data_dict: + data_dict[key] = [None, None, rh98['data'], None] + else: + data_dict[key][2] = rh98['data'] + + if cc is not None: + key = (datetime.utcfromtimestamp(cc['time']), cc['latitude'], cc['longitude']) + + if key not in data_dict: + data_dict[key] = [None, None, None, cc['data']] + else: + data_dict[key][3] = cc['data'] / 10000 + + for i, t in enumerate(times): + for j, lat in enumerate(lats): + for k, lon in enumerate(lons): + vals_4d[i, j, k, :] = data_dict.get((t, lat, lon), [np.nan] * 4) + + ds = xr.DataArray( + data=vals_4d, + dims=['time', 'lat', 'lon', 'var'], + coords=dict( + time=(['time'], times), + lat=(['lat'], lats), + lon=(['lon'], lons), + var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + ) + ) + + ds = ds.to_dataset('var') + + return LidarResults( + results=ds, + meta=dict( + ground_height_dataset=ds_zg, + vegetation_mean_height_dataset=ds_rh50, + canopy_height_dataset=ds_rh98, + canopy_coverage_dataset=ds_cc, + start_time=times[0].strftime(ISO_8601), + end_time=times[-1].strftime(ISO_8601), + b=f'{lons[0]},{lats[0]},{lons[-1]},{lats[-1]}' + ) + ) + + +class LidarResults(NexusResults): + def __init__(self, results=None, meta=None, stats=None, computeOptions=None, status_code=200, **kwargs): + NexusResults.__init__(self, results, meta, stats, computeOptions, status_code, **kwargs) + + def meta(self): + m = NexusResults.meta(self) + + del m['shortName'] + del m['bounds'] + + return m + + def points_list(self): + points = [] + + ds = self.results() + + logger.info('Generating non-NaN points list') + + for i, t in enumerate(ds.time): + for j, lat in enumerate(ds.lat): + for k, lon in enumerate(ds.lon): + ds_point = ds.isel(time=i, lat=j, lon=k) + + zg = ds_point['ground_height'].item() + rh50 = ds_point['mean_veg_height'].item() + rh98 = ds_point['canopy_height'].item() + cc = ds_point['canopy_coverage'].item() + + if all([np.isnan(v) for v in [zg, rh50, rh98, cc]]): + continue + + point_ts = (t.data - NP_EPOCH) / np.timedelta64(1, 's') + + points.append(dict( + latitude=lat.item(), + longitude=lon.item(), + time=point_ts, + time_iso=datetime.utcfromtimestamp(point_ts).strftime(ISO_8601), + ground_height=zg, + mean_vegetation_height=rh50, + canopy_height=rh98, + canopy_coverage=cc + )) + + return points + + def toImage(self): + pass + + def toJson(self): + points = self.points_list() + + logger.info('Dumping to JSON string') + + return json.dumps(dict( + meta=self.meta(), + data=points + ), indent=4) + + def toNetCDF(self): + ds: xr.Dataset = self.results() + meta = self.meta() + + ds.attrs.update(meta) + + with NamedTemporaryFile(suffix='.nc', mode='w') as fp: + comp = {"zlib": True, "complevel": 9} + encoding = {vname: comp for vname in ds.data_vars} + + ds.to_netcdf(fp.name, encoding=encoding) + + fp.flush() + + with open(fp.name, 'rb') as rfp: + buf = BytesIO(rfp.read()) + + buf.seek(0) + return buf.read() + + def toCSV(self): + pass + + def toZip(self): + pass + + +# TODO. Subset style return: ground elevation (ZG), canopy height(ZG + RH098), mean veg height(ZG + RH050), +# canopy cover (CC). Renderers for CSV, NetCDF, and PNG + diff --git a/analysis/webservice/algorithms/__init__.py b/analysis/webservice/algorithms/__init__.py index 6063009d..c4bd25d8 100644 --- a/analysis/webservice/algorithms/__init__.py +++ b/analysis/webservice/algorithms/__init__.py @@ -30,3 +30,4 @@ from . import TimeAvgMap from . import TimeSeries from . import TimeSeriesSolr +from . import Lidar From 39f95dc0b701aff0c75a2e5fd3c9deb6887ca861 Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 24 Oct 2023 10:42:08 -0700 Subject: [PATCH 02/27] PNG renderer --- analysis/webservice/algorithms/Lidar.py | 46 ++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index f48a9f4f..20aaf23a 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -328,7 +328,51 @@ def points_list(self): return points def toImage(self): - pass + ds = self.results() + meta = self.meta() + + min_lon, min_lat, max_lon, max_lat = [float(c) for c in meta['b'].split(',')] + + lat_len = max_lat - min_lat + lon_len = max_lon - min_lon + + if lat_len >= lon_len: + diff = lat_len - lon_len + + min_lon -= (diff / 2) + max_lon += (diff / 2) + else: + diff = lon_len - lat_len + + min_lat -= (diff / 2) + max_lat += (diff / 2) + + extent = [min_lon, max_lon, min_lat, max_lat] + + fig, ((rh50_ax, rh98_ax), (zg_ax, cc_ax)) = plt.subplots(2, 2, figsize=(10, 10)) + + rh50_im = rh50_ax.imshow(np.squeeze(ds['mean_veg_height']).data, extent=extent, aspect='equal', cmap='viridis') + rh98_im = rh98_ax.imshow(np.squeeze(ds['canopy_height']).data, extent=extent, aspect='equal', cmap='viridis') + zg_im = zg_ax.imshow(np.squeeze(ds['ground_height']).data, extent=extent, aspect='equal', cmap='viridis') + cc_im = cc_ax.imshow(np.squeeze(ds['canopy_coverage']).data, extent=extent, aspect='equal', cmap='viridis') + + rh50_cb = plt.colorbar(rh50_im, ax=rh50_ax, label='Height above terrain [m]') + rh98_cb = plt.colorbar(rh98_im, ax=rh98_ax, label='Height above terrain [m]') + zg_cb = plt.colorbar(zg_im, ax=zg_ax, label='Height above ellipsoid [m]') + cc_cb = plt.colorbar(cc_im, ax=cc_ax, label='Coverage [%]') + + rh50_ax.set_title('Mean Vegetation Height') + rh98_ax.set_title('Canopy Height') + zg_ax.set_title('Terrain Height') + cc_ax.set_title('Canopy Coverage') + + plt.tight_layout() + + buffer = BytesIO() + + plt.savefig(buffer, format='png', facecolor='white') + buffer.seek(0) + return buffer.read() def toJson(self): points = self.points_list() From 6c54a97e325b7de96c36a62e47e78ebb9a4a0d7c Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 24 Oct 2023 11:07:50 -0700 Subject: [PATCH 03/27] CSV and ZIP renderers --- analysis/webservice/algorithms/Lidar.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 20aaf23a..fb3c16ba 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -28,7 +28,9 @@ from itertools import zip_longest, chain from functools import partial import json +import pandas as pd from tempfile import NamedTemporaryFile +import zipfile logger = logging.getLogger(__name__) @@ -312,7 +314,7 @@ def points_list(self): if all([np.isnan(v) for v in [zg, rh50, rh98, cc]]): continue - point_ts = (t.data - NP_EPOCH) / np.timedelta64(1, 's') + point_ts = int((t.data - NP_EPOCH) / np.timedelta64(1, 's')) points.append(dict( latitude=lat.item(), @@ -405,12 +407,23 @@ def toNetCDF(self): return buf.read() def toCSV(self): - pass + df = pd.DataFrame(self.points_list()) + + buffer = BytesIO() + + df.to_csv(buffer, index=False) + + buffer.seek(0) + return buffer.read() def toZip(self): - pass + csv_results = self.toCSV() + + buffer = BytesIO() + with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip: + zip.writestr('lidar_subset.csv', csv_results) -# TODO. Subset style return: ground elevation (ZG), canopy height(ZG + RH098), mean veg height(ZG + RH050), -# canopy cover (CC). Renderers for CSV, NetCDF, and PNG + buffer.seek(0) + return buffer.read() From 91b88ce8ed1ddb50185df7519fe08e55e9154c67 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 26 Oct 2023 14:05:34 -0700 Subject: [PATCH 04/27] Added slicing params to lidar alg spec --- analysis/webservice/algorithms/Lidar.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index fb3c16ba..c24807ce 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -71,6 +71,16 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Ending time in format YYYY-MM-DDTHH:mm:ssZ or seconds since EPOCH. Required" }, + "latSlice": { + "name": "Latitude slice", + "type": "string", + "description": "Comma separated values: latitude to slice on[,min lon of slice,max lon of slice]" + }, + "lonSlice": { + "name": "Longitude slice", + "type": "string", + "description": "Comma separated values: longitude to slice on[,min lat of slice,max lat of slice]" + }, } singleton = True From 3bad8c5f721844d053e41142393741282d1e1715 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 2 Nov 2023 14:20:14 -0700 Subject: [PATCH 05/27] Improved plotting + slices --- analysis/webservice/algorithms/Lidar.py | 438 +++++++++++++++++++----- 1 file changed, 351 insertions(+), 87 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index c24807ce..26ea022f 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -31,6 +31,7 @@ import pandas as pd from tempfile import NamedTemporaryFile import zipfile +from mpl_toolkits.axes_grid1 import make_axes_locatable logger = logging.getLogger(__name__) @@ -123,10 +124,55 @@ def parse_arguments(self, request): "formatted as Minimum (Western) Longitude, Minimum (Southern) " "Latitude, Maximum (Eastern) Longitude, Maximum (Northern) Latitude.") - return ds, start_time, end_time, bounding_polygon + lat_slice = request.get_argument('latSlice', None) + lon_slice = request.get_argument('lonSlice', None) + + if lat_slice is not None: + parts = lat_slice.split(',') + + if len(parts) not in [1, 3]: + raise NexusProcessingException( + reason='latSlice must consist of either one number (lat to slice on), or 3 numbers separated by ' + 'commas (lat to slice on, min lon of slice, max lon of slice)', code=400 + ) + + try: + if len(parts) == 1: + lat_slice = (float(parts[0]), None, None) + else: + lat_slice = tuple([float(p) for p in parts]) + except ValueError: + raise NexusProcessingException( + reason='Invalid numerical component provided. latSlice must consist of either one number (lat to ' + 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' + 'slice)', code=400 + ) + + if lon_slice is not None: + parts = lon_slice.split(',') + + if len(parts) not in [1, 3]: + raise NexusProcessingException( + reason='lonSlice must consist of either one number (lon to slice on), or 3 numbers separated by ' + 'commas (lon to slice on, min lat of slice, max lat of slice)', code=400 + ) + + try: + if len(parts) == 1: + lon_slice = (float(parts[0]), None, None) + else: + lon_slice = tuple([float(p) for p in parts]) + except ValueError: + raise NexusProcessingException( + reason='Invalid numerical component provided. lonSlice must consist of either one number (lon to ' + 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' + 'slice)', code=400 + ) + + return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice def calc(self, computeOptions, **args): - dataset, start_time, end_time, bounding_polygon = self.parse_arguments(computeOptions) + dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() @@ -155,69 +201,85 @@ def calc(self, computeOptions, **args): for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): if tile_zg: logger.info(f'Processing ground height tile {tile_zg.tile_id}') + npg_zg = tile_zg.nexus_point_generator() + else: + npg_zg = [] if tile_50: logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') + npg_50 = tile_50.nexus_point_generator() + else: + npg_50 = [] if tile_98: logger.info(f'Processing canopy height tile {tile_98.tile_id}') + npg_98 = tile_98.nexus_point_generator() + else: + npg_98 = [] if tile_cc: logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') + npg_cc = tile_cc.nexus_point_generator() + else: + npg_cc = [] for np_zg, np_50, np_98, np_cc in zip_longest( - tile_zg.nexus_point_generator(), - tile_50.nexus_point_generator(), - tile_98.nexus_point_generator(), - tile_cc.nexus_point_generator() + npg_zg, + npg_50, + npg_98, + npg_cc ): - p_zg = dict( - latitude=np_zg.latitude, - longitude=np_zg.longitude, - time=np_zg.time, - data=np_zg.data_vals - ) - - if isinstance(p_zg['data'], list): - p_zg['data'] = p_zg['data'][0] - - points_zg.append(p_zg) - - p_50 = dict( - latitude=np_50.latitude, - longitude=np_50.longitude, - time=np_50.time, - data=np_50.data_vals - ) - - if isinstance(p_50['data'], list): - p_50['data'] = p_50['data'][0] - - points_50.append(p_50) - - p_98 = dict( - latitude=np_98.latitude, - longitude=np_98.longitude, - time=np_98.time, - data=np_98.data_vals - ) - - if isinstance(p_98['data'], list): - p_98['data'] = p_98['data'][0] - - points_98.append(p_98) - - p_cc = dict( - latitude=np_cc.latitude, - longitude=np_cc.longitude, - time=np_cc.time, - data=np_cc.data_vals - ) - - if isinstance(p_cc['data'], list): - p_cc['data'] = p_cc['data'][0] - - points_cc.append(p_cc) + if npg_zg: + p_zg = dict( + latitude=np_zg.latitude, + longitude=np_zg.longitude, + time=np_zg.time, + data=np_zg.data_vals + ) + + if isinstance(p_zg['data'], list): + p_zg['data'] = p_zg['data'][0] + + points_zg.append(p_zg) + + if npg_50: + p_50 = dict( + latitude=np_50.latitude, + longitude=np_50.longitude, + time=np_50.time, + data=np_50.data_vals + ) + + if isinstance(p_50['data'], list): + p_50['data'] = p_50['data'][0] + + points_50.append(p_50) + + if npg_98: + p_98 = dict( + latitude=np_98.latitude, + longitude=np_98.longitude, + time=np_98.time, + data=np_98.data_vals + ) + + if isinstance(p_98['data'], list): + p_98['data'] = p_98['data'][0] + + points_98.append(p_98) + + if npg_cc: + p_cc = dict( + latitude=np_cc.latitude, + longitude=np_cc.longitude, + time=np_cc.time, + data=np_cc.data_vals + ) + + if isinstance(p_cc['data'], list): + p_cc['data'] = p_cc['data'][0] + + points_cc.append(p_cc) lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) @@ -274,13 +336,47 @@ def calc(self, computeOptions, **args): lon=(['lon'], lons), var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) ) - ) + ).to_dataset('var') - ds = ds.to_dataset('var') + slice_lat, slice_lon = None, None + slice_min_lat, slice_max_lat = None, None + slice_min_lon, slice_max_lon = None, None - return LidarResults( - results=ds, - meta=dict( + if lat_slice is not None: + slice_lat, slice_min_lon, slice_max_lon = lat_slice + + if slice_min_lon is None: + slice_min_lon = ds.lon.min().item() + + if slice_max_lon is None: + slice_max_lon = ds.lon.max().item() + + if len(ds['time']) > 1: + slice_ds = ds.mean(dim='time', skipna=True) + else: + slice_ds = ds + + lat_slice = slice_ds.sel(lat=slice_lat, method='nearest').sel(lon=slice(slice_min_lon, slice_max_lon)) + + if lon_slice is not None: + slice_lon, slice_min_lat, slice_max_lat = lon_slice + + if slice_min_lat is None: + slice_min_lat = ds.lat.min().item() + + if slice_max_lat is None: + slice_max_lat = ds.lat.max().item() + + if len(ds['time']) > 1: + slice_ds = ds.mean(dim='time', skipna=True) + else: + slice_ds = ds + + lon_slice = slice_ds.sel(lon=slice_lon, method='nearest').sel(lat=slice(slice_min_lat, slice_max_lat)) + + slices = (lat_slice, lon_slice) + + result_meta = dict( ground_height_dataset=ds_zg, vegetation_mean_height_dataset=ds_rh50, canopy_height_dataset=ds_rh98, @@ -289,6 +385,16 @@ def calc(self, computeOptions, **args): end_time=times[-1].strftime(ISO_8601), b=f'{lons[0]},{lats[0]},{lons[-1]},{lats[-1]}' ) + + if lat_slice is not None: + result_meta['slice_lat'] = (slice_lat, slice_min_lon, slice_max_lon) + + if lon_slice is not None: + result_meta['slice_lon'] = (slice_lon, slice_min_lat, slice_max_lat) + + return LidarResults( + results=(ds, slices), + meta=result_meta ) @@ -304,10 +410,19 @@ def meta(self): return m + def results(self, reduce_time=False): + ds, (lat_slice, lon_slice) = NexusResults.results(self) + + if reduce_time and len(ds['time']) > 1: + ds = ds.mean(dim='time', skipna=True) + + return ds, (lat_slice, lon_slice) + def points_list(self): points = [] + slice_points = {} - ds = self.results() + ds, (lat_slice, lon_slice) = self.results() logger.info('Generating non-NaN points list') @@ -337,67 +452,216 @@ def points_list(self): canopy_coverage=cc )) - return points + if lat_slice is not None: + if len(lat_slice['time']) > 1: + lat_slice = lat_slice.mean(dim='time', skipna=True) + else: + lat_slice = lat_slice.squeeze(dim='time') + + pts = [dict( + longitude=lat_slice.isel(lon=l).lon.item(), + ground_height=lat_slice.ground_height.isel(lon=l).item(), + mean_vegetation_height=lat_slice.mean_veg_height.isel(lon=l).item(), + canopy_height=lat_slice.canopy_height.isel(lon=l).item(), + canopy_coverage=lat_slice.canopy_coverage.isel(lon=l).item() + ) for l in range(len(ds['lon']))] + + slice_points['latitude'] = dict(latitude=lat_slice.lat.item(), slice=pts) + + if lon_slice is not None: + if len(lon_slice['time']) > 1: + lon_slice = lon_slice.mean(dim='time', skipna=True) + else: + lon_slice = lon_slice.squeeze(dim='time') + + pts = [dict( + latitude=lon_slice.isel(lat=l).lat.item(), + ground_height=lon_slice.ground_height.isel(lat=l).item(), + mean_vegetation_height=lon_slice.mean_veg_height.isel(lat=l).item(), + canopy_height=lon_slice.canopy_height.isel(lat=l).item(), + canopy_coverage=lon_slice.canopy_coverage.isel(lat=l).item() + ) for l in range(len(ds['lat']))] + + slice_points['longitude'] = dict(longitude=lat_slice.lon.item(), slice=pts) + + return points, slice_points + + @staticmethod + def slice_point_list(ds, s, coord): + if len(ds['time']) > 1: + s = s.mean(dim='time', skipna=True) + else: + s = s.squeeze(dim='time') + + if coord == 'latitude': + pts = [dict( + s=s.isel(lon=l).lon.item(), + ground_height=s.ground_height.isel(lon=l).item(), + mean_vegetation_height=s.mean_veg_height.isel(lon=l).item(), + canopy_height=s.canopy_height.isel(lon=l).item(), + canopy_coverage=s.canopy_coverage.isel(lon=l).item() + ) for l in range(len(s['lon']))] + + return s.lat.item(), pts + else: + pts = [dict( + s=s.isel(lat=l).lat.item(), + ground_height=s.ground_height.isel(lat=l).item(), + mean_vegetation_height=s.mean_veg_height.isel(lat=l).item(), + canopy_height=s.canopy_height.isel(lat=l).item(), + canopy_coverage=s.canopy_coverage.isel(lat=l).item() + ) for l in range(len(s['lat']))] + + return s.lon.item(), pts def toImage(self): - ds = self.results() + ds, (lat_slice, lon_slice) = self.results() meta = self.meta() - min_lon, min_lat, max_lon, max_lat = [float(c) for c in meta['b'].split(',')] + slice_lat, slice_lon = meta.get('slice_lat'), meta.get('slice_lon') - lat_len = max_lat - min_lat - lon_len = max_lon - min_lon + n_rows = 2 + + if lat_slice is not None: + n_rows += 2 + if lon_slice is not None: + n_rows += 2 - if lat_len >= lon_len: - diff = lat_len - lon_len - - min_lon -= (diff / 2) - max_lon += (diff / 2) - else: - diff = lon_len - lat_len + min_lon, min_lat, max_lon, max_lat = ( + ds.lon.min().item(), + ds.lat.min().item(), + ds.lon.max().item(), + ds.lat.max().item(), + ) - min_lat -= (diff / 2) - max_lat += (diff / 2) + # lat_len = max_lat - min_lat + # lon_len = max_lon - min_lon + # + # if lat_len >= lon_len: + # diff = lat_len - lon_len + # + # min_lon -= (diff / 2) + # max_lon += (diff / 2) + # else: + # diff = lon_len - lat_len + # + # min_lat -= (diff / 2) + # max_lat += (diff / 2) extent = [min_lon, max_lon, min_lat, max_lat] - fig, ((rh50_ax, rh98_ax), (zg_ax, cc_ax)) = plt.subplots(2, 2, figsize=(10, 10)) - - rh50_im = rh50_ax.imshow(np.squeeze(ds['mean_veg_height']).data, extent=extent, aspect='equal', cmap='viridis') - rh98_im = rh98_ax.imshow(np.squeeze(ds['canopy_height']).data, extent=extent, aspect='equal', cmap='viridis') - zg_im = zg_ax.imshow(np.squeeze(ds['ground_height']).data, extent=extent, aspect='equal', cmap='viridis') - cc_im = cc_ax.imshow(np.squeeze(ds['canopy_coverage']).data, extent=extent, aspect='equal', cmap='viridis') + fig = plt.figure( + figsize=(8, 10 + (max(0, n_rows - 2) * 4)), constrained_layout=True + ) - rh50_cb = plt.colorbar(rh50_im, ax=rh50_ax, label='Height above terrain [m]') - rh98_cb = plt.colorbar(rh98_im, ax=rh98_ax, label='Height above terrain [m]') - zg_cb = plt.colorbar(zg_im, ax=zg_ax, label='Height above ellipsoid [m]') - cc_cb = plt.colorbar(cc_im, ax=cc_ax, label='Coverage [%]') + gs = fig.add_gridspec(n_rows, 2) + + rh50_ax = fig.add_subplot(gs[0, 0]) + rh98_ax = fig.add_subplot(gs[0, 1]) + zg_ax = fig.add_subplot(gs[1, 0]) + cc_ax = fig.add_subplot(gs[1, 1]) + + rh50_im = rh50_ax.imshow(np.flipud(np.squeeze(ds['mean_veg_height'])), extent=extent, aspect='equal', cmap='viridis') + rh98_im = rh98_ax.imshow(np.flipud(np.squeeze(ds['canopy_height'])), extent=extent, aspect='equal', cmap='viridis') + zg_im = zg_ax.imshow(np.flipud(np.squeeze(ds['ground_height'])), extent=extent, aspect='equal', cmap='viridis') + cc_im = cc_ax.imshow(np.flipud(np.squeeze(ds['canopy_coverage'])) * 100, extent=extent, aspect='equal', cmap='viridis', vmin=0, vmax=100) + + if slice_lat is not None: + rh50_ax.plot([slice_lat[1], slice_lat[2]], [slice_lat[0], slice_lat[0]], 'r--') + rh98_ax.plot([slice_lat[1], slice_lat[2]], [slice_lat[0], slice_lat[0]], 'r--') + zg_ax.plot([slice_lat[1], slice_lat[2]], [slice_lat[0], slice_lat[0]], 'r--') + cc_ax.plot([slice_lat[1], slice_lat[2]], [slice_lat[0], slice_lat[0]], 'r--') + + if slice_lon is not None: + rh50_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') + rh98_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') + zg_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') + cc_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') + + divider_50 = make_axes_locatable(rh50_ax) + divider_98 = make_axes_locatable(rh98_ax) + divider_zg = make_axes_locatable(zg_ax) + divider_cc = make_axes_locatable(cc_ax) + + cax_50 = divider_50.append_axes("right", size="5%", pad=0.05) + cax_98 = divider_98.append_axes("right", size="5%", pad=0.05) + cax_zg = divider_zg.append_axes("right", size="5%", pad=0.05) + cax_cc = divider_cc.append_axes("right", size="5%", pad=0.05) + + rh50_cb = plt.colorbar(rh50_im, cax=cax_50, label='Height above terrain [m]', use_gridspec=True) + rh98_cb = plt.colorbar(rh98_im, cax=cax_98, label='Height above terrain [m]', use_gridspec=True) + zg_cb = plt.colorbar(zg_im, cax=cax_zg, label='Height above ellipsoid [m]', use_gridspec=True) + cc_cb = plt.colorbar(cc_im, cax=cax_cc, label='Coverage [%]', use_gridspec=True) rh50_ax.set_title('Mean Vegetation Height') rh98_ax.set_title('Canopy Height') zg_ax.set_title('Terrain Height') cc_ax.set_title('Canopy Coverage') - plt.tight_layout() + row = 2 + + for s, coord in zip([lat_slice, lon_slice], ['latitude', 'longitude']): + if s is None: + continue + + slice_ax = fig.add_subplot(gs[row, :]) + + slice_point, pts = LidarResults.slice_point_list(ds, s, coord) + + x_pts = [p['s'] for p in pts] + rh50_pts = np.array([p['mean_vegetation_height'] for p in pts]) + rh98_pts = np.array([p['canopy_height'] for p in pts]) + zg_pts = np.array([p['ground_height'] for p in pts]) + cc_pts = np.array([p['canopy_coverage'] for p in pts]) * 100 + + slice_ax.plot( + x_pts, rh98_pts + zg_pts, + x_pts, rh50_pts + zg_pts, + x_pts, zg_pts, + ) + + slice_ax.set_title(f'Slice at {coord}={slice_point}\nHeights w.r.t. to reference ellipsoid (m)') + slice_ax.ticklabel_format(useOffset=False) + + slice_ax.legend([ + 'Canopy Height', + 'Mean Vegetation Height', + 'Ground Height', + ]) + + cc_slice_ax = fig.add_subplot(gs[row + 1, :]) + + cc_slice_ax.plot(x_pts, cc_pts) + cc_slice_ax.ticklabel_format(useOffset=False) + cc_slice_ax.set_ylim([0, 100]) + + cc_slice_ax.set_title(f'Slice at {coord}={slice_point}\nCanopy coverage (%)') + + cc_slice_ax.legend(['Canopy Coverage']) + + row += 2 buffer = BytesIO() plt.savefig(buffer, format='png', facecolor='white') buffer.seek(0) + + plt.close(fig) return buffer.read() def toJson(self): - points = self.points_list() + points, slice_points = self.points_list() logger.info('Dumping to JSON string') return json.dumps(dict( meta=self.meta(), - data=points + data=points, + slices=slice_points ), indent=4) def toNetCDF(self): - ds: xr.Dataset = self.results() + ds, (lat_slice, lon_slice) = self.results() meta = self.meta() ds.attrs.update(meta) From b4bfc8591a78c2637996e1403fad07895727690d Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 6 Nov 2023 07:34:36 -0800 Subject: [PATCH 06/27] Improved plotting + slices --- analysis/webservice/algorithms/Lidar.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 26ea022f..51cef006 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -534,20 +534,6 @@ def toImage(self): ds.lat.max().item(), ) - # lat_len = max_lat - min_lat - # lon_len = max_lon - min_lon - # - # if lat_len >= lon_len: - # diff = lat_len - lon_len - # - # min_lon -= (diff / 2) - # max_lon += (diff / 2) - # else: - # diff = lon_len - lat_len - # - # min_lat -= (diff / 2) - # max_lat += (diff / 2) - extent = [min_lon, max_lon, min_lat, max_lat] fig = plt.figure( @@ -608,6 +594,8 @@ def toImage(self): slice_point, pts = LidarResults.slice_point_list(ds, s, coord) + x_lim = [min_lon, max_lon] if coord == 'latitude' else [min_lat, max_lat] + x_pts = [p['s'] for p in pts] rh50_pts = np.array([p['mean_vegetation_height'] for p in pts]) rh98_pts = np.array([p['canopy_height'] for p in pts]) @@ -622,6 +610,7 @@ def toImage(self): slice_ax.set_title(f'Slice at {coord}={slice_point}\nHeights w.r.t. to reference ellipsoid (m)') slice_ax.ticklabel_format(useOffset=False) + slice_ax.set_xlim(x_lim) slice_ax.legend([ 'Canopy Height', @@ -634,6 +623,7 @@ def toImage(self): cc_slice_ax.plot(x_pts, cc_pts) cc_slice_ax.ticklabel_format(useOffset=False) cc_slice_ax.set_ylim([0, 100]) + cc_slice_ax.set_xlim(x_lim) cc_slice_ax.set_title(f'Slice at {coord}={slice_point}\nCanopy coverage (%)') From 5af60866c4b7f99fd7c4af327fd50bc731c5a14a Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 29 Jan 2024 10:20:01 -0800 Subject: [PATCH 07/27] Suppress overly verbose loggers instead of all loggers to INFO --- analysis/webservice/algorithms/Lidar.py | 261 +++++++++++++++++------- 1 file changed, 189 insertions(+), 72 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 51cef006..904f40f6 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -13,26 +13,25 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import logging +import zipfile +from datetime import datetime +from functools import partial from io import BytesIO -from typing import Dict, Literal, Union, Tuple +from itertools import zip_longest, chain +from tempfile import NamedTemporaryFile import matplotlib.pyplot as plt import numpy as np +import pandas as pd import xarray as xr +from mpl_toolkits.axes_grid1 import make_axes_locatable +from pytz import timezone +from scipy.interpolate import griddata from webservice.NexusHandler import nexus_handler from webservice.algorithms.NexusCalcHandler import NexusCalcHandler from webservice.webmodel import NexusResults, NexusProcessingException -from datetime import datetime -from pytz import timezone -from itertools import zip_longest, chain -from functools import partial -import json -import pandas as pd -from tempfile import NamedTemporaryFile -import zipfile -from mpl_toolkits.axes_grid1 import make_axes_locatable - logger = logging.getLogger(__name__) @@ -45,6 +44,13 @@ NP_EPOCH = np.datetime64('1970-01-01T00:00:00') ISO_8601 = '%Y-%m-%dT%H:%M:%S%z' +# Precomputed resolutions of LIDAR data grids, computed over the entire ABoVE dataset +# Temporary solution to use for grid mapping +LAT_RES_AVG = 0.0005339746629669614 +LON_RES_AVG = 0.0005339746629670938 +LAT_RES_MIN = 0.0002819728712069036 +LON_RES_MIN = 0.0002819728712069036 + @nexus_handler class LidarVegetation(NexusCalcHandler): name = "LIDAR Vegetation Data" @@ -82,6 +88,14 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Comma separated values: longitude to slice on[,min lat of slice,max lat of slice]" }, + "mapToGrid": { + "name": "Map scenes to grid", + "type": "boolean", + "description": "If multiple LIDAR captures are matched in the defined bounds, map their points to a shared " + "lat/lon grid at a similar resolution to the source data. Otherwise, the data cannot be " + "merged & rendered properly and will thus raise an error. NOTE: this option may take a long " + "time to compute. Default: false" + } } singleton = True @@ -169,10 +183,13 @@ def parse_arguments(self, request): 'slice)', code=400 ) - return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice + map_to_grid = request.get_boolean_arg('mapToGrid') + + return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid def calc(self, computeOptions, **args): - dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice = self.parse_arguments(computeOptions) + dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid\ + = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() @@ -198,28 +215,40 @@ def calc(self, computeOptions, **args): points_zg, points_50, points_98, points_cc = [], [], [], [] + sources = set() + + def source_name_from_granule(granule: str, end=-3) -> str: + return '_'.join(granule.split('_')[:end]) + + include_nan = map_to_grid # If we may need to map to grid, get nans from source tiles so we can best estimate + # grid resolution + for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): if tile_zg: logger.info(f'Processing ground height tile {tile_zg.tile_id}') - npg_zg = tile_zg.nexus_point_generator() + npg_zg = tile_zg.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_zg.granule)) else: npg_zg = [] if tile_50: logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') - npg_50 = tile_50.nexus_point_generator() + npg_50 = tile_50.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_50.granule)) else: npg_50 = [] if tile_98: logger.info(f'Processing canopy height tile {tile_98.tile_id}') - npg_98 = tile_98.nexus_point_generator() + npg_98 = tile_98.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_98.granule)) else: npg_98 = [] if tile_cc: logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') - npg_cc = tile_cc.nexus_point_generator() + npg_cc = tile_cc.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_cc.granule, -4)) else: npg_cc = [] @@ -229,12 +258,13 @@ def calc(self, computeOptions, **args): npg_98, npg_cc ): - if npg_zg: + if np_zg: p_zg = dict( latitude=np_zg.latitude, longitude=np_zg.longitude, time=np_zg.time, - data=np_zg.data_vals + data=np_zg.data_vals, + source=source_name_from_granule(tile_zg.granule) ) if isinstance(p_zg['data'], list): @@ -242,12 +272,13 @@ def calc(self, computeOptions, **args): points_zg.append(p_zg) - if npg_50: + if np_50: p_50 = dict( latitude=np_50.latitude, longitude=np_50.longitude, time=np_50.time, - data=np_50.data_vals + data=np_50.data_vals, + source=source_name_from_granule(tile_50.granule) ) if isinstance(p_50['data'], list): @@ -255,12 +286,13 @@ def calc(self, computeOptions, **args): points_50.append(p_50) - if npg_98: + if np_98: p_98 = dict( latitude=np_98.latitude, longitude=np_98.longitude, time=np_98.time, - data=np_98.data_vals + data=np_98.data_vals, + source=source_name_from_granule(tile_98.granule) ) if isinstance(p_98['data'], list): @@ -268,12 +300,13 @@ def calc(self, computeOptions, **args): points_98.append(p_98) - if npg_cc: + if np_cc: p_cc = dict( latitude=np_cc.latitude, longitude=np_cc.longitude, time=np_cc.time, - data=np_cc.data_vals + data=np_cc.data_vals, + source=source_name_from_granule(tile_cc.granule, -4) ) if isinstance(p_cc['data'], list): @@ -281,62 +314,146 @@ def calc(self, computeOptions, **args): points_cc.append(p_cc) - lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) - lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) - times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + if len(sources) == 1: + logger.info('Only one source LIDAR scene, using simple mapping to grid') - vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) + lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) - data_dict = {} + vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) - for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): - if zg is not None: - key = (datetime.utcfromtimestamp(zg['time']), zg['latitude'], zg['longitude']) + data_dict = {} - if key not in data_dict: - data_dict[key] = [zg['data'], None, None, None] - else: - data_dict[key][0] = zg['data'] + for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): + if zg is not None: + key = (datetime.utcfromtimestamp(zg['time']), zg['latitude'], zg['longitude']) - if rh50 is not None: - key = (datetime.utcfromtimestamp(rh50['time']), rh50['latitude'], rh50['longitude']) + if key not in data_dict: + data_dict[key] = [zg['data'], None, None, None] + else: + data_dict[key][0] = zg['data'] - if key not in data_dict: - data_dict[key] = [None, rh50['data'], None, None] - else: - data_dict[key][1] = rh50['data'] + if rh50 is not None: + key = (datetime.utcfromtimestamp(rh50['time']), rh50['latitude'], rh50['longitude']) - if rh98 is not None: - key = (datetime.utcfromtimestamp(rh98['time']), rh98['latitude'], rh98['longitude']) + if key not in data_dict: + data_dict[key] = [None, rh50['data'], None, None] + else: + data_dict[key][1] = rh50['data'] - if key not in data_dict: - data_dict[key] = [None, None, rh98['data'], None] - else: - data_dict[key][2] = rh98['data'] + if rh98 is not None: + key = (datetime.utcfromtimestamp(rh98['time']), rh98['latitude'], rh98['longitude']) - if cc is not None: - key = (datetime.utcfromtimestamp(cc['time']), cc['latitude'], cc['longitude']) + if key not in data_dict: + data_dict[key] = [None, None, rh98['data'], None] + else: + data_dict[key][2] = rh98['data'] - if key not in data_dict: - data_dict[key] = [None, None, None, cc['data']] - else: - data_dict[key][3] = cc['data'] / 10000 - - for i, t in enumerate(times): - for j, lat in enumerate(lats): - for k, lon in enumerate(lons): - vals_4d[i, j, k, :] = data_dict.get((t, lat, lon), [np.nan] * 4) - - ds = xr.DataArray( - data=vals_4d, - dims=['time', 'lat', 'lon', 'var'], - coords=dict( - time=(['time'], times), - lat=(['lat'], lats), - lon=(['lon'], lons), - var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + if cc is not None: + key = (datetime.utcfromtimestamp(cc['time']), cc['latitude'], cc['longitude']) + + if key not in data_dict: + data_dict[key] = [None, None, None, cc['data'] / 10000] + else: + data_dict[key][3] = cc['data'] / 10000 + + for i, t in enumerate(times): + for j, lat in enumerate(lats): + for k, lon in enumerate(lons): + vals_4d[i, j, k, :] = data_dict.get((t, lat, lon), [np.nan] * 4) + + ds = xr.DataArray( + data=vals_4d, + dims=['time', 'lat', 'lon', 'var'], + coords=dict( + time=(['time'], times), + lat=(['lat'], lats), + lon=(['lon'], lons), + var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + ) + ).to_dataset('var') + elif map_to_grid: + logger.info('More than one scene matched, will align points to a common grid') + + min_lat, max_lat = tuple( + np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)])[[0, -1]] + ) + + min_lon, max_lon = tuple( + np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)])[[0, -1]] + ) + + times = np.unique( + [datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + + logger.debug('Building gridding coordinate meshes') + + lons = np.arange(min_lon, max_lon + (LON_RES_MIN / 2), LON_RES_MIN) + lats = np.arange(min_lat, max_lat + (LAT_RES_MIN / 2), LAT_RES_MIN) + + X, Y = np.meshgrid(lons, lats) + + logger.info('Gridding ground heights') + gridded_zg = griddata( + list(zip([p['longitude'] for p in points_zg], [p['latitude'] for p in points_zg])), + np.array([p['data'] for p in points_zg]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding mean vegetation heights') + gridded_50 = griddata( + list(zip([p['longitude'] for p in points_50], [p['latitude'] for p in points_50])), + np.array([p['data'] for p in points_50]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding canopy heights') + gridded_98 = griddata( + list(zip([p['longitude'] for p in points_98], [p['latitude'] for p in points_98])), + np.array([p['data'] for p in points_98]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding canopy coverage') + gridded_cc = griddata( + list(zip([p['longitude'] for p in points_cc], [p['latitude'] for p in points_cc])), + np.array([p['data'] / 10000 for p in points_cc]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + gridded_vals = np.array([ + gridded_zg, + gridded_50, + gridded_98, + gridded_cc + ]) + + gridded_vals = np.moveaxis(gridded_vals, 0, -1)[np.newaxis, ...] + + ds = xr.DataArray( + data=gridded_vals, + dims=['time', 'lat', 'lon', 'var'], + coords=dict( + time=(['time'], [times[0]]), + lat=(['lat'], lats), + lon=(['lon'], lons), + var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + ) + ).to_dataset('var') + else: + raise NexusProcessingException( + reason='Selected bounds match multiple scenes, there is no way to merge them to a shared grid', + code=400 ) - ).to_dataset('var') slice_lat, slice_lon = None, None slice_min_lat, slice_max_lat = None, None @@ -351,7 +468,7 @@ def calc(self, computeOptions, **args): if slice_max_lon is None: slice_max_lon = ds.lon.max().item() - if len(ds['time']) > 1: + if 'time' in ds.coords and len(ds['time']) > 1: slice_ds = ds.mean(dim='time', skipna=True) else: slice_ds = ds @@ -367,7 +484,7 @@ def calc(self, computeOptions, **args): if slice_max_lat is None: slice_max_lat = ds.lat.max().item() - if len(ds['time']) > 1: + if 'time' in ds.coords and len(ds['time']) > 1: slice_ds = ds.mean(dim='time', skipna=True) else: slice_ds = ds From 4caee5006b23d0eca0fb0455a5ce577c5784f345 Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 29 Jan 2024 10:20:01 -0800 Subject: [PATCH 08/27] Fit data from multiple scenes to common grid Enabled by query option mapToGrid --- analysis/webservice/algorithms/Lidar.py | 261 +++++++++++++++++------- 1 file changed, 189 insertions(+), 72 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 51cef006..904f40f6 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -13,26 +13,25 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import logging +import zipfile +from datetime import datetime +from functools import partial from io import BytesIO -from typing import Dict, Literal, Union, Tuple +from itertools import zip_longest, chain +from tempfile import NamedTemporaryFile import matplotlib.pyplot as plt import numpy as np +import pandas as pd import xarray as xr +from mpl_toolkits.axes_grid1 import make_axes_locatable +from pytz import timezone +from scipy.interpolate import griddata from webservice.NexusHandler import nexus_handler from webservice.algorithms.NexusCalcHandler import NexusCalcHandler from webservice.webmodel import NexusResults, NexusProcessingException -from datetime import datetime -from pytz import timezone -from itertools import zip_longest, chain -from functools import partial -import json -import pandas as pd -from tempfile import NamedTemporaryFile -import zipfile -from mpl_toolkits.axes_grid1 import make_axes_locatable - logger = logging.getLogger(__name__) @@ -45,6 +44,13 @@ NP_EPOCH = np.datetime64('1970-01-01T00:00:00') ISO_8601 = '%Y-%m-%dT%H:%M:%S%z' +# Precomputed resolutions of LIDAR data grids, computed over the entire ABoVE dataset +# Temporary solution to use for grid mapping +LAT_RES_AVG = 0.0005339746629669614 +LON_RES_AVG = 0.0005339746629670938 +LAT_RES_MIN = 0.0002819728712069036 +LON_RES_MIN = 0.0002819728712069036 + @nexus_handler class LidarVegetation(NexusCalcHandler): name = "LIDAR Vegetation Data" @@ -82,6 +88,14 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Comma separated values: longitude to slice on[,min lat of slice,max lat of slice]" }, + "mapToGrid": { + "name": "Map scenes to grid", + "type": "boolean", + "description": "If multiple LIDAR captures are matched in the defined bounds, map their points to a shared " + "lat/lon grid at a similar resolution to the source data. Otherwise, the data cannot be " + "merged & rendered properly and will thus raise an error. NOTE: this option may take a long " + "time to compute. Default: false" + } } singleton = True @@ -169,10 +183,13 @@ def parse_arguments(self, request): 'slice)', code=400 ) - return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice + map_to_grid = request.get_boolean_arg('mapToGrid') + + return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid def calc(self, computeOptions, **args): - dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice = self.parse_arguments(computeOptions) + dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid\ + = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() @@ -198,28 +215,40 @@ def calc(self, computeOptions, **args): points_zg, points_50, points_98, points_cc = [], [], [], [] + sources = set() + + def source_name_from_granule(granule: str, end=-3) -> str: + return '_'.join(granule.split('_')[:end]) + + include_nan = map_to_grid # If we may need to map to grid, get nans from source tiles so we can best estimate + # grid resolution + for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): if tile_zg: logger.info(f'Processing ground height tile {tile_zg.tile_id}') - npg_zg = tile_zg.nexus_point_generator() + npg_zg = tile_zg.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_zg.granule)) else: npg_zg = [] if tile_50: logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') - npg_50 = tile_50.nexus_point_generator() + npg_50 = tile_50.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_50.granule)) else: npg_50 = [] if tile_98: logger.info(f'Processing canopy height tile {tile_98.tile_id}') - npg_98 = tile_98.nexus_point_generator() + npg_98 = tile_98.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_98.granule)) else: npg_98 = [] if tile_cc: logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') - npg_cc = tile_cc.nexus_point_generator() + npg_cc = tile_cc.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_cc.granule, -4)) else: npg_cc = [] @@ -229,12 +258,13 @@ def calc(self, computeOptions, **args): npg_98, npg_cc ): - if npg_zg: + if np_zg: p_zg = dict( latitude=np_zg.latitude, longitude=np_zg.longitude, time=np_zg.time, - data=np_zg.data_vals + data=np_zg.data_vals, + source=source_name_from_granule(tile_zg.granule) ) if isinstance(p_zg['data'], list): @@ -242,12 +272,13 @@ def calc(self, computeOptions, **args): points_zg.append(p_zg) - if npg_50: + if np_50: p_50 = dict( latitude=np_50.latitude, longitude=np_50.longitude, time=np_50.time, - data=np_50.data_vals + data=np_50.data_vals, + source=source_name_from_granule(tile_50.granule) ) if isinstance(p_50['data'], list): @@ -255,12 +286,13 @@ def calc(self, computeOptions, **args): points_50.append(p_50) - if npg_98: + if np_98: p_98 = dict( latitude=np_98.latitude, longitude=np_98.longitude, time=np_98.time, - data=np_98.data_vals + data=np_98.data_vals, + source=source_name_from_granule(tile_98.granule) ) if isinstance(p_98['data'], list): @@ -268,12 +300,13 @@ def calc(self, computeOptions, **args): points_98.append(p_98) - if npg_cc: + if np_cc: p_cc = dict( latitude=np_cc.latitude, longitude=np_cc.longitude, time=np_cc.time, - data=np_cc.data_vals + data=np_cc.data_vals, + source=source_name_from_granule(tile_cc.granule, -4) ) if isinstance(p_cc['data'], list): @@ -281,62 +314,146 @@ def calc(self, computeOptions, **args): points_cc.append(p_cc) - lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) - lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) - times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + if len(sources) == 1: + logger.info('Only one source LIDAR scene, using simple mapping to grid') - vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) + lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) + times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) - data_dict = {} + vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) - for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): - if zg is not None: - key = (datetime.utcfromtimestamp(zg['time']), zg['latitude'], zg['longitude']) + data_dict = {} - if key not in data_dict: - data_dict[key] = [zg['data'], None, None, None] - else: - data_dict[key][0] = zg['data'] + for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): + if zg is not None: + key = (datetime.utcfromtimestamp(zg['time']), zg['latitude'], zg['longitude']) - if rh50 is not None: - key = (datetime.utcfromtimestamp(rh50['time']), rh50['latitude'], rh50['longitude']) + if key not in data_dict: + data_dict[key] = [zg['data'], None, None, None] + else: + data_dict[key][0] = zg['data'] - if key not in data_dict: - data_dict[key] = [None, rh50['data'], None, None] - else: - data_dict[key][1] = rh50['data'] + if rh50 is not None: + key = (datetime.utcfromtimestamp(rh50['time']), rh50['latitude'], rh50['longitude']) - if rh98 is not None: - key = (datetime.utcfromtimestamp(rh98['time']), rh98['latitude'], rh98['longitude']) + if key not in data_dict: + data_dict[key] = [None, rh50['data'], None, None] + else: + data_dict[key][1] = rh50['data'] - if key not in data_dict: - data_dict[key] = [None, None, rh98['data'], None] - else: - data_dict[key][2] = rh98['data'] + if rh98 is not None: + key = (datetime.utcfromtimestamp(rh98['time']), rh98['latitude'], rh98['longitude']) - if cc is not None: - key = (datetime.utcfromtimestamp(cc['time']), cc['latitude'], cc['longitude']) + if key not in data_dict: + data_dict[key] = [None, None, rh98['data'], None] + else: + data_dict[key][2] = rh98['data'] - if key not in data_dict: - data_dict[key] = [None, None, None, cc['data']] - else: - data_dict[key][3] = cc['data'] / 10000 - - for i, t in enumerate(times): - for j, lat in enumerate(lats): - for k, lon in enumerate(lons): - vals_4d[i, j, k, :] = data_dict.get((t, lat, lon), [np.nan] * 4) - - ds = xr.DataArray( - data=vals_4d, - dims=['time', 'lat', 'lon', 'var'], - coords=dict( - time=(['time'], times), - lat=(['lat'], lats), - lon=(['lon'], lons), - var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + if cc is not None: + key = (datetime.utcfromtimestamp(cc['time']), cc['latitude'], cc['longitude']) + + if key not in data_dict: + data_dict[key] = [None, None, None, cc['data'] / 10000] + else: + data_dict[key][3] = cc['data'] / 10000 + + for i, t in enumerate(times): + for j, lat in enumerate(lats): + for k, lon in enumerate(lons): + vals_4d[i, j, k, :] = data_dict.get((t, lat, lon), [np.nan] * 4) + + ds = xr.DataArray( + data=vals_4d, + dims=['time', 'lat', 'lon', 'var'], + coords=dict( + time=(['time'], times), + lat=(['lat'], lats), + lon=(['lon'], lons), + var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + ) + ).to_dataset('var') + elif map_to_grid: + logger.info('More than one scene matched, will align points to a common grid') + + min_lat, max_lat = tuple( + np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)])[[0, -1]] + ) + + min_lon, max_lon = tuple( + np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)])[[0, -1]] + ) + + times = np.unique( + [datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + + logger.debug('Building gridding coordinate meshes') + + lons = np.arange(min_lon, max_lon + (LON_RES_MIN / 2), LON_RES_MIN) + lats = np.arange(min_lat, max_lat + (LAT_RES_MIN / 2), LAT_RES_MIN) + + X, Y = np.meshgrid(lons, lats) + + logger.info('Gridding ground heights') + gridded_zg = griddata( + list(zip([p['longitude'] for p in points_zg], [p['latitude'] for p in points_zg])), + np.array([p['data'] for p in points_zg]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding mean vegetation heights') + gridded_50 = griddata( + list(zip([p['longitude'] for p in points_50], [p['latitude'] for p in points_50])), + np.array([p['data'] for p in points_50]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding canopy heights') + gridded_98 = griddata( + list(zip([p['longitude'] for p in points_98], [p['latitude'] for p in points_98])), + np.array([p['data'] for p in points_98]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + logger.info('Gridding canopy coverage') + gridded_cc = griddata( + list(zip([p['longitude'] for p in points_cc], [p['latitude'] for p in points_cc])), + np.array([p['data'] / 10000 for p in points_cc]), + (X, Y), + method='nearest', + fill_value=np.nan + ) + + gridded_vals = np.array([ + gridded_zg, + gridded_50, + gridded_98, + gridded_cc + ]) + + gridded_vals = np.moveaxis(gridded_vals, 0, -1)[np.newaxis, ...] + + ds = xr.DataArray( + data=gridded_vals, + dims=['time', 'lat', 'lon', 'var'], + coords=dict( + time=(['time'], [times[0]]), + lat=(['lat'], lats), + lon=(['lon'], lons), + var=(['var'], ['ground_height', 'mean_veg_height', 'canopy_height', 'canopy_coverage']) + ) + ).to_dataset('var') + else: + raise NexusProcessingException( + reason='Selected bounds match multiple scenes, there is no way to merge them to a shared grid', + code=400 ) - ).to_dataset('var') slice_lat, slice_lon = None, None slice_min_lat, slice_max_lat = None, None @@ -351,7 +468,7 @@ def calc(self, computeOptions, **args): if slice_max_lon is None: slice_max_lon = ds.lon.max().item() - if len(ds['time']) > 1: + if 'time' in ds.coords and len(ds['time']) > 1: slice_ds = ds.mean(dim='time', skipna=True) else: slice_ds = ds @@ -367,7 +484,7 @@ def calc(self, computeOptions, **args): if slice_max_lat is None: slice_max_lat = ds.lat.max().item() - if len(ds['time']) > 1: + if 'time' in ds.coords and len(ds['time']) > 1: slice_ds = ds.mean(dim='time', skipna=True) else: slice_ds = ds From a791007dee0ff647ff1a99130753fc960dcb2a2e Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 29 Jan 2024 10:34:54 -0800 Subject: [PATCH 09/27] Suppress overly verbose loggers instead of all loggers to INFO --- analysis/webservice/algorithms/Lidar.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 904f40f6..e33d9503 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -15,6 +15,7 @@ import json import logging +import warnings import zipfile from datetime import datetime from functools import partial @@ -188,6 +189,8 @@ def parse_arguments(self, request): return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid def calc(self, computeOptions, **args): + warnings.filterwarnings('ignore', category=UserWarning) + dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid\ = self.parse_arguments(computeOptions) @@ -450,6 +453,8 @@ def source_name_from_granule(granule: str, end=-3) -> str: ) ).to_dataset('var') else: + warnings.filterwarnings('default', category=UserWarning) + raise NexusProcessingException( reason='Selected bounds match multiple scenes, there is no way to merge them to a shared grid', code=400 @@ -509,6 +514,8 @@ def source_name_from_granule(granule: str, end=-3) -> str: if lon_slice is not None: result_meta['slice_lon'] = (slice_lon, slice_min_lat, slice_max_lat) + warnings.filterwarnings('default', category=UserWarning) + return LidarResults( results=(ds, slices), meta=result_meta From 3234e65a4e3b26938d9dd9194909ecc3d2cf50d9 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 1 Feb 2024 10:26:57 -0800 Subject: [PATCH 10/27] lidar 3d plotting --- analysis/webservice/algorithms/Lidar.py | 392 ++++++++++++++++++++---- 1 file changed, 332 insertions(+), 60 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index e33d9503..4bbd19f1 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -13,21 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. +import contextlib import json import logging +import os.path import warnings import zipfile from datetime import datetime from functools import partial from io import BytesIO from itertools import zip_longest, chain -from tempfile import NamedTemporaryFile +from tempfile import NamedTemporaryFile, TemporaryDirectory +import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd import xarray as xr +from matplotlib.colors import XKCD_COLORS from mpl_toolkits.axes_grid1 import make_axes_locatable +from mpl_toolkits.basemap import Basemap +from PIL import Image from pytz import timezone from scipy.interpolate import griddata from webservice.NexusHandler import nexus_handler @@ -36,7 +42,6 @@ logger = logging.getLogger(__name__) - # NOTE: Current implementation expects the data vars to be in separate collections named # _ @@ -52,6 +57,7 @@ LAT_RES_MIN = 0.0002819728712069036 LON_RES_MIN = 0.0002819728712069036 + @nexus_handler class LidarVegetation(NexusCalcHandler): name = "LIDAR Vegetation Data" @@ -79,6 +85,17 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Ending time in format YYYY-MM-DDTHH:mm:ssZ or seconds since EPOCH. Required" }, + "renderType": { + "name": "Rendering type", + "type": "string", + "description": "Type of rendering to perform. Must be either 2D or 3D. Default: 2D" + }, + "output": { + "name": "Output format", + "type": "string", + "description": "Desired output format. Must be one of \"PNG\", \"JSON\", \"NetCDF\", \"CSV\", or \"ZIP\" " + "if renderType == \"2D\"; \"PNG\" or \"GIF\" if renderType == \"3D\". Required.", + }, "latSlice": { "name": "Latitude slice", "type": "string", @@ -89,6 +106,24 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Comma separated values: longitude to slice on[,min lat of slice,max lat of slice]" }, + "orbit": { + "name": "Orbit settings", + "type": "comma-delimited pair of ints", + "description": "If renderType = 3D and output==GIF, specifies the orbit to be used in the animation. " + "Format: elevation angle,orbit step. Default: 30, 10; Ranges: [-180,180],[1,90]" + }, + "viewAngle": { + "name": "Static view angle", + "type": "comma-delimited pair of ints", + "description": "If renderType = 3D and output==PNG, specifies the angle to be used for the render. Format: " + "azimuth,elevation angle. Default: 30,45; Ranges: [0,359],[-180,180]" + }, + "frameDuration": { + "name": "Frame duration", + "type": "int", + "description": "If renderType = 3D and output==GIF, specifies the duration of each frame in the animation " + "in milliseconds. Default: 100; Range: >=100" + }, "mapToGrid": { "name": "Map scenes to grid", "type": "boolean", @@ -139,67 +174,123 @@ def parse_arguments(self, request): "formatted as Minimum (Western) Longitude, Minimum (Southern) " "Latitude, Maximum (Eastern) Longitude, Maximum (Northern) Latitude.") + render_type = request.get_argument('renderType', '2D').upper() + + if render_type not in ['2D', '3D']: + raise NexusProcessingException( + reason=f'Missing or invalid required parameter: renderType = {render_type}', + code=400 + ) + + output = request.get_argument('output', '').upper() + + if (render_type == '2D' and output not in ['PNG', 'JSON', 'NETCDF', 'CSV', 'ZIP']) or \ + (render_type == '3D' and output not in ['PNG', 'GIF']): + raise NexusProcessingException( + reason=f'Missing or invalid required parameter: output = {output}', + code=400 + ) + lat_slice = request.get_argument('latSlice', None) lon_slice = request.get_argument('lonSlice', None) - if lat_slice is not None: - parts = lat_slice.split(',') + if render_type == '2D': + if lat_slice is not None: + parts = lat_slice.split(',') - if len(parts) not in [1, 3]: - raise NexusProcessingException( - reason='latSlice must consist of either one number (lat to slice on), or 3 numbers separated by ' - 'commas (lat to slice on, min lon of slice, max lon of slice)', code=400 - ) + if len(parts) not in [1, 3]: + raise NexusProcessingException( + reason='latSlice must consist of either one number (lat to slice on), or 3 numbers separated by ' + 'commas (lat to slice on, min lon of slice, max lon of slice)', code=400 + ) - try: - if len(parts) == 1: - lat_slice = (float(parts[0]), None, None) - else: - lat_slice = tuple([float(p) for p in parts]) - except ValueError: - raise NexusProcessingException( - reason='Invalid numerical component provided. latSlice must consist of either one number (lat to ' - 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' - 'slice)', code=400 - ) + try: + if len(parts) == 1: + lat_slice = (float(parts[0]), None, None) + else: + lat_slice = tuple([float(p) for p in parts]) + except ValueError: + raise NexusProcessingException( + reason='Invalid numerical component provided. latSlice must consist of either one number (lat to ' + 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' + 'slice)', code=400 + ) - if lon_slice is not None: - parts = lon_slice.split(',') + if lon_slice is not None: + parts = lon_slice.split(',') - if len(parts) not in [1, 3]: - raise NexusProcessingException( - reason='lonSlice must consist of either one number (lon to slice on), or 3 numbers separated by ' - 'commas (lon to slice on, min lat of slice, max lat of slice)', code=400 - ) + if len(parts) not in [1, 3]: + raise NexusProcessingException( + reason='lonSlice must consist of either one number (lon to slice on), or 3 numbers separated by ' + 'commas (lon to slice on, min lat of slice, max lat of slice)', code=400 + ) - try: - if len(parts) == 1: - lon_slice = (float(parts[0]), None, None) - else: - lon_slice = tuple([float(p) for p in parts]) - except ValueError: - raise NexusProcessingException( - reason='Invalid numerical component provided. lonSlice must consist of either one number (lon to ' - 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' - 'slice)', code=400 - ) + try: + if len(parts) == 1: + lon_slice = (float(parts[0]), None, None) + else: + lon_slice = tuple([float(p) for p in parts]) + except ValueError: + raise NexusProcessingException( + reason='Invalid numerical component provided. lonSlice must consist of either one number (lon to ' + 'slice on), or 3 numbers separated by commas (lon to slice on, min lat of slice, max lat of ' + 'slice)', code=400 + ) map_to_grid = request.get_boolean_arg('mapToGrid') - return ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid + orbit_elev, orbit_step, frame_duration, view_azim, view_elev = None, None, None, None, None + + if render_type == '3D': + orbit_params = request.get_argument('orbit', '30,10') + view_params = request.get_argument('viewAngle', '30,45') + + frame_duration = request.get_int_arg('frameDuration', 100) + + if output == 'GIF': + try: + orbit_params = orbit_params.split(',') + assert len(orbit_params) == 2 + orbit_elev, orbit_step = tuple([int(p) for p in orbit_params]) + + assert -180 <= orbit_elev <= 180 + assert 1 <= orbit_step <= 90 + assert frame_duration >= 100 + except: + raise NexusProcessingException( + reason=f'Invalid orbit parameters: {orbit_params} & {frame_duration}', + code=400 + ) + + view_azim, view_elev = None, None + else: + try: + view_params = view_params.split(',') + assert len(view_params) == 2 + view_azim, view_elev = tuple([int(p) for p in view_params]) + + assert 0 <= view_azim <= 359 + assert -180 <= view_elev <= 180 + except: + raise NexusProcessingException(reason=f'Invalid view angle string: {orbit_params}', code=400) + + orbit_elev, orbit_step = None, None + + return (ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid, render_type, + (orbit_elev, orbit_step, frame_duration, view_azim, view_elev)) def calc(self, computeOptions, **args): warnings.filterwarnings('ignore', category=UserWarning) - dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid\ + dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid, render_type, params_3d \ = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() - ds_zg = f'{dataset}_ZG' + ds_zg = f'{dataset}_ZG' ds_rh50 = f'{dataset}_RH050' ds_rh98 = f'{dataset}_RH098' - ds_cc = f'{dataset}_CC' + ds_cc = f'{dataset}_CC' get_tiles = partial( tile_service.find_tiles_in_polygon, @@ -224,7 +315,7 @@ def source_name_from_granule(granule: str, end=-3) -> str: return '_'.join(granule.split('_')[:end]) include_nan = map_to_grid # If we may need to map to grid, get nans from source tiles so we can best estimate - # grid resolution + # grid resolution for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): if tile_zg: @@ -322,7 +413,8 @@ def source_name_from_granule(granule: str, end=-3) -> str: lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) - times = np.unique([datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + times = np.unique( + [datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) @@ -464,6 +556,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: slice_min_lat, slice_max_lat = None, None slice_min_lon, slice_max_lon = None, None + # tmp + ds.to_netcdf('/tmp/lidar.nc') + if lat_slice is not None: slice_lat, slice_min_lon, slice_max_lon = lat_slice @@ -499,14 +594,14 @@ def source_name_from_granule(granule: str, end=-3) -> str: slices = (lat_slice, lon_slice) result_meta = dict( - ground_height_dataset=ds_zg, - vegetation_mean_height_dataset=ds_rh50, - canopy_height_dataset=ds_rh98, - canopy_coverage_dataset=ds_cc, - start_time=times[0].strftime(ISO_8601), - end_time=times[-1].strftime(ISO_8601), - b=f'{lons[0]},{lats[0]},{lons[-1]},{lats[-1]}' - ) + ground_height_dataset=ds_zg, + vegetation_mean_height_dataset=ds_rh50, + canopy_height_dataset=ds_rh98, + canopy_coverage_dataset=ds_cc, + start_time=times[0].strftime(ISO_8601), + end_time=times[-1].strftime(ISO_8601), + b=f'{lons[0]},{lats[0]},{lons[-1]},{lats[-1]}' + ) if lat_slice is not None: result_meta['slice_lat'] = (slice_lat, slice_min_lon, slice_max_lon) @@ -516,10 +611,19 @@ def source_name_from_granule(granule: str, end=-3) -> str: warnings.filterwarnings('default', category=UserWarning) - return LidarResults( - results=(ds, slices), - meta=result_meta - ) + if render_type == '2D': + results = LidarResults( + results=(ds, slices), + meta=result_meta + ) + else: + results = LidarResults3D( + results=ds, + meta=result_meta, + render_params=params_3d + ) + + return results class LidarResults(NexusResults): @@ -645,7 +749,7 @@ def toImage(self): slice_lat, slice_lon = meta.get('slice_lat'), meta.get('slice_lon') n_rows = 2 - + if lat_slice is not None: n_rows += 2 if lon_slice is not None: @@ -671,10 +775,13 @@ def toImage(self): zg_ax = fig.add_subplot(gs[1, 0]) cc_ax = fig.add_subplot(gs[1, 1]) - rh50_im = rh50_ax.imshow(np.flipud(np.squeeze(ds['mean_veg_height'])), extent=extent, aspect='equal', cmap='viridis') - rh98_im = rh98_ax.imshow(np.flipud(np.squeeze(ds['canopy_height'])), extent=extent, aspect='equal', cmap='viridis') + rh50_im = rh50_ax.imshow(np.flipud(np.squeeze(ds['mean_veg_height'])), extent=extent, aspect='equal', + cmap='viridis') + rh98_im = rh98_ax.imshow(np.flipud(np.squeeze(ds['canopy_height'])), extent=extent, aspect='equal', + cmap='viridis') zg_im = zg_ax.imshow(np.flipud(np.squeeze(ds['ground_height'])), extent=extent, aspect='equal', cmap='viridis') - cc_im = cc_ax.imshow(np.flipud(np.squeeze(ds['canopy_coverage'])) * 100, extent=extent, aspect='equal', cmap='viridis', vmin=0, vmax=100) + cc_im = cc_ax.imshow(np.flipud(np.squeeze(ds['canopy_coverage'])) * 100, extent=extent, aspect='equal', + cmap='viridis', vmin=0, vmax=100) if slice_lat is not None: rh50_ax.plot([slice_lat[1], slice_lat[2]], [slice_lat[0], slice_lat[0]], 'r--') @@ -815,3 +922,168 @@ def toZip(self): buffer.seek(0) return buffer.read() + +class LidarResults3D(NexusResults): + def __init__(self, results=None, meta=None, stats=None, computeOptions=None, status_code=200, **kwargs): + NexusResults.__init__(self, results, meta, stats, computeOptions, status_code, **kwargs) + self.render_params = kwargs['render_params'] + + def results(self): + ds: xr.Dataset = NexusResults.results(self) + df: pd.DataFrame = ds.to_dataframe() + + return df.reset_index(level=['lon', 'lat']) + + def toImage(self): + _, _, _, view_azim, view_elev = self.render_params + + results = self.results() + + fig = plt.figure(figsize=(10, 7)) + ax = fig.add_subplot(111, projection='3d') + ax.view_init(elev=view_elev, azim=view_azim) + + # ZG + + lats = np.unique(results['lat'].values) + lons = np.unique(results['lon'].values) + + data_dict = {} + + for r in results.itertuples(index=False): + key = (r.lon, r.lat) + data_dict[key] = r.ground_height + + vals = np.empty((len(lats), len(lons))) + + for i, lat in enumerate(lats): + for j, lon in enumerate(lons): + vals[i, j] = data_dict.get((lon, lat), np.nan) + + X, Y = np.meshgrid(lons, lats) + + s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:leaf') + + results = results[results['mean_veg_height'].notnull()] + + xy = results[['lon', 'lat']].values + + MARKER_SIZE = 1 + + s1 = ax.scatter( + xy[:, 0], xy[:, 1], results['mean_veg_height'].values + results['ground_height'].values, + marker=',', + alpha=results['canopy_coverage'].values, + facecolors='brown', + zdir='z', + depthshade=True, + s=MARKER_SIZE, + linewidth=0 + ) + + s2 = ax.scatter( + xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, + marker=',', + alpha=results['canopy_coverage'].values, + facecolors='brown', + zdir='z', + depthshade=True, + s=MARKER_SIZE, + linewidth=0 + ) + + ax.set_ylabel('Latitude') + ax.set_xlabel('Longitude') + ax.set_zlabel('Elevation w.r.t. dataset reference (m)') + + plt.tight_layout() + + buffer = BytesIO() + + logger.info('Writing plot to buffer') + plt.savefig(buffer, format='png', facecolor='white') + + buffer.seek(0) + return buffer.read() + + def toGif(self): + orbit_elev, orbit_step, frame_duration, _, _ = self.render_params + + results = self.results() + + fig = plt.figure(figsize=(10, 7)) + ax = fig.add_subplot(111, projection='3d') + ax.view_init(elev=orbit_elev, azim=0) + + lats = np.unique(results['lat'].values) + lons = np.unique(results['lon'].values) + + data_dict = {} + + for r in results.itertuples(index=False): + key = (r.lon, r.lat) + data_dict[key] = r.ground_height + + vals = np.empty((len(lats), len(lons))) + + for i, lat in enumerate(lats): + for j, lon in enumerate(lons): + vals[i, j] = data_dict.get((lon, lat), np.nan) + + X, Y = np.meshgrid(lons, lats) + + s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:leaf') + + results = results[results['mean_veg_height'].notnull()] + + xy = results[['lon', 'lat']].values + + MARKER_SIZE = 1 + + s1 = ax.scatter( + xy[:, 0], xy[:, 1], results['mean_veg_height'].values + results['ground_height'].values, + marker=',', + alpha=results['canopy_coverage'].values, + facecolors='brown', + zdir='z', + depthshade=True, + s=MARKER_SIZE, + linewidth=0 + ) + + s2 = ax.scatter( + xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, + marker=',', + alpha=results['canopy_coverage'].values, + facecolors='brown', + zdir='z', + depthshade=True, + s=MARKER_SIZE, + linewidth=0 + ) + + ax.set_ylabel('Latitude') + ax.set_xlabel('Longitude') + ax.set_zlabel('Elevation w.r.t. dataset reference (m)') + + plt.tight_layout() + + buffer = BytesIO() + + with TemporaryDirectory() as td: + for azim in range(0, 360, orbit_step): + logger.info(f'Saving frame for azimuth = {azim}') + + ax.view_init(azim=azim) + + plt.savefig(os.path.join(td, f'fr_{azim}.png')) + + with contextlib.ExitStack() as stack: + logger.info('Combining frames into final GIF') + + imgs = (stack.enter_context(Image.open(os.path.join(td, f'fr_{a}.png'))) for a in range(0, 360, orbit_step)) + img = next(imgs) + img.save(buffer, format='GIF', append_images=imgs, save_all=True, duration=frame_duration, loop=0) + + buffer.seek(0) + return buffer.read() From 01bea5718014bf0e2bfbdaca46d96dac4f5960a2 Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 5 Feb 2024 09:07:50 -0800 Subject: [PATCH 11/27] SDAP-495 3d plotting fixes --- analysis/webservice/algorithms/Lidar.py | 9 ++++-- .../request/renderers/NexusGIFRenderer.py | 32 +++++++++++++++++++ .../request/renderers/NexusRendererFactory.py | 8 ++++- .../request/renderers/__init__.py | 1 + 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 analysis/webservice/nexus_tornado/request/renderers/NexusGIFRenderer.py diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 4bbd19f1..8c026dd2 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -116,7 +116,7 @@ class LidarVegetation(NexusCalcHandler): "name": "Static view angle", "type": "comma-delimited pair of ints", "description": "If renderType = 3D and output==PNG, specifies the angle to be used for the render. Format: " - "azimuth,elevation angle. Default: 30,45; Ranges: [0,359],[-180,180]" + "azimuth,elevation angle. Default: 300,30; Ranges: [0,359],[-180,180]" }, "frameDuration": { "name": "Frame duration", @@ -242,8 +242,11 @@ def parse_arguments(self, request): orbit_elev, orbit_step, frame_duration, view_azim, view_elev = None, None, None, None, None if render_type == '3D': + lat_slice = (None, None, None) + lon_slice = (None, None, None) + orbit_params = request.get_argument('orbit', '30,10') - view_params = request.get_argument('viewAngle', '30,45') + view_params = request.get_argument('viewAngle', '300,30') frame_duration = request.get_int_arg('frameDuration', 100) @@ -272,7 +275,7 @@ def parse_arguments(self, request): assert 0 <= view_azim <= 359 assert -180 <= view_elev <= 180 except: - raise NexusProcessingException(reason=f'Invalid view angle string: {orbit_params}', code=400) + raise NexusProcessingException(reason=f'Invalid view angle string: {view_params}', code=400) orbit_elev, orbit_step = None, None diff --git a/analysis/webservice/nexus_tornado/request/renderers/NexusGIFRenderer.py b/analysis/webservice/nexus_tornado/request/renderers/NexusGIFRenderer.py new file mode 100644 index 00000000..2aa0d756 --- /dev/null +++ b/analysis/webservice/nexus_tornado/request/renderers/NexusGIFRenderer.py @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import traceback +from webservice.webmodel import NexusProcessingException + + +class NexusGIFRenderer(object): + def __init__(self, nexus_request): + self._request = nexus_request + + def render(self, tornado_handler, result): + tornado_handler.set_header("Content-Type", "image/gif") + try: + tornado_handler.write(result.toGif()) + tornado_handler.finish() + except AttributeError: + traceback.print_exc(file=sys.stdout) + raise NexusProcessingException(reason="Unable to convert results to a GIF.") \ No newline at end of file diff --git a/analysis/webservice/nexus_tornado/request/renderers/NexusRendererFactory.py b/analysis/webservice/nexus_tornado/request/renderers/NexusRendererFactory.py index e0dabe22..f172c9e3 100644 --- a/analysis/webservice/nexus_tornado/request/renderers/NexusRendererFactory.py +++ b/analysis/webservice/nexus_tornado/request/renderers/NexusRendererFactory.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from webservice.webmodel import NexusProcessingException + class NexusRendererFactory(object): - content_types = ["CSV", "JSON", "XML", "PNG", "NETCDF", "ZIP"] + content_types = ["CSV", "JSON", "XML", "PNG", "NETCDF", "ZIP", "GIF"] module = __import__(__name__) @classmethod @@ -24,6 +26,10 @@ def get_renderer(cls, request): renderer_name = 'Nexus' + content_type + 'Renderer' renderer = getattr(cls.module.nexus_tornado.request.renderers, renderer_name) return renderer(request) + else: + raise NexusProcessingException( + reason=f'Invalid output format {content_type}', code=400 + ) diff --git a/analysis/webservice/nexus_tornado/request/renderers/__init__.py b/analysis/webservice/nexus_tornado/request/renderers/__init__.py index 4c1d31e7..28502d9a 100644 --- a/analysis/webservice/nexus_tornado/request/renderers/__init__.py +++ b/analysis/webservice/nexus_tornado/request/renderers/__init__.py @@ -18,4 +18,5 @@ from .NexusCSVRenderer import NexusCSVRenderer from .NexusNETCDFRenderer import NexusNETCDFRenderer from .NexusPNGRenderer import NexusPNGRenderer +from .NexusGIFRenderer import NexusGIFRenderer from .NexusZIPRenderer import NexusZIPRenderer \ No newline at end of file From 30ce242112778bae6593a35be73e900a0c24bfcd Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 5 Feb 2024 16:27:36 -0800 Subject: [PATCH 12/27] minor tweaks --- analysis/webservice/algorithms/Lidar.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 8c026dd2..d7682fa7 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -26,13 +26,12 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory import matplotlib as mpl +mpl.use('Qt5Agg') import matplotlib.pyplot as plt import numpy as np import pandas as pd import xarray as xr -from matplotlib.colors import XKCD_COLORS from mpl_toolkits.axes_grid1 import make_axes_locatable -from mpl_toolkits.basemap import Basemap from PIL import Image from pytz import timezone from scipy.interpolate import griddata @@ -492,6 +491,8 @@ def source_name_from_granule(granule: str, end=-3) -> str: X, Y = np.meshgrid(lons, lats) + logger.info(f'Output grid shape: {X.shape}') + logger.info('Gridding ground heights') gridded_zg = griddata( list(zip([p['longitude'] for p in points_zg], [p['latitude'] for p in points_zg])), @@ -965,7 +966,7 @@ def toImage(self): X, Y = np.meshgrid(lons, lats) - s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:leaf') + s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:dirt') results = results[results['mean_veg_height'].notnull()] @@ -988,7 +989,7 @@ def toImage(self): xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, marker=',', alpha=results['canopy_coverage'].values, - facecolors='brown', + facecolors='xkcd:leaf', zdir='z', depthshade=True, s=MARKER_SIZE, @@ -1035,18 +1036,19 @@ def toGif(self): X, Y = np.meshgrid(lons, lats) - s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:leaf') + s = ax.plot_surface(X, Y, vals, rstride=1, cstride=1, color='xkcd:dirt') results = results[results['mean_veg_height'].notnull()] xy = results[['lon', 'lat']].values MARKER_SIZE = 1 + ALPHA_SCALING = 1.0 s1 = ax.scatter( xy[:, 0], xy[:, 1], results['mean_veg_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values, + alpha=results['canopy_coverage'].values * ALPHA_SCALING, facecolors='brown', zdir='z', depthshade=True, @@ -1057,8 +1059,8 @@ def toGif(self): s2 = ax.scatter( xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values, - facecolors='brown', + alpha=results['canopy_coverage'].values * ALPHA_SCALING, + facecolors='xkcd:leaf', zdir='z', depthshade=True, s=MARKER_SIZE, From cdf4e7a2f1b87927c6be2958daf56d04efe99e19 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 8 Feb 2024 14:05:05 -0800 Subject: [PATCH 13/27] Remove mpl backend call --- analysis/webservice/algorithms/Lidar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index d7682fa7..ddc4e685 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -26,7 +26,6 @@ from tempfile import NamedTemporaryFile, TemporaryDirectory import matplotlib as mpl -mpl.use('Qt5Agg') import matplotlib.pyplot as plt import numpy as np import pandas as pd From 7bb8bcb3a054b54eb37a816be5c419f35a49553b Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 8 Feb 2024 14:08:47 -0800 Subject: [PATCH 14/27] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8db48ef7..d617a241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SDAP-506: - Added STAC Catalog endpoint for matchup outputs - SDAP-508: Added spatial extents to the satellite dataset entries in `/list` and `/cdmslist` +- SDAP-495: Added visualization endpoint `/stv/lidar` to produce 2d and 3d visualizations of the [ABoVE LVIS 2017 & 2019 L3 Vegetation Structure dataset](https://daac.ornl.gov/ABOVE/guides/ABoVE_LVIS_VegetationStructure.html). These endpoints provide visualization for ground height, mean vegetation height (RH050), canopy height (RH098) and canopy complexity (CC >= 03p00m) ### Changed - SDAP-493: - Updated /job endpoint to use `executionId` terminology for consistency with existing `/cdmsresults` endpoint From 421b86db0b2a177dfefa2a81e795c911b0c93a72 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 15 Feb 2024 11:49:41 -0800 Subject: [PATCH 15/27] Support for 2D Lidar slicing along any arbitrary line defined by WKT --- analysis/conda-requirements.txt | 1 + analysis/webservice/algorithms/Lidar.py | 297 ++++++++++++++++++++---- 2 files changed, 254 insertions(+), 44 deletions(-) diff --git a/analysis/conda-requirements.txt b/analysis/conda-requirements.txt index 8efb9043..3f31bf82 100644 --- a/analysis/conda-requirements.txt +++ b/analysis/conda-requirements.txt @@ -35,3 +35,4 @@ importlib_metadata==4.11.4 #singledispatch==3.4.0.3 xarray matplotlib +geopy diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index ddc4e685..c1a9bb17 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -29,11 +29,15 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +import shapely.wkt as wkt import xarray as xr +from geopy.distance import geodesic from mpl_toolkits.axes_grid1 import make_axes_locatable from PIL import Image from pytz import timezone from scipy.interpolate import griddata +from shapely.geometry import LineString +from shapely.errors import ShapelyError from webservice.NexusHandler import nexus_handler from webservice.algorithms.NexusCalcHandler import NexusCalcHandler from webservice.webmodel import NexusResults, NexusProcessingException @@ -104,6 +108,18 @@ class LidarVegetation(NexusCalcHandler): "type": "string", "description": "Comma separated values: longitude to slice on[,min lat of slice,max lat of slice]" }, + "sliceWKT": { + "name": "Slice WKT", + "type": "string", + "description": "WTK LineString representation of the slice along the Lidar data you wish to display. " + "Control the number of samples along this line with the sliceSamples param. Plot x-axes will" + " be distance along the line." + }, + "sliceSamples": { + "name": "Slice Samples", + "type": "integer > 0", + "description": "Number of points along sliceWKT to plot. Default = 100" + }, "orbit": { "name": "Orbit settings", "type": "comma-delimited pair of ints", @@ -192,7 +208,18 @@ def parse_arguments(self, request): lat_slice = request.get_argument('latSlice', None) lon_slice = request.get_argument('lonSlice', None) + slice_wkt = request.get_argument('sliceWKT', None) + slice_samples = request.get_int_arg('sliceSamples', 100) + + slice_line = None + if render_type == '2D': + if (lat_slice is not None or lon_slice is not None) and slice_wkt is not None: + raise NexusProcessingException( + reason='Cannot define latSlice and/or lonSlice and sliceWKT at the same time', + code=400 + ) + if lat_slice is not None: parts = lat_slice.split(',') @@ -235,6 +262,28 @@ def parse_arguments(self, request): 'slice)', code=400 ) + if slice_wkt is not None: + try: + slice_line = wkt.loads(slice_wkt) + except ShapelyError as e: + logger.exception(e) + raise NexusProcessingException( + reason=f'Invalid WKT: {slice_wkt}', + code=400 + ) + + if not isinstance(slice_line, LineString): + raise NexusProcessingException( + reason=f'Invalid geometry type for sliceWKT: {slice_line.geom_type}. Expected LineString', + code=400 + ) + + if not slice_samples > 0: + raise NexusProcessingException( + reason='Slice samples must be > 0', + code=400 + ) + map_to_grid = request.get_boolean_arg('mapToGrid') orbit_elev, orbit_step, frame_duration, view_azim, view_elev = None, None, None, None, None @@ -277,14 +326,14 @@ def parse_arguments(self, request): orbit_elev, orbit_step = None, None - return (ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid, render_type, - (orbit_elev, orbit_step, frame_duration, view_azim, view_elev)) + return (ds, start_time, end_time, bounding_polygon, lon_slice, lat_slice, slice_line, slice_samples, + map_to_grid, render_type, (orbit_elev, orbit_step, frame_duration, view_azim, view_elev)) def calc(self, computeOptions, **args): warnings.filterwarnings('ignore', category=UserWarning) - dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, map_to_grid, render_type, params_3d \ - = self.parse_arguments(computeOptions) + (dataset, start_time, end_time, bounding_polygon, lon_slice, lat_slice, slice_line, slice_samples, map_to_grid, + render_type, params_3d) = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() @@ -407,6 +456,8 @@ def source_name_from_granule(granule: str, end=-3) -> str: if isinstance(p_cc['data'], list): p_cc['data'] = p_cc['data'][0] + p_cc['data'] = p_cc['data'] * 100 + points_cc.append(p_cc) if len(sources) == 1: @@ -558,9 +609,13 @@ def source_name_from_granule(granule: str, end=-3) -> str: slice_lat, slice_lon = None, None slice_min_lat, slice_max_lat = None, None slice_min_lon, slice_max_lon = None, None + slice_wkt = None # tmp - ds.to_netcdf('/tmp/lidar.nc') + # try: + # ds.to_netcdf('/tmp/lidar.nc') + # except: + # print('failed to dump test netcdf :(') if lat_slice is not None: slice_lat, slice_min_lon, slice_max_lon = lat_slice @@ -594,7 +649,59 @@ def source_name_from_granule(granule: str, end=-3) -> str: lon_slice = slice_ds.sel(lon=slice_lon, method='nearest').sel(lat=slice(slice_min_lat, slice_max_lat)) - slices = (lat_slice, lon_slice) + if slice_line is not None: + point_coords = [] + + for p in np.linspace(0, 1, slice_samples): + point_coords.append(slice_line.interpolate(p, True).coords[0]) + + point_coords = [dict(lon=p[0], lat=p[1]) for p in point_coords] + + slice_ds = ds.copy() + + if len(slice_ds['time']) > 1: + logger.warning('slicing: ds has multiple time steps. Reducing them to 1') + slice_ds = slice_ds.mean(dim='time') + else: + slice_ds = slice_ds.squeeze(dim=['time'], drop=True) + + points = [] # {coord: tuple (l,l), ground_height: float, mean_veg_height: float, canopy_height: float, canopy_coverage: float, distance: float} + + last_point = None + + for coord in point_coords: + ds_sel = slice_ds.sel(coord, method='nearest') + + sel_coord = (ds_sel.lat.item(), ds_sel.lon.item()) + + if last_point is None: + last_point = dict( + coord=sel_coord, + ground_height=ds_sel.ground_height.item(), + mean_veg_height=ds_sel.mean_veg_height.item(), + canopy_height=ds_sel.canopy_height.item(), + canopy_coverage=ds_sel.canopy_coverage.item(), + distance=0.0 + ) + points.append(last_point) + elif sel_coord != last_point['coord'] or coord == point_coords[-1]: + next_distance = last_point['distance'] + abs(geodesic(sel_coord, last_point['coord']).m) + + if not coord == point_coords[-1] and next_distance != last_point['distance']: + last_point = dict( + coord=sel_coord, + ground_height=ds_sel.ground_height.item(), + mean_veg_height=ds_sel.mean_veg_height.item(), + canopy_height=ds_sel.canopy_height.item(), + canopy_coverage=ds_sel.canopy_coverage.item(), + distance=next_distance + ) + points.append(last_point) + + slice_wkt = slice_line.wkt + slice_line = points + + slices = (lat_slice, lon_slice, slice_line) result_meta = dict( ground_height_dataset=ds_zg, @@ -612,6 +719,11 @@ def source_name_from_granule(granule: str, end=-3) -> str: if lon_slice is not None: result_meta['slice_lon'] = (slice_lon, slice_min_lat, slice_max_lat) + if slice_line is not None: + result_meta['slice_wkt'] = slice_wkt + result_meta['slice_samples_requested'] = slice_samples + result_meta['slice_samples_actual'] = len(slice_line) + warnings.filterwarnings('default', category=UserWarning) if render_type == '2D': @@ -636,24 +748,31 @@ def __init__(self, results=None, meta=None, stats=None, computeOptions=None, sta def meta(self): m = NexusResults.meta(self) - del m['shortName'] - del m['bounds'] + try: + del m['shortName'] + except KeyError: + ... + + try: + del m['bounds'] + except KeyError: + ... return m def results(self, reduce_time=False): - ds, (lat_slice, lon_slice) = NexusResults.results(self) + ds, (lat_slice, lon_slice, line_slice) = NexusResults.results(self) if reduce_time and len(ds['time']) > 1: ds = ds.mean(dim='time', skipna=True) - return ds, (lat_slice, lon_slice) + return ds, (lat_slice, lon_slice, line_slice) def points_list(self): points = [] slice_points = {} - ds, (lat_slice, lon_slice) = self.results() + ds, (lat_slice, lon_slice, line_slice) = self.results() logger.info('Generating non-NaN points list') @@ -713,7 +832,20 @@ def points_list(self): canopy_coverage=lon_slice.canopy_coverage.isel(lat=l).item() ) for l in range(len(ds['lat']))] - slice_points['longitude'] = dict(longitude=lat_slice.lon.item(), slice=pts) + slice_points['longitude'] = dict(longitude=lon_slice.lon.item(), slice=pts) + + if line_slice is not None: + pts = [dict( + latitude=p['coord'][0], + longitude=p['coord'][1], + distance_along_slice_line=p['distance'], + ground_height=p['ground_height'], + mean_vegetation_height=p['mean_veg_height'], + canopy_height=p['canopy_height'], + canopy_coverage=p['canopy_coverage'] + ) for p in line_slice] + + slice_points['line'] = dict(line=self.meta()['slice_wkt'], slice_point_count=len(pts), slice=pts) return points, slice_points @@ -746,7 +878,7 @@ def slice_point_list(ds, s, coord): return s.lon.item(), pts def toImage(self): - ds, (lat_slice, lon_slice) = self.results() + ds, (lat_slice, lon_slice, line_slice) = self.results() meta = self.meta() slice_lat, slice_lon = meta.get('slice_lat'), meta.get('slice_lon') @@ -757,6 +889,8 @@ def toImage(self): n_rows += 2 if lon_slice is not None: n_rows += 2 + if line_slice is not None: + n_rows += 2 min_lon, min_lat, max_lon, max_lat = ( ds.lon.min().item(), @@ -783,7 +917,7 @@ def toImage(self): rh98_im = rh98_ax.imshow(np.flipud(np.squeeze(ds['canopy_height'])), extent=extent, aspect='equal', cmap='viridis') zg_im = zg_ax.imshow(np.flipud(np.squeeze(ds['ground_height'])), extent=extent, aspect='equal', cmap='viridis') - cc_im = cc_ax.imshow(np.flipud(np.squeeze(ds['canopy_coverage'])) * 100, extent=extent, aspect='equal', + cc_im = cc_ax.imshow(np.flipud(np.squeeze(ds['canopy_coverage'])), extent=extent, aspect='equal', cmap='viridis', vmin=0, vmax=100) if slice_lat is not None: @@ -798,6 +932,17 @@ def toImage(self): zg_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') cc_ax.plot([slice_lon[0], slice_lon[0]], [slice_lon[1], slice_lon[2]], 'r--') + if line_slice is not None: + try: + line = wkt.loads(self.meta()['slice_wkt']) + line_coords = list(line.coords) + rh50_ax.plot([c[0] for c in line_coords], [c[1] for c in line_coords], 'r--') + rh98_ax.plot([c[0] for c in line_coords], [c[1] for c in line_coords], 'r--') + zg_ax.plot([c[0] for c in line_coords], [c[1] for c in line_coords], 'r--') + cc_ax.plot([c[0] for c in line_coords], [c[1] for c in line_coords], 'r--') + except: + ... + divider_50 = make_axes_locatable(rh50_ax) divider_98 = make_axes_locatable(rh98_ax) divider_zg = make_axes_locatable(zg_ax) @@ -820,48 +965,80 @@ def toImage(self): row = 2 - for s, coord in zip([lat_slice, lon_slice], ['latitude', 'longitude']): + for s, coord in zip([lat_slice, lon_slice, line_slice], ['latitude', 'longitude', None]): if s is None: continue slice_ax = fig.add_subplot(gs[row, :]) - slice_point, pts = LidarResults.slice_point_list(ds, s, coord) + if coord in ['latitude', 'longitude']: + slice_point, pts = LidarResults.slice_point_list(ds, s, coord) - x_lim = [min_lon, max_lon] if coord == 'latitude' else [min_lat, max_lat] + x_lim = [min_lon, max_lon] if coord == 'latitude' else [min_lat, max_lat] - x_pts = [p['s'] for p in pts] - rh50_pts = np.array([p['mean_vegetation_height'] for p in pts]) - rh98_pts = np.array([p['canopy_height'] for p in pts]) - zg_pts = np.array([p['ground_height'] for p in pts]) - cc_pts = np.array([p['canopy_coverage'] for p in pts]) * 100 + x_pts = [p['s'] for p in pts] + rh50_pts = np.array([p['mean_vegetation_height'] for p in pts]) + rh98_pts = np.array([p['canopy_height'] for p in pts]) + zg_pts = np.array([p['ground_height'] for p in pts]) + cc_pts = np.array([p['canopy_coverage'] for p in pts]) - slice_ax.plot( - x_pts, rh98_pts + zg_pts, - x_pts, rh50_pts + zg_pts, - x_pts, zg_pts, - ) + slice_ax.plot( + x_pts, rh98_pts + zg_pts, + x_pts, rh50_pts + zg_pts, + x_pts, zg_pts, + ) - slice_ax.set_title(f'Slice at {coord}={slice_point}\nHeights w.r.t. to reference ellipsoid (m)') - slice_ax.ticklabel_format(useOffset=False) - slice_ax.set_xlim(x_lim) + slice_ax.set_title(f'Slice at {coord}={slice_point}\nHeights w.r.t. to reference ellipsoid (m)') + slice_ax.ticklabel_format(useOffset=False) + slice_ax.set_xlim(x_lim) - slice_ax.legend([ - 'Canopy Height', - 'Mean Vegetation Height', - 'Ground Height', - ]) + slice_ax.legend([ + 'Canopy Height', + 'Mean Vegetation Height', + 'Ground Height', + ]) + + cc_slice_ax = fig.add_subplot(gs[row + 1, :]) - cc_slice_ax = fig.add_subplot(gs[row + 1, :]) + cc_slice_ax.plot(x_pts, cc_pts) + cc_slice_ax.ticklabel_format(useOffset=False) + cc_slice_ax.set_ylim([0, 100]) + cc_slice_ax.set_xlim(x_lim) - cc_slice_ax.plot(x_pts, cc_pts) - cc_slice_ax.ticklabel_format(useOffset=False) - cc_slice_ax.set_ylim([0, 100]) - cc_slice_ax.set_xlim(x_lim) + cc_slice_ax.set_title(f'Slice at {coord}={slice_point}\nCanopy coverage (%)') - cc_slice_ax.set_title(f'Slice at {coord}={slice_point}\nCanopy coverage (%)') + cc_slice_ax.legend(['Canopy Coverage']) + else: + x_pts = np.array([p['distance'] for p in s]) + rh50_pts = np.array([p['mean_veg_height'] for p in s]) + rh98_pts = np.array([p['canopy_height'] for p in s]) + zg_pts = np.array([p['ground_height'] for p in s]) + cc_pts = np.array([p['canopy_coverage'] for p in s]) + + slice_ax.plot( + x_pts, rh98_pts + zg_pts, + x_pts, rh50_pts + zg_pts, + x_pts, zg_pts, + ) - cc_slice_ax.legend(['Canopy Coverage']) + slice_ax.set_title(f'Slice along line\nHeights w.r.t. to reference ellipsoid (m)') + slice_ax.ticklabel_format(useOffset=False) + + slice_ax.legend([ + 'Canopy Height', + 'Mean Vegetation Height', + 'Ground Height', + ]) + + cc_slice_ax = fig.add_subplot(gs[row + 1, :]) + + cc_slice_ax.plot(x_pts, cc_pts) + cc_slice_ax.ticklabel_format(useOffset=False) + cc_slice_ax.set_ylim([0, 100]) + + cc_slice_ax.set_title(f'Slice along line\nCanopy coverage (%)') + + cc_slice_ax.legend(['Canopy Coverage']) row += 2 @@ -885,11 +1062,40 @@ def toJson(self): ), indent=4) def toNetCDF(self): - ds, (lat_slice, lon_slice) = self.results() + ds, (lat_slice, lon_slice, line_slice) = self.results() meta = self.meta() ds.attrs.update(meta) + if lat_slice is not None: + rename_dict = {var: f'lat_slice_{var}' for var in lat_slice.data_vars} + lat_slice = lat_slice.rename(rename_dict) + ds = xr.merge([ds, lat_slice]) + + if lon_slice is not None: + rename_dict = {var: f'lon_slice_{var}' for var in lon_slice.data_vars} + lon_slice = lon_slice.rename(rename_dict) + ds = xr.merge([ds, lon_slice]) + + if line_slice is not None: + slice_ds = xr.DataArray( + data=np.array( + [ + [p['ground_height'], p['mean_veg_height'], p['canopy_height'], p['canopy_coverage']] + for p in line_slice + ] + ), + dims=['distance', 'var'], + coords=dict( + distance=(['distance'], np.array([p['distance'] for p in line_slice])), + var=( + ['var'], + ['slice_ground_height', 'slice_mean_veg_height', 'slice_canopy_height', 'slice_canopy_coverage'] + ) + ) + ).to_dataset('var') + ds = xr.merge([ds, slice_ds]) + with NamedTemporaryFile(suffix='.nc', mode='w') as fp: comp = {"zlib": True, "complevel": 9} encoding = {vname: comp for vname in ds.data_vars} @@ -905,7 +1111,10 @@ def toNetCDF(self): return buf.read() def toCSV(self): - df = pd.DataFrame(self.points_list()) + points, slice_points = self.points_list() + df = pd.DataFrame(points) + + # print(df) buffer = BytesIO() From e982f4b53cc65fe463b731192688a1cd46b24e10 Mon Sep 17 00:00:00 2001 From: rileykk Date: Fri, 16 Feb 2024 14:01:21 -0800 Subject: [PATCH 16/27] Slicing improvements: - Removed restriction that lat and/or lon slices cannot be used with arbitrary line slices, though the png result can get a bit long if all three are used - ZIP output now contains CSV files for slices as well as main subset - Vastly improved time for JSON, CSV and ZIP outputs --- analysis/webservice/algorithms/Lidar.py | 93 ++++++++++++++----------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index c1a9bb17..19e00d41 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -24,8 +24,8 @@ from io import BytesIO from itertools import zip_longest, chain from tempfile import NamedTemporaryFile, TemporaryDirectory +from typing import Tuple -import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -50,6 +50,7 @@ EPOCH = timezone('UTC').localize(datetime(1970, 1, 1)) NP_EPOCH = np.datetime64('1970-01-01T00:00:00') +PD_EPOCH = pd.Timestamp('1970-01-01T00:00:00') ISO_8601 = '%Y-%m-%dT%H:%M:%S%z' # Precomputed resolutions of LIDAR data grids, computed over the entire ABoVE dataset @@ -214,11 +215,11 @@ def parse_arguments(self, request): slice_line = None if render_type == '2D': - if (lat_slice is not None or lon_slice is not None) and slice_wkt is not None: - raise NexusProcessingException( - reason='Cannot define latSlice and/or lonSlice and sliceWKT at the same time', - code=400 - ) + # if (lat_slice is not None or lon_slice is not None) and slice_wkt is not None: + # raise NexusProcessingException( + # reason='Cannot define latSlice and/or lonSlice and sliceWKT at the same time', + # code=400 + # ) if lat_slice is not None: parts = lat_slice.split(',') @@ -612,10 +613,10 @@ def source_name_from_granule(granule: str, end=-3) -> str: slice_wkt = None # tmp - # try: - # ds.to_netcdf('/tmp/lidar.nc') - # except: - # print('failed to dump test netcdf :(') + try: + ds.to_netcdf('/tmp/lidar.nc') + except: + print('failed to dump test netcdf :(') if lat_slice is not None: slice_lat, slice_min_lon, slice_max_lon = lat_slice @@ -760,7 +761,7 @@ def meta(self): return m - def results(self, reduce_time=False): + def results(self, reduce_time=False) -> Tuple[xr.Dataset, tuple]: ds, (lat_slice, lon_slice, line_slice) = NexusResults.results(self) if reduce_time and len(ds['time']) > 1: @@ -776,31 +777,28 @@ def points_list(self): logger.info('Generating non-NaN points list') - for i, t in enumerate(ds.time): - for j, lat in enumerate(ds.lat): - for k, lon in enumerate(ds.lon): - ds_point = ds.isel(time=i, lat=j, lon=k) - - zg = ds_point['ground_height'].item() - rh50 = ds_point['mean_veg_height'].item() - rh98 = ds_point['canopy_height'].item() - cc = ds_point['canopy_coverage'].item() + for pt in ds.to_dataframe().reset_index(level=['lat', 'lon', 'time']).itertuples(): + zg = getattr(pt, 'ground_height') + rh50 = getattr(pt, 'mean_veg_height') + rh98 = getattr(pt, 'canopy_height') + cc = getattr(pt, 'canopy_coverage') - if all([np.isnan(v) for v in [zg, rh50, rh98, cc]]): - continue + if all([np.isnan(v) for v in [zg, rh50, rh98, cc]]): + continue - point_ts = int((t.data - NP_EPOCH) / np.timedelta64(1, 's')) + ts: pd.Timestamp = getattr(pt, 'time') + point_ts = int((ts - PD_EPOCH).total_seconds()) - points.append(dict( - latitude=lat.item(), - longitude=lon.item(), - time=point_ts, - time_iso=datetime.utcfromtimestamp(point_ts).strftime(ISO_8601), - ground_height=zg, - mean_vegetation_height=rh50, - canopy_height=rh98, - canopy_coverage=cc - )) + points.append(dict( + latitude=getattr(pt, 'lat'), + longitude=getattr(pt, 'lon'), + time=point_ts, + time_iso=datetime.utcfromtimestamp(point_ts).strftime(ISO_8601), + ground_height=zg, + mean_vegetation_height=rh50, + canopy_height=rh98, + canopy_coverage=cc + )) if lat_slice is not None: if len(lat_slice['time']) > 1: @@ -1110,12 +1108,11 @@ def toNetCDF(self): buf.seek(0) return buf.read() - def toCSV(self): - points, slice_points = self.points_list() + def toCSV(self, points=None): + if points is None: + points, _ = self.points_list() df = pd.DataFrame(points) - # print(df) - buffer = BytesIO() df.to_csv(buffer, index=False) @@ -1124,12 +1121,30 @@ def toCSV(self): return buffer.read() def toZip(self): - csv_results = self.toCSV() + points, slice_points = self.points_list() + + csv_results = self.toCSV(points) + + csv_slices = [] + + for slice_type in slice_points: + s = slice_points[slice_type] + + if slice_type in ['latitude', 'longitude']: + filename = f'{slice_type}_slice.csv' + else: + filename = 'line_slice.csv' + + slice_csv = self.toCSV(s['slice']) + + csv_slices.append((filename, slice_csv)) buffer = BytesIO() with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip: zip.writestr('lidar_subset.csv', csv_results) + for s in csv_slices: + zip.writestr(*s) buffer.seek(0) return buffer.read() @@ -1155,8 +1170,6 @@ def toImage(self): ax = fig.add_subplot(111, projection='3d') ax.view_init(elev=view_elev, azim=view_azim) - # ZG - lats = np.unique(results['lat'].values) lons = np.unique(results['lon'].values) From b93ec8b7e0f7b98f5eb99c6b5a519296981392d3 Mon Sep 17 00:00:00 2001 From: rileykk Date: Wed, 28 Feb 2024 14:30:28 -0800 Subject: [PATCH 17/27] Response for no data in bounds --- analysis/webservice/algorithms/Lidar.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 19e00d41..905ecf6e 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -40,7 +40,7 @@ from shapely.errors import ShapelyError from webservice.NexusHandler import nexus_handler from webservice.algorithms.NexusCalcHandler import NexusCalcHandler -from webservice.webmodel import NexusResults, NexusProcessingException +from webservice.webmodel import NexusResults, NexusProcessingException, NoDataException logger = logging.getLogger(__name__) @@ -358,6 +358,9 @@ def calc(self, computeOptions, **args): logger.info(f'Matched tile counts by variable: ZG={len(tiles_zg):,}, RH050={len(tiles_rh50):,}, ' f'RH098={len(tiles_rh98):,}, CC={len(tiles_cc):,}') + if all([len(t) == 0 for t in [tiles_zg, tiles_rh50, tiles_rh98, tiles_cc]]): + raise NoDataException(reason='No data was found within the selected parameters') + points_zg, points_50, points_98, points_cc = [], [], [], [] sources = set() @@ -612,12 +615,6 @@ def source_name_from_granule(granule: str, end=-3) -> str: slice_min_lon, slice_max_lon = None, None slice_wkt = None - # tmp - try: - ds.to_netcdf('/tmp/lidar.nc') - except: - print('failed to dump test netcdf :(') - if lat_slice is not None: slice_lat, slice_min_lon, slice_max_lon = lat_slice From deb62e0e680be7a6fa7fb0178f8de086e9c593ba Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 7 Mar 2024 14:43:06 -0800 Subject: [PATCH 18/27] Better handling of close-together flightlines still could use improvement... --- analysis/webservice/algorithms/Lidar.py | 97 +++++++++++++++++-------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 905ecf6e..0795ea99 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -547,41 +547,74 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info(f'Output grid shape: {X.shape}') - logger.info('Gridding ground heights') - gridded_zg = griddata( - list(zip([p['longitude'] for p in points_zg], [p['latitude'] for p in points_zg])), - np.array([p['data'] for p in points_zg]), - (X, Y), - method='nearest', - fill_value=np.nan - ) + gridded_zg, gridded_50, gridded_98, gridded_cc = [], [], [], [] - logger.info('Gridding mean vegetation heights') - gridded_50 = griddata( - list(zip([p['longitude'] for p in points_50], [p['latitude'] for p in points_50])), - np.array([p['data'] for p in points_50]), - (X, Y), - method='nearest', - fill_value=np.nan - ) + source_point_map = {} - logger.info('Gridding canopy heights') - gridded_98 = griddata( - list(zip([p['longitude'] for p in points_98], [p['latitude'] for p in points_98])), - np.array([p['data'] for p in points_98]), - (X, Y), - method='nearest', - fill_value=np.nan - ) + for zg, rh50, rh98, cc in zip_longest(points_zg, points_50, points_98, points_cc): + if zg is not None: + zg_source = zg['source'] + source_point_map.setdefault(zg_source, {}).setdefault('zg', []).append(zg) - logger.info('Gridding canopy coverage') - gridded_cc = griddata( - list(zip([p['longitude'] for p in points_cc], [p['latitude'] for p in points_cc])), - np.array([p['data'] / 10000 for p in points_cc]), - (X, Y), - method='nearest', - fill_value=np.nan - ) + if rh50 is not None: + rh50_source = rh50['source'] + source_point_map.setdefault(rh50_source, {}).setdefault('50', []).append(rh50) + + if rh98 is not None: + rh98_source = rh98['source'] + source_point_map.setdefault(rh98_source, {}).setdefault('98', []).append(rh98) + + if cc is not None: + cc_source = cc['source'] + source_point_map.setdefault(cc_source, {}).setdefault('cc', []).append(cc) + + for src in source_point_map: + logger.info(f'Gridding ground heights for {src}') + source_points = source_point_map[src] + gridded_zg.append(griddata( + list(zip([p['longitude'] for p in source_points['zg']], [p['latitude'] for p in source_points['zg']])), + np.array([p['data'] for p in source_points['zg']]), + (X, Y), + method='nearest', + fill_value=np.nan + )) + + logger.info(f'Gridding mean vegetation heights for {src}') + gridded_50.append(griddata( + list(zip([p['longitude'] for p in source_points['50']], [p['latitude'] for p in source_points['50']])), + np.array([p['data'] for p in source_points['50']]), + (X, Y), + method='nearest', + fill_value=np.nan + )) + + logger.info(f'Gridding canopy heights for {src}') + gridded_98.append(griddata( + list(zip([p['longitude'] for p in source_points['98']], [p['latitude'] for p in source_points['98']])), + np.array([p['data'] for p in source_points['98']]), + (X, Y), + method='nearest', + fill_value=np.nan + )) + + logger.info(f'Gridding canopy coverage for {src}') + gridded_cc.append(griddata( + list(zip([p['longitude'] for p in source_points['cc']], [p['latitude'] for p in source_points['cc']])), + np.array([p['data'] / 10000 for p in source_points['cc']]), + (X, Y), + method='nearest', + fill_value=np.nan + )) + + with warnings.catch_warnings(): + # Numpy will likely raise a warning for all NaN slices. This is expected due to the nature of the data + # being reduced here, so just suppress it for now + warnings.simplefilter("ignore", category=RuntimeWarning) + + gridded_zg = np.nanmean(gridded_zg, axis=0) + gridded_50 = np.nanmean(gridded_50, axis=0) + gridded_98 = np.nanmean(gridded_98, axis=0) + gridded_cc = np.nanmean(gridded_cc, axis=0) gridded_vals = np.array([ gridded_zg, From 3879137f86aef4f0ab1af839ebbda137d5861380 Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 18 Mar 2024 07:19:50 -0700 Subject: [PATCH 19/27] Lidar updates --- analysis/webservice/algorithms/Lidar.py | 77 +- poetry.lock | 2035 +++++++++++++++++++++++ pyproject.toml | 78 + 3 files changed, 2164 insertions(+), 26 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 0795ea99..11445e4f 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -189,6 +189,11 @@ def parse_arguments(self, request): "formatted as Minimum (Western) Longitude, Minimum (Southern) " "Latitude, Maximum (Eastern) Longitude, Maximum (Northern) Latitude.") + max_lat = bounding_polygon.bounds[3] + min_lon = bounding_polygon.bounds[0] + min_lat = bounding_polygon.bounds[1] + max_lon = bounding_polygon.bounds[2] + render_type = request.get_argument('renderType', '2D').upper() if render_type not in ['2D', '3D']: @@ -235,6 +240,11 @@ def parse_arguments(self, request): lat_slice = (float(parts[0]), None, None) else: lat_slice = tuple([float(p) for p in parts]) + + if not (min_lat <= lat_slice[0] <= max_lat): + raise NexusProcessingException( + reason='Selected slice latitude is outside the selected bounding box', code=400 + ) except ValueError: raise NexusProcessingException( reason='Invalid numerical component provided. latSlice must consist of either one number (lat to ' @@ -256,6 +266,11 @@ def parse_arguments(self, request): lon_slice = (float(parts[0]), None, None) else: lon_slice = tuple([float(p) for p in parts]) + + if not (min_lon <= lon_slice[0] <= max_lon): + raise NexusProcessingException( + reason='Selected slice longitude is outside the selected bounding box', code=400 + ) except ValueError: raise NexusProcessingException( reason='Invalid numerical component provided. lonSlice must consist of either one number (lon to ' @@ -285,6 +300,11 @@ def parse_arguments(self, request): code=400 ) + if not bounding_polygon.intersects(slice_line): + raise NexusProcessingException( + reason='Selected line string is entirely outside the selected bounding box', code=400 + ) + map_to_grid = request.get_boolean_arg('mapToGrid') orbit_elev, orbit_step, frame_duration, view_azim, view_elev = None, None, None, None, None @@ -568,7 +588,7 @@ def source_name_from_granule(granule: str, end=-3) -> str: cc_source = cc['source'] source_point_map.setdefault(cc_source, {}).setdefault('cc', []).append(cc) - for src in source_point_map: + for src in sorted(source_point_map): logger.info(f'Gridding ground heights for {src}') source_points = source_point_map[src] gridded_zg.append(griddata( @@ -606,15 +626,21 @@ def source_name_from_granule(granule: str, end=-3) -> str: fill_value=np.nan )) - with warnings.catch_warnings(): - # Numpy will likely raise a warning for all NaN slices. This is expected due to the nature of the data - # being reduced here, so just suppress it for now - warnings.simplefilter("ignore", category=RuntimeWarning) + gridded_zg = np.array(gridded_zg) + gridded_50 = np.array(gridded_50) + gridded_98 = np.array(gridded_98) + gridded_cc = np.array(gridded_cc) + + logger.info('Flattening gridded flightlines to single 2D view') - gridded_zg = np.nanmean(gridded_zg, axis=0) - gridded_50 = np.nanmean(gridded_50, axis=0) - gridded_98 = np.nanmean(gridded_98, axis=0) - gridded_cc = np.nanmean(gridded_cc, axis=0) + # Reduce 3D arrays of gridded flight lines to single 2D array, with each value being the most recent non-nan + # value along axis 0. In other words, stack the flight lines on top of each other, with more recent flight + # lines covering over previous ones + + gridded_zg = np.choose((~np.isnan(gridded_zg)).cumsum(0).argmax(0), gridded_zg) + gridded_50 = np.choose((~np.isnan(gridded_50)).cumsum(0).argmax(0), gridded_50) + gridded_98 = np.choose((~np.isnan(gridded_98)).cumsum(0).argmax(0), gridded_98) + gridded_cc = np.choose((~np.isnan(gridded_cc)).cumsum(0).argmax(0), gridded_cc) gridded_vals = np.array([ gridded_zg, @@ -930,15 +956,19 @@ def toImage(self): extent = [min_lon, max_lon, min_lat, max_lat] fig = plt.figure( - figsize=(8, 10 + (max(0, n_rows - 2) * 4)), constrained_layout=True + figsize=(10, 10 + (max(0, n_rows - 2) * 4)), constrained_layout=True ) - gs = fig.add_gridspec(n_rows, 2) + gs = fig.add_gridspec(n_rows, 4, width_ratios=[1, 0.05, 1, 0.05]) rh50_ax = fig.add_subplot(gs[0, 0]) - rh98_ax = fig.add_subplot(gs[0, 1]) + rh50_cax = fig.add_subplot(gs[0, 1]) + rh98_ax = fig.add_subplot(gs[0, 2]) + rh98_cax = fig.add_subplot(gs[0, 3]) zg_ax = fig.add_subplot(gs[1, 0]) - cc_ax = fig.add_subplot(gs[1, 1]) + zg_cax = fig.add_subplot(gs[1, 1]) + cc_ax = fig.add_subplot(gs[1, 2]) + cc_cax = fig.add_subplot(gs[1, 3]) rh50_im = rh50_ax.imshow(np.flipud(np.squeeze(ds['mean_veg_height'])), extent=extent, aspect='equal', cmap='viridis') @@ -971,20 +1001,15 @@ def toImage(self): except: ... - divider_50 = make_axes_locatable(rh50_ax) - divider_98 = make_axes_locatable(rh98_ax) - divider_zg = make_axes_locatable(zg_ax) - divider_cc = make_axes_locatable(cc_ax) - - cax_50 = divider_50.append_axes("right", size="5%", pad=0.05) - cax_98 = divider_98.append_axes("right", size="5%", pad=0.05) - cax_zg = divider_zg.append_axes("right", size="5%", pad=0.05) - cax_cc = divider_cc.append_axes("right", size="5%", pad=0.05) + rh50_ax.tick_params(axis='x', labelrotation=90) + rh98_ax.tick_params(axis='x', labelrotation=90) + zg_ax.tick_params(axis='x', labelrotation=90) + cc_ax.tick_params(axis='x', labelrotation=90) - rh50_cb = plt.colorbar(rh50_im, cax=cax_50, label='Height above terrain [m]', use_gridspec=True) - rh98_cb = plt.colorbar(rh98_im, cax=cax_98, label='Height above terrain [m]', use_gridspec=True) - zg_cb = plt.colorbar(zg_im, cax=cax_zg, label='Height above ellipsoid [m]', use_gridspec=True) - cc_cb = plt.colorbar(cc_im, cax=cax_cc, label='Coverage [%]', use_gridspec=True) + rh50_cb = plt.colorbar(rh50_im, cax=rh50_cax, label='Height above terrain [m]', use_gridspec=True) + rh98_cb = plt.colorbar(rh98_im, cax=rh98_cax, label='Height above terrain [m]', use_gridspec=True) + zg_cb = plt.colorbar(zg_im, cax=zg_cax, label='Height above ellipsoid [m]', use_gridspec=True) + cc_cb = plt.colorbar(cc_im, cax=cc_cax, label='Coverage [%]', use_gridspec=True) rh50_ax.set_title('Mean Vegetation Height') rh98_ax.set_title('Canopy Height') diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..bf1a4f1c --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2035 @@ +# This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand. + +[[package]] +name = "astroid" +version = "2.15.8" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, + {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = [ + {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, + {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, +] + +[[package]] +name = "backports-functools-lru-cache" +version = "1.6.1" +description = "Backport of functools.lru_cache" +optional = false +python-versions = ">=2.6" +files = [ + {file = "backports.functools_lru_cache-1.6.1-py2.py3-none-any.whl", hash = "sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848"}, + {file = "backports.functools_lru_cache-1.6.1.tar.gz", hash = "sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a"}, +] + +[package.extras] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8"] + +[[package]] +name = "boto3" +version = "1.16.63" +description = "The AWS SDK for Python" +optional = false +python-versions = "*" +files = [ + {file = "boto3-1.16.63-py2.py3-none-any.whl", hash = "sha256:1c0003609e63e8cff51dee7a49e904bcdb20e140b5f7a10a03006289fd8c8dc1"}, + {file = "boto3-1.16.63.tar.gz", hash = "sha256:c919dac9773115025e1e2a7e462f60ca082e322bb6f4354247523e4226133b0b"}, +] + +[package.dependencies] +botocore = ">=1.19.63,<1.20.0" +jmespath = ">=0.7.1,<1.0.0" +s3transfer = ">=0.3.0,<0.4.0" + +[[package]] +name = "botocore" +version = "1.19.63" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = "*" +files = [ + {file = "botocore-1.19.63-py2.py3-none-any.whl", hash = "sha256:ad4adfcc195b5401d84b0c65d3a89e507c1d54c201879c8761ff10ef5c361e21"}, + {file = "botocore-1.19.63.tar.gz", hash = "sha256:d3694f6ef918def8082513e5ef309cd6cd83b612e9984e3a66e8adc98c650a92"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<1.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<1.27", markers = "python_version != \"3.4\""} + +[[package]] +name = "cassandra-driver" +version = "3.24.0" +description = "DataStax Driver for Apache Cassandra" +optional = false +python-versions = "*" +files = [ + {file = "cassandra-driver-3.24.0.tar.gz", hash = "sha256:83ec8d9a5827ee44bb1c0601a63696a8a9086beaf0151c8255556299246081bd"}, + {file = "cassandra_driver-3.24.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:110a89f41b3a7cfe92aef67119464a6af9a86c14fa018ff0bb3b152942331f7c"}, + {file = "cassandra_driver-3.24.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b48a710eadf6e1ea33489522fdd1fd9f2e23bb4408ee9139205f3f8072ab4e78"}, + {file = "cassandra_driver-3.24.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7526443f02b85652a8654be466730fab8052883103536cc94085c8593b7162a9"}, + {file = "cassandra_driver-3.24.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a4a56292ab4bed0bedaf5d3e88aa6c38bc130ef65bed12774a2ab62d01087f3e"}, + {file = "cassandra_driver-3.24.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a74ee7e047f73ddbab10546d6c6733836096611b88c8b416c9969895f88c512b"}, + {file = "cassandra_driver-3.24.0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:4e228e3951150e7905fe1363e2dbe61940d8a34df208764452044934d8ce061f"}, + {file = "cassandra_driver-3.24.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8024490895f48f78be06bad5ebec1b2d6adb658f5e3d5f67d9b4c8669cf391f2"}, + {file = "cassandra_driver-3.24.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:970900e1d181f99ac6560ada02969c04d24d21bce1419c7daaf154d5c892ab3f"}, + {file = "cassandra_driver-3.24.0-cp35-cp35m-win32.whl", hash = "sha256:e7d641dca1fdbf239bc3edbeb6bae63eb50382eee50567334dd62aecdd7cf4b4"}, + {file = "cassandra_driver-3.24.0-cp35-cp35m-win_amd64.whl", hash = "sha256:81a1355b2edd08f81ef1577c9fc51812e12d309f9b0a7ebeddecceb0dc2d0b86"}, + {file = "cassandra_driver-3.24.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea1ffe18b40f7323cc57270fc8e4c680c0af39b1d2108b0aa624895d435808e6"}, + {file = "cassandra_driver-3.24.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3d1287cfa1ebf439646d31808b19d784884d8f717fce2e7728130e9ab8ff9ccd"}, + {file = "cassandra_driver-3.24.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2ccbfdbf168fbb5e0055f2161790de12c280840881cb3fb7893848af1e4d9c07"}, + {file = "cassandra_driver-3.24.0-cp36-cp36m-win32.whl", hash = "sha256:59e06084ce3079b1bc69e3de3e22a07a722d984f8b51fc0a98e98112bfbba9b8"}, + {file = "cassandra_driver-3.24.0-cp36-cp36m-win_amd64.whl", hash = "sha256:def8803132d5ec801333ebaa46edd8c0f28e56fefdb41acf0a1fad9e1c72bfcd"}, + {file = "cassandra_driver-3.24.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:97f7668636a48282d0e65eeb914d04fd1280c2304de004bed1ea4de5c9c42212"}, + {file = "cassandra_driver-3.24.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:590ca23f6ca6996ea454d7531d74ea526c90c6734842f62cd190a7d24c8a1bff"}, + {file = "cassandra_driver-3.24.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f90699c8f1aa4eb58bf84be0fbff859ba094799b662592d1c20237c7cade702e"}, + {file = "cassandra_driver-3.24.0-cp37-cp37m-win32.whl", hash = "sha256:f0f95cdbaba88d57a0529eb67b83536279a0ec02f1b51bc27a73c1eb3aaabe07"}, + {file = "cassandra_driver-3.24.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d1d302abc500a3ddfc1922f9914c843d4c57c87f5480f0b226e8f2806a2281ab"}, + {file = "cassandra_driver-3.24.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e793f4545b53353c3df5daafb44ce2b4479aad1aebb0dd4989490283716aff7"}, + {file = "cassandra_driver-3.24.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:64b1386a18940e70462fe564e1e056891ded8c4a812d9dc8ad21a500f959de1d"}, + {file = "cassandra_driver-3.24.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:51d43208dec31f59a4c038b105cc43c9ebb86a9172970079556918208472e77c"}, + {file = "cassandra_driver-3.24.0-cp38-cp38-win32.whl", hash = "sha256:cf132f0d170ec1afc90780349275e7932f64d603492652f29fd9a4082d5f2ae6"}, + {file = "cassandra_driver-3.24.0-cp38-cp38-win_amd64.whl", hash = "sha256:c76ff976df5975d4444c330d52ea17324e7f33e35a6366b8a8286e5102bacdd8"}, +] + +[package.dependencies] +futures = "*" +geomet = ">=0.1,<0.3" +six = ">=1.9" + +[package.extras] +graph = ["gremlinpython (==3.4.6)"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cftime" +version = "1.6.2" +description = "Time-handling functionality from netcdf4-python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cftime-1.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4d2a1920f0aad663f25700b30621ff64af373499e52b544da1148dd8c09409a"}, + {file = "cftime-1.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ba7909a0cd4adcb16797d8d6ab2767e7ddb980b2bf9dbabfc71b3bdd94f072b"}, + {file = "cftime-1.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb294fdb80e33545ae54b4421df35c4e578708a5ffce1c00408b2294e70ecef"}, + {file = "cftime-1.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:2abdac6ca5b8b6102f319122546739dfc42406b816c16f2a98a8f0cd406d3bf0"}, + {file = "cftime-1.6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eb7f8cd0996640b83020133b5ef6b97fc9216c3129eaeeaca361abdff5d82166"}, + {file = "cftime-1.6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d49d69c64cee2c175478eed84c3a57fce083da4ceebce16440f72be561a8489"}, + {file = "cftime-1.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455cec3627e6ca8694b0d9201da6581eb4381b58389f1fbcb51a14fa0e2b3d94"}, + {file = "cftime-1.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:29c18601abea0fd160fbe423e05c7a56fe1d38dd250a6b010de499a132d3fe18"}, + {file = "cftime-1.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:afb5b38b51b8bc02f1656a9f15c52b0b20a3999adbe1ab9ac57f926e0065b48a"}, + {file = "cftime-1.6.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aedfb7a783d19d7a30cb41951310f3bfe98f9f21fffc723c8af08a11962b0b17"}, + {file = "cftime-1.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3042048324b4d6a1066c978ec78101effdd84320e8862bfdbf8122d7ad7588ec"}, + {file = "cftime-1.6.2-cp37-none-win_amd64.whl", hash = "sha256:ee70fa069802652cf534de1dd3fc590b7d22d4127447bf96ac9849abcdadadf1"}, + {file = "cftime-1.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:93f00f454329c1f2588ebca2650e8edf7607d6189dbdcc81b5f3be2080155cc4"}, + {file = "cftime-1.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e83db2fdda900eb154a9f79dfb665ac6190781c61d2e18151996de5ee7ffd8a2"}, + {file = "cftime-1.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56d0242fc4990584b265622622b25bb262a178097711d2d95e53ef52a9d23e7e"}, + {file = "cftime-1.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:055d5d60a756c6c1857cf84d77655bb707057bb6c4a4fbb104a550e76c40aad9"}, + {file = "cftime-1.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0955e1f3e1c09a9e0296b50f135ff9719cb2466f81c8ad4a10ef06fa394de984"}, + {file = "cftime-1.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:07fdef2f75a0f0952b0376fa4cd08ef8a1dad3b963976ac07517811d434936b7"}, + {file = "cftime-1.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:892d5dc38f8b998c83a2a01f131e63896d020586de473e1878f9e85acc70ad44"}, + {file = "cftime-1.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86fe550b94525c327578a90b2e13418ca5ba6c636d5efe3edec310e631757eea"}, + {file = "cftime-1.6.2.tar.gz", hash = "sha256:8614c00fb8a5046de304fdd86dbd224f99408185d7b245ac6628d0276596e6d2"}, +] + +[package.dependencies] +numpy = ">1.13.3" + +[[package]] +name = "cftime" +version = "1.6.3" +description = "Time-handling functionality from netcdf4-python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cftime-1.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b62d42546fa5c914dfea5b15a9aaed2087ea1211cc36d08c374502ef95892038"}, + {file = "cftime-1.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb6dd70b2ccabfe1a14b7fbb0bbdce0418e71697094373c0d573c880790fa291"}, + {file = "cftime-1.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9878bfd8c1c3f24184ecbd528f739ba46ebaceaf1c8a24d348d7befb117a285"}, + {file = "cftime-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:3cf6e216a4c06f9a628cdf8e9c9d5e8097fb3eb02dd087dd14ab3b18478a7271"}, + {file = "cftime-1.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d2c01456d9d7b46aa710a41d1c711a50d5ea259aff4a987d0e973d1093bc922"}, + {file = "cftime-1.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80eb1170ce1639016f55760847f4aadd04b0312496c5bac2797e930914bba48d"}, + {file = "cftime-1.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87dadd0824262bdd7493babd2a44447da0a22175ded8ae9e060a3aebec7c5d7"}, + {file = "cftime-1.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:0a38eb9f5c733a23e1714bd3ef2762ed5acee34f127670f8fb4ad6464946f6b3"}, + {file = "cftime-1.6.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2d113a01ab924445e61d65c26bbd95bc08e4a22878d3b947064bba056c884c4a"}, + {file = "cftime-1.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f11685663a6af97418908060492a07663c16d42519c139ca03c2ffb1377fd25"}, + {file = "cftime-1.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a98abb1d46d118e52b0611ce668a0b714b407be26177ef0581ecf5e95f894725"}, + {file = "cftime-1.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:4d6fbd5f41b322cfa7b0ac3aaadeceb4450100a164b5bccbbb9e7c5048489a88"}, + {file = "cftime-1.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bedb577bc8b8f3f10f5336c0792e5dae88605781890f50f36b45bb46907968e8"}, + {file = "cftime-1.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:022dabf1610cdd04a693e730fa8f71d307059717f29dba921e7486e553412bb4"}, + {file = "cftime-1.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbf782ab4ac0605bdec2b941952c897595613203942b7f8c2fccd17efa5147df"}, + {file = "cftime-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:9eb177a02db7cd84aa6962278e4bd2d3106a545de82e6aacd9404f1e153661db"}, + {file = "cftime-1.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b86be8c2f254147be4ba88f12099466dde457a4a3a21de6c69d52a7224c13ae"}, + {file = "cftime-1.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:523b9a6bf03f5e36407979e248381d0fcab2d225b915bbde77d00c6dde192b90"}, + {file = "cftime-1.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a14d2c7d22fd2a6dfa6ad563283b6d6679f1df95e0ed8d14b8f284dad402887"}, + {file = "cftime-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:d9b00c2844c7a1701d8ede5336b6321dfee256ceab81a34a1aff0483d56891a6"}, + {file = "cftime-1.6.3.tar.gz", hash = "sha256:d0a6b29f72a13f08e008b9becff247cc75c84acb213332ede18879c5b6aa4dfd"}, +] + +[package.dependencies] +numpy = {version = ">1.13.3", markers = "python_version < \"3.12.0.rc1\""} + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "contourpy" +version = "1.1.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc"}, + {file = "contourpy-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, + {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, + {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, + {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, + {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, + {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, + {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, + {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, + {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, + {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, + {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, + {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, + {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, + {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, + {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, + {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, + {file = "contourpy-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104"}, + {file = "contourpy-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f"}, + {file = "contourpy-1.1.0.tar.gz", hash = "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.2.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + +[[package]] +name = "contourpy" +version = "1.1.1" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:46e24f5412c948d81736509377e255f6040e94216bf1a9b5ea1eaa9d29f6ec1b"}, + {file = "contourpy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e48694d6a9c5a26ee85b10130c77a011a4fedf50a7279fa0bdaf44bafb4299d"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a66045af6cf00e19d02191ab578a50cb93b2028c3eefed999793698e9ea768ae"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ebf42695f75ee1a952f98ce9775c873e4971732a87334b099dde90b6af6a916"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6aec19457617ef468ff091669cca01fa7ea557b12b59a7908b9474bb9674cf0"}, + {file = "contourpy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462c59914dc6d81e0b11f37e560b8a7c2dbab6aca4f38be31519d442d6cde1a1"}, + {file = "contourpy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6d0a8efc258659edc5299f9ef32d8d81de8b53b45d67bf4bfa3067f31366764d"}, + {file = "contourpy-1.1.1-cp310-cp310-win32.whl", hash = "sha256:d6ab42f223e58b7dac1bb0af32194a7b9311065583cc75ff59dcf301afd8a431"}, + {file = "contourpy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:549174b0713d49871c6dee90a4b499d3f12f5e5f69641cd23c50a4542e2ca1eb"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:407d864db716a067cc696d61fa1ef6637fedf03606e8417fe2aeed20a061e6b2"}, + {file = "contourpy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe80c017973e6a4c367e037cb31601044dd55e6bfacd57370674867d15a899b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e30aaf2b8a2bac57eb7e1650df1b3a4130e8d0c66fc2f861039d507a11760e1b"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3de23ca4f381c3770dee6d10ead6fff524d540c0f662e763ad1530bde5112532"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:566f0e41df06dfef2431defcfaa155f0acfa1ca4acbf8fd80895b1e7e2ada40e"}, + {file = "contourpy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04c2f0adaf255bf756cf08ebef1be132d3c7a06fe6f9877d55640c5e60c72c5"}, + {file = "contourpy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0c188ae66b772d9d61d43c6030500344c13e3f73a00d1dc241da896f379bb62"}, + {file = "contourpy-1.1.1-cp311-cp311-win32.whl", hash = "sha256:0683e1ae20dc038075d92e0e0148f09ffcefab120e57f6b4c9c0f477ec171f33"}, + {file = "contourpy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:8636cd2fc5da0fb102a2504fa2c4bea3cbc149533b345d72cdf0e7a924decc45"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:560f1d68a33e89c62da5da4077ba98137a5e4d3a271b29f2f195d0fba2adcb6a"}, + {file = "contourpy-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24216552104ae8f3b34120ef84825400b16eb6133af2e27a190fdc13529f023e"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56de98a2fb23025882a18b60c7f0ea2d2d70bbbcfcf878f9067234b1c4818442"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:07d6f11dfaf80a84c97f1a5ba50d129d9303c5b4206f776e94037332e298dda8"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1eaac5257a8f8a047248d60e8f9315c6cff58f7803971170d952555ef6344a7"}, + {file = "contourpy-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19557fa407e70f20bfaba7d55b4d97b14f9480856c4fb65812e8a05fe1c6f9bf"}, + {file = "contourpy-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:081f3c0880712e40effc5f4c3b08feca6d064cb8cfbb372ca548105b86fd6c3d"}, + {file = "contourpy-1.1.1-cp312-cp312-win32.whl", hash = "sha256:059c3d2a94b930f4dafe8105bcdc1b21de99b30b51b5bce74c753686de858cb6"}, + {file = "contourpy-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f44d78b61740e4e8c71db1cf1fd56d9050a4747681c59ec1094750a658ceb970"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:70e5a10f8093d228bb2b552beeb318b8928b8a94763ef03b858ef3612b29395d"}, + {file = "contourpy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8394e652925a18ef0091115e3cc191fef350ab6dc3cc417f06da66bf98071ae9"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5bd5680f844c3ff0008523a71949a3ff5e4953eb7701b28760805bc9bcff217"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66544f853bfa85c0d07a68f6c648b2ec81dafd30f272565c37ab47a33b220684"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0c02b75acfea5cab07585d25069207e478d12309557f90a61b5a3b4f77f46ce"}, + {file = "contourpy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41339b24471c58dc1499e56783fedc1afa4bb018bcd035cfb0ee2ad2a7501ef8"}, + {file = "contourpy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f29fb0b3f1217dfe9362ec55440d0743fe868497359f2cf93293f4b2701b8251"}, + {file = "contourpy-1.1.1-cp38-cp38-win32.whl", hash = "sha256:f9dc7f933975367251c1b34da882c4f0e0b2e24bb35dc906d2f598a40b72bfc7"}, + {file = "contourpy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:498e53573e8b94b1caeb9e62d7c2d053c263ebb6aa259c81050766beb50ff8d9"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba42e3810999a0ddd0439e6e5dbf6d034055cdc72b7c5c839f37a7c274cb4eba"}, + {file = "contourpy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c06e4c6e234fcc65435223c7b2a90f286b7f1b2733058bdf1345d218cc59e34"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca6fab080484e419528e98624fb5c4282148b847e3602dc8dbe0cb0669469887"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93df44ab351119d14cd1e6b52a5063d3336f0754b72736cc63db59307dabb718"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eafbef886566dc1047d7b3d4b14db0d5b7deb99638d8e1be4e23a7c7ac59ff0f"}, + {file = "contourpy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efe0fab26d598e1ec07d72cf03eaeeba8e42b4ecf6b9ccb5a356fde60ff08b85"}, + {file = "contourpy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f08e469821a5e4751c97fcd34bcb586bc243c39c2e39321822060ba902eac49e"}, + {file = "contourpy-1.1.1-cp39-cp39-win32.whl", hash = "sha256:bfc8a5e9238232a45ebc5cb3bfee71f1167064c8d382cadd6076f0d51cff1da0"}, + {file = "contourpy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:c84fdf3da00c2827d634de4fcf17e3e067490c4aea82833625c4c8e6cdea0887"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:229a25f68046c5cf8067d6d6351c8b99e40da11b04d8416bf8d2b1d75922521e"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a10dab5ea1bd4401c9483450b5b0ba5416be799bbd50fc7a6cc5e2a15e03e8a3"}, + {file = "contourpy-1.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f9147051cb8fdb29a51dc2482d792b3b23e50f8f57e3720ca2e3d438b7adf23"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a75cc163a5f4531a256f2c523bd80db509a49fc23721b36dd1ef2f60ff41c3cb"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b53d5769aa1f2d4ea407c65f2d1d08002952fac1d9e9d307aa2e1023554a163"}, + {file = "contourpy-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11b836b7dbfb74e049c302bbf74b4b8f6cb9d0b6ca1bf86cfa8ba144aedadd9c"}, + {file = "contourpy-1.1.1.tar.gz", hash = "sha256:96ba37c2e24b7212a77da85004c38e7c4d155d3e72a45eeaf22c1f03f607e8ab"}, +] + +[package.dependencies] +numpy = {version = ">=1.16,<2.0", markers = "python_version <= \"3.11\""} + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.4.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "wurlitzer"] + +[[package]] +name = "coverage" +version = "7.4.3" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "elastic-transport" +version = "8.12.0" +description = "Transport classes and utilities shared among Python Elastic client libraries" +optional = false +python-versions = ">=3.7" +files = [ + {file = "elastic-transport-8.12.0.tar.gz", hash = "sha256:48839b942fcce199eece1558ecea6272e116c58da87ca8d495ef12eb61effaf7"}, + {file = "elastic_transport-8.12.0-py3-none-any.whl", hash = "sha256:87d9dc9dee64a05235e7624ed7e6ab6e5ca16619aa7a6d22e853273b9f1cfbee"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.2,<3" + +[package.extras] +develop = ["aiohttp", "furo", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "sphinx (>2)", "sphinx-autodoc-typehints", "trustme"] + +[[package]] +name = "elasticsearch" +version = "8.3.1" +description = "Python client for Elasticsearch" +optional = false +python-versions = ">=3.6, <4" +files = [ + {file = "elasticsearch-8.3.1-py3-none-any.whl", hash = "sha256:c29e9e59bfbe345f4a03877e5087aa93fdf7556ef538f52ebdfc26a89bbcdbb1"}, + {file = "elasticsearch-8.3.1.tar.gz", hash = "sha256:bd6c3b8c751114f42c0265646a202da0f00f6337a9e334b4abc60ef03d1aad7c"}, +] + +[package.dependencies] +elastic-transport = ">=8,<9" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +requests = ["requests (>=2.4.0,<3.0.0)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fonttools" +version = "4.49.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, + {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, + {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, + {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, + {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, + {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, + {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, + {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, + {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, + {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, + {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, + {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, + {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "pycairo", "scipy"] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.1.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "futures" +version = "3.0.5" +description = "Backport of the concurrent.futures package from Python 3.2" +optional = false +python-versions = "*" +files = [ + {file = "futures-3.0.5-py2-none-any.whl", hash = "sha256:f7f16b6bf9653a918a03f1f2c2d62aac0cd64b1bc088e93ea279517f6b61120b"}, + {file = "futures-3.0.5.tar.gz", hash = "sha256:0542525145d5afc984c88f914a0c85c77527f65946617edb5274f72406f981df"}, +] + +[[package]] +name = "geographiclib" +version = "2.0" +description = "The geodesic routines from GeographicLib" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"}, + {file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"}, +] + +[[package]] +name = "geomet" +version = "0.2.1.post1" +description = "GeoJSON <-> WKT/WKB conversion utilities" +optional = false +python-versions = ">2.6, !=3.3.*, <4" +files = [ + {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, + {file = "geomet-0.2.1.post1.tar.gz", hash = "sha256:91d754f7c298cbfcabd3befdb69c641c27fe75e808b27aa55028605761d17e95"}, +] + +[package.dependencies] +click = "*" +six = "*" + +[[package]] +name = "geopy" +version = "2.4.1" +description = "Python Geocoding Toolbox" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7"}, + {file = "geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1"}, +] + +[package.dependencies] +geographiclib = ">=1.52,<3" + +[package.extras] +aiohttp = ["aiohttp"] +dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"] +dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"] +requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"] +timezone = ["pytz"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "importlib-metadata" +version = "4.11.4" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "6.3.0" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.3.0-py3-none-any.whl", hash = "sha256:783407aa1cd05550e3aa123e8f7cfaebee35ffa9cb0242919e2d1e4172222705"}, + {file = "importlib_resources-6.3.0.tar.gz", hash = "sha256:166072a97e86917a9025876f34286f549b9caf1d10b35a1b372bffa1600c6569"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "0.10.0" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, + {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.10.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.5" +description = "Python plotting package" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.5-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:4a87b69cb1cb20943010f63feb0b2901c17a3b435f75349fd9865713bfa63925"}, + {file = "matplotlib-3.7.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:d3ce45010fefb028359accebb852ca0c21bd77ec0f281952831d235228f15810"}, + {file = "matplotlib-3.7.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbea1e762b28400393d71be1a02144aa16692a3c4c676ba0178ce83fc2928fdd"}, + {file = "matplotlib-3.7.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec0e1adc0ad70ba8227e957551e25a9d2995e319c29f94a97575bb90fa1d4469"}, + {file = "matplotlib-3.7.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6738c89a635ced486c8a20e20111d33f6398a9cbebce1ced59c211e12cd61455"}, + {file = "matplotlib-3.7.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1210b7919b4ed94b5573870f316bca26de3e3b07ffdb563e79327dc0e6bba515"}, + {file = "matplotlib-3.7.5-cp310-cp310-win32.whl", hash = "sha256:068ebcc59c072781d9dcdb82f0d3f1458271c2de7ca9c78f5bd672141091e9e1"}, + {file = "matplotlib-3.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:f098ffbaab9df1e3ef04e5a5586a1e6b1791380698e84938d8640961c79b1fc0"}, + {file = "matplotlib-3.7.5-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:f65342c147572673f02a4abec2d5a23ad9c3898167df9b47c149f32ce61ca078"}, + {file = "matplotlib-3.7.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ddf7fc0e0dc553891a117aa083039088d8a07686d4c93fb8a810adca68810af"}, + {file = "matplotlib-3.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0ccb830fc29442360d91be48527809f23a5dcaee8da5f4d9b2d5b867c1b087b8"}, + {file = "matplotlib-3.7.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc6bb28178e844d1f408dd4d6341ee8a2e906fc9e0fa3dae497da4e0cab775d"}, + {file = "matplotlib-3.7.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b15c4c2d374f249f324f46e883340d494c01768dd5287f8bc00b65b625ab56c"}, + {file = "matplotlib-3.7.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d028555421912307845e59e3de328260b26d055c5dac9b182cc9783854e98fb"}, + {file = "matplotlib-3.7.5-cp311-cp311-win32.whl", hash = "sha256:fe184b4625b4052fa88ef350b815559dd90cc6cc8e97b62f966e1ca84074aafa"}, + {file = "matplotlib-3.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:084f1f0f2f1010868c6f1f50b4e1c6f2fb201c58475494f1e5b66fed66093647"}, + {file = "matplotlib-3.7.5-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:34bceb9d8ddb142055ff27cd7135f539f2f01be2ce0bafbace4117abe58f8fe4"}, + {file = "matplotlib-3.7.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c5a2134162273eb8cdfd320ae907bf84d171de948e62180fa372a3ca7cf0f433"}, + {file = "matplotlib-3.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:039ad54683a814002ff37bf7981aa1faa40b91f4ff84149beb53d1eb64617980"}, + {file = "matplotlib-3.7.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d742ccd1b09e863b4ca58291728db645b51dab343eebb08d5d4b31b308296ce"}, + {file = "matplotlib-3.7.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:743b1c488ca6a2bc7f56079d282e44d236bf375968bfd1b7ba701fd4d0fa32d6"}, + {file = "matplotlib-3.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:fbf730fca3e1f23713bc1fae0a57db386e39dc81ea57dc305c67f628c1d7a342"}, + {file = "matplotlib-3.7.5-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:cfff9b838531698ee40e40ea1a8a9dc2c01edb400b27d38de6ba44c1f9a8e3d2"}, + {file = "matplotlib-3.7.5-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:1dbcca4508bca7847fe2d64a05b237a3dcaec1f959aedb756d5b1c67b770c5ee"}, + {file = "matplotlib-3.7.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4cdf4ef46c2a1609a50411b66940b31778db1e4b73d4ecc2eaa40bd588979b13"}, + {file = "matplotlib-3.7.5-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:167200ccfefd1674b60e957186dfd9baf58b324562ad1a28e5d0a6b3bea77905"}, + {file = "matplotlib-3.7.5-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:53e64522934df6e1818b25fd48cf3b645b11740d78e6ef765fbb5fa5ce080d02"}, + {file = "matplotlib-3.7.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e3bc79b2d7d615067bd010caff9243ead1fc95cf735c16e4b2583173f717eb"}, + {file = "matplotlib-3.7.5-cp38-cp38-win32.whl", hash = "sha256:6b641b48c6819726ed47c55835cdd330e53747d4efff574109fd79b2d8a13748"}, + {file = "matplotlib-3.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:f0b60993ed3488b4532ec6b697059897891927cbfc2b8d458a891b60ec03d9d7"}, + {file = "matplotlib-3.7.5-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:090964d0afaff9c90e4d8de7836757e72ecfb252fb02884016d809239f715651"}, + {file = "matplotlib-3.7.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9fc6fcfbc55cd719bc0bfa60bde248eb68cf43876d4c22864603bdd23962ba25"}, + {file = "matplotlib-3.7.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7cc3078b019bb863752b8b60e8b269423000f1603cb2299608231996bd9d54"}, + {file = "matplotlib-3.7.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e4e9a868e8163abaaa8259842d85f949a919e1ead17644fb77a60427c90473c"}, + {file = "matplotlib-3.7.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa7ebc995a7d747dacf0a717d0eb3aa0f0c6a0e9ea88b0194d3a3cd241a1500f"}, + {file = "matplotlib-3.7.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3785bfd83b05fc0e0c2ae4c4a90034fe693ef96c679634756c50fe6efcc09856"}, + {file = "matplotlib-3.7.5-cp39-cp39-win32.whl", hash = "sha256:29b058738c104d0ca8806395f1c9089dfe4d4f0f78ea765c6c704469f3fffc81"}, + {file = "matplotlib-3.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:fd4028d570fa4b31b7b165d4a685942ae9cdc669f33741e388c01857d9723eab"}, + {file = "matplotlib-3.7.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2a9a3f4d6a7f88a62a6a18c7e6a84aedcaf4faf0708b4ca46d87b19f1b526f88"}, + {file = "matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b3fd853d4a7f008a938df909b96db0b454225f935d3917520305b90680579c"}, + {file = "matplotlib-3.7.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ad550da9f160737d7890217c5eeed4337d07e83ca1b2ca6535078f354e7675"}, + {file = "matplotlib-3.7.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:20da7924a08306a861b3f2d1da0d1aa9a6678e480cf8eacffe18b565af2813e7"}, + {file = "matplotlib-3.7.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b45c9798ea6bb920cb77eb7306409756a7fab9db9b463e462618e0559aecb30e"}, + {file = "matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a99866267da1e561c7776fe12bf4442174b79aac1a47bd7e627c7e4d077ebd83"}, + {file = "matplotlib-3.7.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b6aa62adb6c268fc87d80f963aca39c64615c31830b02697743c95590ce3fbb"}, + {file = "matplotlib-3.7.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e530ab6a0afd082d2e9c17eb1eb064a63c5b09bb607b2b74fa41adbe3e162286"}, + {file = "matplotlib-3.7.5.tar.gz", hash = "sha256:1e5c971558ebc811aa07f54c7b7c677d78aa518ef4c390e14673a09e0860184a"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.0.1" +numpy = ">=1.20,<2" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mock" +version = "4.0.3" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, + {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest (<5.4)", "pytest-cov"] + +[[package]] +name = "mpld3" +version = "0.5.1" +description = "D3 Viewer for Matplotlib" +optional = false +python-versions = "*" +files = [ + {file = "mpld3-0.5.1.tar.gz", hash = "sha256:1677a314125ff2984e2f291d595188d5ee8ca66ac21b0c2c8633961bc33a7bf8"}, +] + +[package.dependencies] +jinja2 = "*" +matplotlib = "*" + +[[package]] +name = "netcdf4" +version = "1.5.5.1" +description = "Provides an object-oriented python interface to the netCDF version 4 library." +optional = false +python-versions = "*" +files = [ + {file = "netCDF4-1.5.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:29d829b05b010ded1825c8745d4eeb5190fa928b87f5e9edf06a51eb0834fb40"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:50b5466c08c7f4be82241d654d9a86e373d487f080d05fb74a7eca00357e7204"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7901a8d7561cf179a8c839e0ff346a918ff62fd3c66d079426fe38fde74e4ca0"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:57795a70d9bbfbaf9a7e48bc944c75f43a12e10994b4805f178c0c4fae21223e"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:43f2ef8531ae55dcd0f4352609afadaf55f48bef897448ffabd6848f9e630e45"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:f1536837acfd560c3aa0b499b297c92a8c8449b2ffb915c828beaedb4df5cf68"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:d345ec5c185de4dceddbe47b944de2c5521a4bc80230f34d1d6c92817d6b6c2b"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-win32.whl", hash = "sha256:897f818bfb4f63af69a61f90841495851e8cebce88aaaf2673e6225d19e1c268"}, + {file = "netCDF4-1.5.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6018d9a0c451b8271d85f5d9cbae22a8ff7d74277a451b9423d9d14c6e673a22"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:70adfb91dd3befd3ba7b4ead1f257015769ae4662cc36372005988e22fc7a2d2"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4a653cac1110199859ecc8b2a3ab3e1679fcbf1465f4089a8c8f68b190453aca"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f632cd94636731347e9d61f0117204f9ab1f6f9994897564ab115fc5a7cd9781"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:d2cd9ff96cb854bb3b38c54216a6ed2684107b9c18b17fc153514965ceb3d154"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b05b65fa8dfb9e83d4ea4b4547e85ae03c8a9709b941355a00c05f5cef88cd62"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-win32.whl", hash = "sha256:b2404230cf97cf99b43ee76566da785ad59e21405febb1a2902268065da54dd5"}, + {file = "netCDF4-1.5.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5f6b2e75b330aedd3fedcb3ba03d84ce755379cdf81bce36d630bf732f80a5cd"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6e671b35ce871ce3fd86a55b7cf2ae0194521cf985edeb977fb9fe8e72a6c1de"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9c4a91f4a4da3f8e315b7a0acdd5544f150a79a49d46b7b6cc4b037514527987"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4b619f1b91afc4b973fd68f828e61da5b552a3da0d5132f67e1b340d716fed6b"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ce784b0f529d816590ae2b787780f27f8420411e7b5374a10390ea451f69bd61"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:31d23ba63b588aee31e90b09bd5c1fd7884ad1437dc0436e46e2f25839b67f68"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-win32.whl", hash = "sha256:0f52b40a5001f8090dcc76bc0bd854ff26e81c1671f55b1624468c5563b84700"}, + {file = "netCDF4-1.5.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:8dcc478dae0d3f4e5c927cfd5bc0676d4e1d105a07a9b2499a9c099be4e31f8e"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c0efca35106414885872f932db99af37bf5a85283fd66b9f9f0925f32ca22c62"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:eb3d4ab88459eccde69868b812a7c5e503665e65449cf6d2a8f5f47fa4dafb22"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:3b9ccfb9400c201e51b9f003600bf5eec96455e7238bac94cc2403247b101e50"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ae187fa3a6e31293923004253733798ab6ec0e4a9dbaea284ba372367b8e4d38"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fc750266caf867ee87aa8da95308887d201b5f74b7fb48fc49694943d7747649"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-win32.whl", hash = "sha256:83c6ffc8cb05062974437b39c51054e31f27dcf0722716575b320aaed4e33a7f"}, + {file = "netCDF4-1.5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:d6cfcac6a776629a0932b6ab93e7c49f48d4cba8e71e584ae3e359fb18cc40ba"}, + {file = "netCDF4-1.5.5.1-pp37-pypy37_pp73-win32.whl", hash = "sha256:72dbe967cbbfc67b45145615b79fac1d542200cf750e461d23e1e9b214e51a9a"}, + {file = "netCDF4-1.5.5.1.tar.gz", hash = "sha256:d957e55a667d1fc651ddef22fea10ded0f142b7d9dbbf4d08c0012d32f445abd"}, +] + +[package.dependencies] +cftime = "*" +numpy = ">=1.9" + +[[package]] +name = "numpy" +version = "1.24.3" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"}, + {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"}, + {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"}, + {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"}, + {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"}, + {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"}, + {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"}, + {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"}, + {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"}, + {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"}, + {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"}, + {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"}, + {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"}, + {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"}, + {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"}, + {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"}, + {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"}, + {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"}, + {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pandas" +version = "2.0.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, + {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, + {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, + {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, + {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, + {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, + {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, + {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, + {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, + {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, + {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.20.3", markers = "python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] +aws = ["s3fs (>=2021.08.0)"] +clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] +compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] +computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2021.07.0)"] +gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] +hdf5 = ["tables (>=3.6.1)"] +html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] +mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] +spss = ["pyreadstat (>=1.1.2)"] +sql-other = ["SQLAlchemy (>=1.4.16)"] +test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.6.3)"] + +[[package]] +name = "pillow" +version = "8.1.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"}, + {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"}, + {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174"}, + {file = "Pillow-8.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded"}, + {file = "Pillow-8.1.0-cp36-cp36m-win32.whl", hash = "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"}, + {file = "Pillow-8.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d"}, + {file = "Pillow-8.1.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234"}, + {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8"}, + {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17"}, + {file = "Pillow-8.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7"}, + {file = "Pillow-8.1.0-cp37-cp37m-win32.whl", hash = "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e"}, + {file = "Pillow-8.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b"}, + {file = "Pillow-8.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0"}, + {file = "Pillow-8.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a"}, + {file = "Pillow-8.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d"}, + {file = "Pillow-8.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"}, + {file = "Pillow-8.1.0-cp38-cp38-win32.whl", hash = "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59"}, + {file = "Pillow-8.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c"}, + {file = "Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6"}, + {file = "Pillow-8.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378"}, + {file = "Pillow-8.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7"}, + {file = "Pillow-8.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0"}, + {file = "Pillow-8.1.0-cp39-cp39-win32.whl", hash = "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b"}, + {file = "Pillow-8.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865"}, + {file = "Pillow-8.1.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9"}, + {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_i686.whl", hash = "sha256:731ca5aabe9085160cf68b2dbef95fc1991015bc0a3a6ea46a371ab88f3d0913"}, + {file = "Pillow-8.1.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:bba80df38cfc17f490ec651c73bb37cd896bc2400cfba27d078c2135223c1206"}, + {file = "Pillow-8.1.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c3d911614b008e8a576b8e5303e3db29224b455d3d66d1b2848ba6ca83f9ece9"}, + {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_i686.whl", hash = "sha256:39725acf2d2e9c17356e6835dccebe7a697db55f25a09207e38b835d5e1bc032"}, + {file = "Pillow-8.1.0-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:81c3fa9a75d9f1afafdb916d5995633f319db09bd773cb56b8e39f1e98d90820"}, + {file = "Pillow-8.1.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:b6f00ad5ebe846cc91763b1d0c6d30a8042e02b2316e27b05de04fa6ec831ec5"}, + {file = "Pillow-8.1.0.tar.gz", hash = "sha256:887668e792b7edbfb1d3c9d8b5d8c859269a0f0eba4dda562adb95500f60dbba"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py4j" +version = "0.10.9.3" +description = "Enables Python programs to dynamically access arbitrary Java objects" +optional = false +python-versions = "*" +files = [ + {file = "py4j-0.10.9.3-py2.py3-none-any.whl", hash = "sha256:04f5b06917c0c8a81ab34121dda09a2ba1f74e96d59203c821d5cb7d28c35363"}, + {file = "py4j-0.10.9.3.tar.gz", hash = "sha256:0d92844da4cb747155b9563c44fc322c9a1562b3ef0979ae692dbde732d784dd"}, +] + +[[package]] +name = "pylint" +version = "2.17.7" +description = "python code static checker" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, + {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, +] + +[package.dependencies] +astroid = ">=2.15.8,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, +] +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyparsing" +version = "3.1.2" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, + {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyproj" +version = "3.5.0" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyproj-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6475ce653880938468a1a1b7321267243909e34b972ba9e53d5982c41d555918"}, + {file = "pyproj-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61e4ad57d89b03a7b173793b31bca8ee110112cde1937ef0f42a70b9120c827d"}, + {file = "pyproj-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdd2021bb6f7f346bfe1d2a358aa109da017d22c4704af2d994e7c7ee0a7a53"}, + {file = "pyproj-3.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5674923351e76222e2c10c58b5e1ac119d7a46b270d822c463035971b06f724b"}, + {file = "pyproj-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd5e2b6aa255023c4acd0b977590f1f7cc801ba21b4d806fcf6dfac3474ebb83"}, + {file = "pyproj-3.5.0-cp310-cp310-win32.whl", hash = "sha256:6f316a66031a14e9c5a88c91f8b77aa97f5454895674541ed6ab630b682be35d"}, + {file = "pyproj-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7c2f4d9681e810cf40239caaca00079930a6d9ee6591139b88d592d36051d82"}, + {file = "pyproj-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7572983134e310e0ca809c63f1722557a040fe9443df5f247bf11ba887eb1229"}, + {file = "pyproj-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eccb417b91d0be27805dfc97550bfb8b7db94e9fe1db5ebedb98f5b88d601323"}, + {file = "pyproj-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621d78a9d8bf4d06e08bef2471021fbcb1a65aa629ad4a20c22e521ce729cc20"}, + {file = "pyproj-3.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9a024370e917c899bff9171f03ea6079deecdc7482a146a2c565f3b9df134ea"}, + {file = "pyproj-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b7c2113c4d11184a238077ec85e31eda1dcc58ffeb9a4429830e0a7036e787d"}, + {file = "pyproj-3.5.0-cp311-cp311-win32.whl", hash = "sha256:a730f5b4c98c8a0f312437873e6e34dbd4cc6dc23d5afd91a6691c62724b1f68"}, + {file = "pyproj-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e97573de0ab3bbbcb4c7748bc41f4ceb6da10b45d35b1a294b5820701e7c25f0"}, + {file = "pyproj-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2b708fd43453b985642b737d4a6e7f1d6a0ab1677ffa4e14cc258537b49224b0"}, + {file = "pyproj-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b60d93a200639e8367c6542a964fd0aa2dbd152f256c1831dc18cd5aa470fb8a"}, + {file = "pyproj-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38862fe07316ae12b79d82d298e390973a4f00b684f3c2d037238e20e00610ba"}, + {file = "pyproj-3.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b65f2a38cd9e16883dbb0f8ae82bdf8f6b79b1b02975c78483ab8428dbbf2f"}, + {file = "pyproj-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b752b7d9c4b08181c7e8c0d9c7f277cbefff42227f34d3310696a87c863d9dd3"}, + {file = "pyproj-3.5.0-cp38-cp38-win32.whl", hash = "sha256:b937215bfbaf404ec8f03ca741fc3f9f2c4c2c5590a02ccddddd820ae3c71331"}, + {file = "pyproj-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:97ed199033c2c770e7eea2ef80ff5e6413426ec2d7ec985b869792f04ab95d05"}, + {file = "pyproj-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:052c49fce8b5d55943a35c36ccecb87350c68b48ba95bc02a789770c374ef819"}, + {file = "pyproj-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1507138ea28bf2134d31797675380791cc1a7156a3aeda484e65a78a4aba9b62"}, + {file = "pyproj-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02742ef3d846401861a878a61ef7ad911ea7539d6cc4619ddb52dbdf7b45aee"}, + {file = "pyproj-3.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:385b0341861d3ebc8cad98337a738821dcb548d465576527399f4955ca24b6ed"}, + {file = "pyproj-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fe6bb1b68a35d07378d38be77b5b2f8dd2bea5910c957bfcc7bee55988d3910"}, + {file = "pyproj-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5c4b85ac10d733c42d73a2e6261c8d6745bf52433a31848dd1b6561c9a382da3"}, + {file = "pyproj-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1798ff7d65d9057ebb2d017ffe8403268b8452f24d0428b2140018c25c7fa1bc"}, + {file = "pyproj-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d711517a8487ef3245b08dc82f781a906df9abb3b6cb0ce0486f0eeb823ca570"}, + {file = "pyproj-3.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:788a5dadb532644a64efe0f5f01bf508c821eb7e984f13a677d56002f1e8a67a"}, + {file = "pyproj-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73f7960a97225812f9b1d7aeda5fb83812f38de9441e3476fcc8abb3e2b2f4de"}, + {file = "pyproj-3.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fde5ece4d2436b5a57c8f5f97b49b5de06a856d03959f836c957d3e609f2de7e"}, + {file = "pyproj-3.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08db25b61cf024648d55973cc3d1c3f1d0818fabf594d5f5a8e2318103d2aa0"}, + {file = "pyproj-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a87b419a2a352413fbf759ecb66da9da50bd19861c8f26db6a25439125b27b9"}, + {file = "pyproj-3.5.0.tar.gz", hash = "sha256:9859d1591c1863414d875ae0759e72c2cffc01ab989dc64137fbac572cc81bf6"}, +] + +[package.dependencies] +certifi = "*" + +[[package]] +name = "pysolr" +version = "3.9.0" +description = "Lightweight Python client for Apache Solr" +optional = false +python-versions = "*" +files = [ + {file = "pysolr-3.9.0.tar.gz", hash = "sha256:6ef05feb87c614894243eddc62e9b0a6134a889c159ae868655cf6cd749545e6"}, +] + +[package.dependencies] +requests = ">=2.9.1" + +[package.extras] +solrcloud = ["kazoo (>=2.5.0)"] + +[[package]] +name = "pyspark" +version = "3.2.1" +description = "Apache Spark Python API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyspark-3.2.1.tar.gz", hash = "sha256:0b81359262ec6e9ac78c353344e7de026027d140c6def949ff0d80ab70f89a54"}, +] + +[package.dependencies] +py4j = "0.10.9.3" + +[package.extras] +ml = ["numpy (>=1.7)"] +mllib = ["numpy (>=1.7)"] +pandas-on-spark = ["numpy (>=1.14)", "pandas (>=0.23.2)", "pyarrow (>=1.0.0)"] +sql = ["pandas (>=0.23.2)", "pyarrow (>=1.0.0)"] + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-integration-mark" +version = "0.2.0" +description = "Automatic integration test marking and excluding plugin for pytest" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pytest_integration_mark-0.2.0-py3-none-any.whl", hash = "sha256:dfff273d47922c2d750923e06ac65bda20ff1c016adba187dee20840f0d5869b"}, + {file = "pytest_integration_mark-0.2.0.tar.gz", hash = "sha256:2f3580fba9aa7fecc9ede2385ae5c0c1414c688cc4e8511ccb61f65025508a14"}, +] + +[package.dependencies] +pytest = ">=5.2" + +[[package]] +name = "pytest-json-report" +version = "1.5.0" +description = "A pytest plugin to report test results as JSON files" +optional = false +python-versions = "*" +files = [ + {file = "pytest-json-report-1.5.0.tar.gz", hash = "sha256:2dde3c647851a19b5f3700729e8310a6e66efb2077d674f27ddea3d34dc615de"}, + {file = "pytest_json_report-1.5.0-py3-none-any.whl", hash = "sha256:9897b68c910b12a2e48dd849f9a284b2c79a732a8a9cb398452ddd23d3c8c325"}, +] + +[package.dependencies] +pytest = ">=3.8.0" +pytest-metadata = "*" + +[[package]] +name = "pytest-metadata" +version = "2.0.4" +description = "pytest plugin for test session metadata" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pytest_metadata-2.0.4-py3-none-any.whl", hash = "sha256:acb739f89fabb3d798c099e9e0c035003062367a441910aaaf2281bc1972ee14"}, + {file = "pytest_metadata-2.0.4.tar.gz", hash = "sha256:fcc653f65fe3035b478820b5284fbf0f52803622ee3f60a2faed7a7d3ba1f41e"}, +] + +[package.dependencies] +pytest = ">=3.0.0,<8.0.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "s3transfer" +version = "0.3.7" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = "*" +files = [ + {file = "s3transfer-0.3.7-py2.py3-none-any.whl", hash = "sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246"}, + {file = "s3transfer-0.3.7.tar.gz", hash = "sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994"}, +] + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[[package]] +name = "scipy" +version = "1.6.0" +description = "SciPy: Scientific Library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "scipy-1.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3d4303e3e21d07d9557b26a1707bb9fc065510ee8501c9bf22a0157249a82fd0"}, + {file = "scipy-1.6.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1bc5b446600c4ff7ab36bade47180673141322f0febaa555f1c433fe04f2a0e3"}, + {file = "scipy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8840a9adb4ede3751f49761653d3ebf664f25195fdd42ada394ffea8903dd51d"}, + {file = "scipy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:8629135ee00cc2182ac8be8e75643b9f02235942443732c2ed69ab48edcb6614"}, + {file = "scipy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:58731bbe0103e96b89b2f41516699db9b63066e4317e31b8402891571f6d358f"}, + {file = "scipy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:876badc33eec20709d4e042a09834f5953ebdac4088d45a4f3a1f18b56885718"}, + {file = "scipy-1.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c0911f3180de343643f369dc5cfedad6ba9f939c2d516bddea4a6871eb000722"}, + {file = "scipy-1.6.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b8af26839ae343655f3ca377a5d5e5466f1d3b3ac7432a43449154fe958ae0e0"}, + {file = "scipy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4f1d9cc977ac6a4a63c124045c1e8bf67ec37098f67c699887a93736961a00ae"}, + {file = "scipy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:eb7928275f3560d47e5538e15e9f32b3d64cd30ea8f85f3e82987425476f53f6"}, + {file = "scipy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:31ab217b5c27ab429d07428a76002b33662f98986095bbce5d55e0788f7e8b15"}, + {file = "scipy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:2f1c2ebca6fd867160e70102200b1bd07b3b2d31a3e6af3c58d688c15d0d07b7"}, + {file = "scipy-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:155225621df90fcd151e25d51c50217e412de717475999ebb76e17e310176981"}, + {file = "scipy-1.6.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f68d5761a2d2376e2b194c8e9192bbf7c51306ca176f1a0889990a52ef0d551f"}, + {file = "scipy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d902d3a5ad7f28874c0a82db95246d24ca07ad932741df668595fe00a4819870"}, + {file = "scipy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:aef3a2dbc436bbe8f6e0b635f0b5fe5ed024b522eee4637dbbe0b974129ca734"}, + {file = "scipy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:cdbc47628184a0ebeb5c08f1892614e1bd4a51f6e0d609c6eed253823a960f5b"}, + {file = "scipy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:313785c4dab65060f9648112d025f6d2fec69a8a889c714328882d678a95f053"}, + {file = "scipy-1.6.0.tar.gz", hash = "sha256:cb6dc9f82dfd95f6b9032a8d7ea70efeeb15d5b5fd6ed4e8537bb3c673580566"}, +] + +[package.dependencies] +numpy = ">=1.16.5" + +[[package]] +name = "shapely" +version = "1.7.1" +description = "Geometric objects, predicates, and operations" +optional = false +python-versions = "*" +files = [ + {file = "Shapely-1.7.1-1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:46da0ea527da9cf9503e66c18bab6981c5556859e518fe71578b47126e54ca93"}, + {file = "Shapely-1.7.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4c10f317e379cc404f8fc510cd9982d5d3e7ba13a9cfd39aa251d894c6366798"}, + {file = "Shapely-1.7.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:17df66e87d0fe0193910aeaa938c99f0b04f67b430edb8adae01e7be557b141b"}, + {file = "Shapely-1.7.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:da38ed3d65b8091447dc3717e5218cc336d20303b77b0634b261bc5c1aa2bae8"}, + {file = "Shapely-1.7.1-cp35-cp35m-win32.whl", hash = "sha256:8e7659dd994792a0aad8fb80439f59055a21163e236faf2f9823beb63a380e19"}, + {file = "Shapely-1.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:791477edb422692e7dc351c5ed6530eb0e949a31b45569946619a0d9cd5f53cb"}, + {file = "Shapely-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3afccf0437edc108eef1e2bb9cc4c7073e7705924eb4cd0bf7715cd1ef0ce1b"}, + {file = "Shapely-1.7.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8f15b6ce67dcc05b61f19c689b60f3fe58550ba994290ff8332f711f5aaa9840"}, + {file = "Shapely-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:60e5b2282619249dbe8dc5266d781cc7d7fb1b27fa49f8241f2167672ad26719"}, + {file = "Shapely-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:de618e67b64a51a0768d26a9963ecd7d338a2cf6e9e7582d2385f88ad005b3d1"}, + {file = "Shapely-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:182716ffb500d114b5d1b75d7fd9d14b7d3414cef3c38c0490534cc9ce20981a"}, + {file = "Shapely-1.7.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4f3c59f6dbf86a9fc293546de492f5e07344e045f9333f3a753f2dda903c45d1"}, + {file = "Shapely-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:6871acba8fbe744efa4f9f34e726d070bfbf9bffb356a8f6d64557846324232b"}, + {file = "Shapely-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:35be1c5d869966569d3dfd4ec31832d7c780e9df760e1fe52131105685941891"}, + {file = "Shapely-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:052eb5b9ba756808a7825e8a8020fb146ec489dd5c919e7d139014775411e688"}, + {file = "Shapely-1.7.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:90a3e2ae0d6d7d50ff2370ba168fbd416a53e7d8448410758c5d6a5920646c1d"}, + {file = "Shapely-1.7.1-cp38-cp38-win32.whl", hash = "sha256:a3774516c8a83abfd1ddffb8b6ec1b0935d7fe6ea0ff5c31a18bfdae567b4eba"}, + {file = "Shapely-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:6593026cd3f5daaea12bcc51ae5c979318070fefee210e7990cb8ac2364e79a1"}, + {file = "Shapely-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:617bf046a6861d7c6b44d2d9cb9e2311548638e684c2cd071d8945f24a926263"}, + {file = "Shapely-1.7.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b40cc7bb089ae4aa9ddba1db900b4cd1bce3925d2a4b5837b639e49de054784f"}, + {file = "Shapely-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2df5260d0f2983309776cb41bfa85c464ec07018d88c0ecfca23d40bfadae2f1"}, + {file = "Shapely-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a5c3a50d823c192f32615a2a6920e8c046b09e07a58eba220407335a9cd2e8ea"}, + {file = "Shapely-1.7.1.tar.gz", hash = "sha256:1641724c1055459a7e2b8bbe47ba25bdc89554582e62aec23cb3f3ca25f9b129"}, +] + +[package.extras] +all = ["numpy", "pytest", "pytest-cov"] +test = ["pytest", "pytest-cov"] +vectorized = ["numpy"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.4" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, + {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, +] + +[[package]] +name = "tornado" +version = "6.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.5" +files = [ + {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, + {file = "tornado-6.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9de9e5188a782be6b1ce866e8a51bc76a0fbaa0e16613823fc38e4fc2556ad05"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:61b32d06ae8a036a6607805e6720ef00a3c98207038444ba7fd3d169cd998910"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:3e63498f680547ed24d2c71e6497f24bca791aca2fe116dbc2bd0ac7f191691b"}, + {file = "tornado-6.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:6c77c9937962577a6a76917845d06af6ab9197702a42e1346d8ae2e76b5e3675"}, + {file = "tornado-6.1-cp35-cp35m-win32.whl", hash = "sha256:6286efab1ed6e74b7028327365cf7346b1d777d63ab30e21a0f4d5b275fc17d5"}, + {file = "tornado-6.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fa2ba70284fa42c2a5ecb35e322e68823288a4251f9ba9cc77be04ae15eada68"}, + {file = "tornado-6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a00ff4561e2929a2c37ce706cb8233b7907e0cdc22eab98888aca5dd3775feb"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:748290bf9112b581c525e6e6d3820621ff020ed95af6f17fedef416b27ed564c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e385b637ac3acaae8022e7e47dfa7b83d3620e432e3ecb9a3f7f58f150e50921"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:25ad220258349a12ae87ede08a7b04aca51237721f63b1808d39bdb4b2164558"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:65d98939f1a2e74b58839f8c4dab3b6b3c1ce84972ae712be02845e65391ac7c"}, + {file = "tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e519d64089b0876c7b467274468709dadf11e41d65f63bba207e04217f47c085"}, + {file = "tornado-6.1-cp36-cp36m-win32.whl", hash = "sha256:b87936fd2c317b6ee08a5741ea06b9d11a6074ef4cc42e031bc6403f82a32575"}, + {file = "tornado-6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:cc0ee35043162abbf717b7df924597ade8e5395e7b66d18270116f8745ceb795"}, + {file = "tornado-6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7250a3fa399f08ec9cb3f7b1b987955d17e044f1ade821b32e5f435130250d7f"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ed3ad863b1b40cd1d4bd21e7498329ccaece75db5a5bf58cd3c9f130843e7102"}, + {file = "tornado-6.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dcef026f608f678c118779cd6591c8af6e9b4155c44e0d1bc0c87c036fb8c8c4"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:70dec29e8ac485dbf57481baee40781c63e381bebea080991893cd297742b8fd"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d3f7594930c423fd9f5d1a76bee85a2c36fd8b4b16921cae7e965f22575e9c01"}, + {file = "tornado-6.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3447475585bae2e77ecb832fc0300c3695516a47d46cefa0528181a34c5b9d3d"}, + {file = "tornado-6.1-cp37-cp37m-win32.whl", hash = "sha256:e7229e60ac41a1202444497ddde70a48d33909e484f96eb0da9baf8dc68541df"}, + {file = "tornado-6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:cb5ec8eead331e3bb4ce8066cf06d2dfef1bfb1b2a73082dfe8a161301b76e37"}, + {file = "tornado-6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:20241b3cb4f425e971cb0a8e4ffc9b0a861530ae3c52f2b0434e6c1b57e9fd95"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c77da1263aa361938476f04c4b6c8916001b90b2c2fdd92d8d535e1af48fba5a"}, + {file = "tornado-6.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:1e8225a1070cd8eec59a996c43229fe8f95689cb16e552d130b9793cb570a288"}, + {file = "tornado-6.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d14d30e7f46a0476efb0deb5b61343b1526f73ebb5ed84f23dc794bdb88f9d9f"}, + {file = "tornado-6.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8f959b26f2634a091bb42241c3ed8d3cedb506e7c27b8dd5c7b9f745318ddbb6"}, + {file = "tornado-6.1-cp38-cp38-win32.whl", hash = "sha256:34ca2dac9e4d7afb0bed4677512e36a52f09caa6fded70b4e3e1c89dbd92c326"}, + {file = "tornado-6.1-cp38-cp38-win_amd64.whl", hash = "sha256:6196a5c39286cc37c024cd78834fb9345e464525d8991c21e908cc046d1cc02c"}, + {file = "tornado-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ba29bafd8e7e22920567ce0d232c26d4d47c8b5cf4ed7b562b5db39fa199c5"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:33892118b165401f291070100d6d09359ca74addda679b60390b09f8ef325ffe"}, + {file = "tornado-6.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7da13da6f985aab7f6f28debab00c67ff9cbacd588e8477034c0652ac141feea"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:e0791ac58d91ac58f694d8d2957884df8e4e2f6687cdf367ef7eb7497f79eaa2"}, + {file = "tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:66324e4e1beede9ac79e60f88de548da58b1f8ab4b2f1354d8375774f997e6c0"}, + {file = "tornado-6.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a48900ecea1cbb71b8c71c620dee15b62f85f7c14189bdeee54966fbd9a0c5bd"}, + {file = "tornado-6.1-cp39-cp39-win32.whl", hash = "sha256:d3d20ea5782ba63ed13bc2b8c291a053c8d807a8fa927d941bd718468f7b950c"}, + {file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"}, + {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, +] + +[[package]] +name = "typing-extensions" +version = "4.10.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "urllib3" +version = "1.26.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ + {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, + {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, +] + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "utm" +version = "0.6.0" +description = "Bidirectional UTM-WGS84 converter for python" +optional = false +python-versions = "*" +files = [ + {file = "utm-0.6.0.tar.gz", hash = "sha256:a3c169ceabec910b4eb98a544b686cc4a25eb555407dfb9cb5f685123d5fa07f"}, +] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "xarray" +version = "2023.1.0" +description = "N-D labeled arrays and datasets in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "xarray-2023.1.0-py3-none-any.whl", hash = "sha256:7e530b1deafdd43e5c2b577d0944e6b528fbe88045fd849e49a8d11871ecd522"}, + {file = "xarray-2023.1.0.tar.gz", hash = "sha256:7bee552751ff1b29dab8b7715726e5ecb56691ac54593cf4881dff41978ce0cd"}, +] + +[package.dependencies] +numpy = ">=1.20" +packaging = ">=21.3" +pandas = ">=1.3" + +[package.extras] +accel = ["bottleneck", "flox", "numbagg", "scipy"] +complete = ["bottleneck", "cfgrib", "cftime", "dask[complete]", "flox", "fsspec", "h5netcdf", "matplotlib", "nc-time-axis", "netCDF4", "numbagg", "pooch", "pydap", "rasterio", "scipy", "seaborn", "zarr"] +docs = ["bottleneck", "cfgrib", "cftime", "dask[complete]", "flox", "fsspec", "h5netcdf", "ipykernel", "ipython", "jupyter-client", "matplotlib", "nbsphinx", "nc-time-axis", "netCDF4", "numbagg", "pooch", "pydap", "rasterio", "scanpydoc", "scipy", "seaborn", "sphinx-autosummary-accessors", "sphinx-rtd-theme", "zarr"] +io = ["cfgrib", "cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap", "rasterio", "scipy", "zarr"] +parallel = ["dask[complete]"] +viz = ["matplotlib", "nc-time-axis", "seaborn"] + +[[package]] +name = "zipp" +version = "3.18.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.0-py3-none-any.whl", hash = "sha256:c1bb803ed69d2cce2373152797064f7e79bc43f0a3748eb494096a867e0ebf79"}, + {file = "zipp-3.18.0.tar.gz", hash = "sha256:df8d042b02765029a09b157efd8e820451045890acc30f8e37dd2f94a060221f"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "~=3.8" +content-hash = "ea9774be0da4f52f4b83283d29b6e46fef30c3e477426d6f05f71813486421fc" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..ac76d320 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,78 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[tool.poetry] +name = "sdap-nexus" +version = "1.2.0" +description = "Nexus component for Apache SDAP" +authors = ["SDAP PPMC "] +license = "Apache-2.0" +readme = "README.md" +packages = [ + { include = "webservice", from = "analysis" }, + { include = "nexustiles", from = "data-access" } +] +repository = "https://github.com/apache/incubator-sdap-nexus" +exclude = ["docs", "tests"] +classifiers=[ + 'Development Status :: 6 - Mature', + 'Intended Audience :: Science/Research', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3.8', + "License :: OSI Approved :: Apache Software License" +] + + +[tool.poetry.dependencies] +python = "~=3.8" +netcdf4 = "1.5.5.1" +scipy = "1.6.0" +pyspark = "3.2.1" +py4j = "*" +pytz = "2021.1" +utm = "0.6.0" +shapely = "1.7.1" +backports-functools-lru-cache = "1.6.1" +boto3 = "1.16.63" +pillow = "8.1.0" +mpld3 = "0.5.1" +tornado = "6.1" +pyproj = "^3" +pyyaml = "6.0" +importlib_metadata = "4.11.4" +numpy = "1.24.3" +cassandra-driver = "3.24.0" +pysolr = "3.9.0" +elasticsearch = "8.3.1" +urllib3 = "1.26.2" +requests = "*" +xarray = "*" +geopy = "*" +pandas = "*" + + +[tool.poetry.dev-dependencies] +mock = "^4.0.3" +pytest = "^7.1.2" +pytest-cov = "^3.0.0" +pytest-integration-mark = "*" +pytest-json-report = "^1.5.0" +pytest-metadata = "^2.0.2" +pylint = "^2.14.5" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" From dec1e8176251c8e2aced12b3c10c28baed02e807 Mon Sep 17 00:00:00 2001 From: rileykk Date: Wed, 20 Mar 2024 13:57:56 -0700 Subject: [PATCH 20/27] poetry re-lock after deps update from merge --- poetry.lock | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 82d44a32..8a3b627f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -911,6 +911,17 @@ files = [ {file = "futures-3.0.5.tar.gz", hash = "sha256:0542525145d5afc984c88f914a0c85c77527f65946617edb5274f72406f981df"}, ] +[[package]] +name = "geographiclib" +version = "2.0" +description = "The geodesic routines from GeographicLib" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"}, + {file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"}, +] + [[package]] name = "geomet" version = "0.2.1.post1" @@ -926,6 +937,29 @@ files = [ click = "*" six = "*" +[[package]] +name = "geopy" +version = "2.4.1" +description = "Python Geocoding Toolbox" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7"}, + {file = "geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1"}, +] + +[package.dependencies] +geographiclib = ">=1.52,<3" + +[package.extras] +aiohttp = ["aiohttp"] +dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"] +dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"] +requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"] +timezone = ["pytz"] + [[package]] name = "idna" version = "3.6" @@ -2542,4 +2576,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~=3.8" -content-hash = "d19daaa356c410276a8cb00f1ab395b777986d146f61abd345fc1e62a0b8747b" +content-hash = "4a270525bf9c9de2049a8311a88e036f322c563b1fb3cd3cf0423229ed12ec91" From 15a80be9d33de976d5170450c7226a6c8ca62638 Mon Sep 17 00:00:00 2001 From: rileykk Date: Thu, 4 Apr 2024 08:33:48 -0700 Subject: [PATCH 21/27] Properly scale CC before plotting --- analysis/webservice/algorithms/Lidar.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 11445e4f..79050928 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -1253,7 +1253,7 @@ def toImage(self): s1 = ax.scatter( xy[:, 0], xy[:, 1], results['mean_veg_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values, + alpha=results['canopy_coverage'].values / 100, facecolors='brown', zdir='z', depthshade=True, @@ -1264,7 +1264,7 @@ def toImage(self): s2 = ax.scatter( xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values, + alpha=results['canopy_coverage'].values / 100, facecolors='xkcd:leaf', zdir='z', depthshade=True, @@ -1324,7 +1324,7 @@ def toGif(self): s1 = ax.scatter( xy[:, 0], xy[:, 1], results['mean_veg_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values * ALPHA_SCALING, + alpha=(results['canopy_coverage'].values / 100) * ALPHA_SCALING, facecolors='brown', zdir='z', depthshade=True, @@ -1335,7 +1335,7 @@ def toGif(self): s2 = ax.scatter( xy[:, 0], xy[:, 1], results['canopy_height'].values + results['ground_height'].values, marker=',', - alpha=results['canopy_coverage'].values * ALPHA_SCALING, + alpha=(results['canopy_coverage'].values / 100) * ALPHA_SCALING, facecolors='xkcd:leaf', zdir='z', depthshade=True, From 11e6382a2716470778c4010e5eaf717461ce8172 Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 23 Apr 2024 13:00:02 -0700 Subject: [PATCH 22/27] Major Lidar update - Handle multivar collection - Validations - Plotting tweaks --- analysis/webservice/algorithms/Lidar.py | 334 ++++++++++++++------- data-access/nexustiles/model/nexusmodel.py | 3 +- data-access/nexustiles/nexustiles.py | 5 +- 3 files changed, 232 insertions(+), 110 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 79050928..883a730a 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -19,6 +19,7 @@ import os.path import warnings import zipfile +from copy import copy from datetime import datetime from functools import partial from io import BytesIO @@ -357,11 +358,13 @@ def calc(self, computeOptions, **args): render_type, params_3d) = self.parse_arguments(computeOptions) tile_service = self._get_tile_service() + points_zg, points_50, points_98, points_cc = [], [], [], [] + sources = set() + + def source_name_from_granule(granule: str, end=-3) -> str: + return '_'.join(granule.split('_')[:end]) - ds_zg = f'{dataset}_ZG' - ds_rh50 = f'{dataset}_RH050' - ds_rh98 = f'{dataset}_RH098' - ds_cc = f'{dataset}_CC' + include_nan = map_to_grid # If we may need to map to grid, get nans from source tiles so we can best estimate get_tiles = partial( tile_service.find_tiles_in_polygon, @@ -370,119 +373,220 @@ def calc(self, computeOptions, **args): end_time=end_time ) - tiles_zg = get_tiles(ds=ds_zg) - tiles_rh50 = get_tiles(ds=ds_rh50) - tiles_rh98 = get_tiles(ds=ds_rh98) - tiles_cc = get_tiles(ds=ds_cc) + single_collection = tile_service.dataset_exists(dataset) + + if single_collection: + tiles = get_tiles(ds=dataset) + (ds_zg, ds_rh50, ds_rh98, ds_cc) = [dataset] * 4 + variable_indices = None + logger.info(f'Matched {len(tiles):,} tiles') + + for tile in tiles: + logger.info(f'Processing tile {tile.tile_id}') + + if variable_indices is None: + variable_indices = {} + + for i, var in enumerate(tile.variables): + if var.variable_name.lower() in ['zg', 'ground_height']: + if 'zg' in variable_indices: + logger.warning('Ground height variable found more than once in the collection') + variable_indices['zg'] = i + continue + + if var.variable_name.lower() in ['rh50', 'rh050', 'mean_veg_height', 'mean_vegetation_height']: + if 'rh50' in variable_indices: + logger.warning('Mean vegetation height variable found more than once in the collection') + variable_indices['rh50'] = i + continue + + if var.variable_name.lower() in ['rh98', 'rh098', 'canopy_height']: + if 'rh98' in variable_indices: + logger.warning('Canopy height variable found more than once in the collection') + variable_indices['rh98'] = i + continue + + if var.variable_name.lower() in ['cc', 'canopy_coverage', 'canopy_cover']: + if 'zg' in variable_indices: + logger.warning('Canopy cover variable found more than once in the collection') + variable_indices['cc'] = i + continue + + if len(variable_indices) != 4: + required = {'zg', 'rh50', 'rh98', 'cc'} + raise NexusProcessingException( + code=400, + reason=f'Could find some needed variables in the selected collection:' + f' {list(required - set(variable_indices.keys()))}' + ) - logger.info(f'Matched tile counts by variable: ZG={len(tiles_zg):,}, RH050={len(tiles_rh50):,}, ' - f'RH098={len(tiles_rh98):,}, CC={len(tiles_cc):,}') + npg = tile.nexus_point_generator(include_nan=include_nan) + source = tile.granule + sources.add(source) - if all([len(t) == 0 for t in [tiles_zg, tiles_rh50, tiles_rh98, tiles_cc]]): - raise NoDataException(reason='No data was found within the selected parameters') + for nexus_point in npg: + point = dict( + latitude=nexus_point.latitude, + longitude=nexus_point.longitude, + time=nexus_point.time, + source=source + ) - points_zg, points_50, points_98, points_cc = [], [], [], [] + p_zg = copy(point) + p_50 = copy(point) + p_98 = copy(point) + p_cc = copy(point) - sources = set() + p_zg['data'] = nexus_point.data_vals[variable_indices['zg']] + if isinstance(p_zg['data'], list): + p_zg['data'] = p_zg['data'][0] - def source_name_from_granule(granule: str, end=-3) -> str: - return '_'.join(granule.split('_')[:end]) + points_zg.append(p_zg) - include_nan = map_to_grid # If we may need to map to grid, get nans from source tiles so we can best estimate - # grid resolution + p_50['data'] = nexus_point.data_vals[variable_indices['rh50']] + if isinstance(p_50['data'], list): + p_50['data'] = p_50['data'][0] + points_50.append(p_50) - for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): - if tile_zg: - logger.info(f'Processing ground height tile {tile_zg.tile_id}') - npg_zg = tile_zg.nexus_point_generator(include_nan=include_nan) - sources.add(source_name_from_granule(tile_zg.granule)) - else: - npg_zg = [] + p_98['data'] = nexus_point.data_vals[variable_indices['rh98']] + if isinstance(p_98['data'], list): + p_98['data'] = p_98['data'][0] + points_98.append(p_98) - if tile_50: - logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') - npg_50 = tile_50.nexus_point_generator(include_nan=include_nan) - sources.add(source_name_from_granule(tile_50.granule)) - else: - npg_50 = [] + p_cc['data'] = nexus_point.data_vals[variable_indices['cc']] + if isinstance(p_cc['data'], list): + p_cc['data'] = p_cc['data'][0] + p_cc['data'] = p_cc['data'] * 100 + points_cc.append(p_cc) + else: + ds_zg = f'{dataset}_ZG' + ds_rh50 = f'{dataset}_RH050' + ds_rh98 = f'{dataset}_RH098' + ds_cc = f'{dataset}_CC' + + if len([d for d in [ds_zg, ds_rh50, ds_rh98, ds_cc] if not tile_service.dataset_exists(d)]) > 0: + raise NexusProcessingException( + code=404, + reason=f'Some datasets are missing: ' + f'{[d for d in [ds_zg, ds_rh50, ds_rh98, ds_cc] if not tile_service.dataset_exists(d)]}' + ) - if tile_98: - logger.info(f'Processing canopy height tile {tile_98.tile_id}') - npg_98 = tile_98.nexus_point_generator(include_nan=include_nan) - sources.add(source_name_from_granule(tile_98.granule)) - else: - npg_98 = [] + tiles_zg = get_tiles(ds=ds_zg) + tiles_rh50 = get_tiles(ds=ds_rh50) + tiles_rh98 = get_tiles(ds=ds_rh98) + tiles_cc = get_tiles(ds=ds_cc) + + logger.info(f'Matched tile counts by variable: ZG={len(tiles_zg):,}, RH050={len(tiles_rh50):,}, ' + f'RH098={len(tiles_rh98):,}, CC={len(tiles_cc):,}') + + if all([len(t) == 0 for t in [tiles_zg, tiles_rh50, tiles_rh98, tiles_cc]]): + raise NoDataException(reason='No data was found within the selected parameters') + + for tile_zg, tile_50, tile_98, tile_cc in zip_longest(tiles_zg, tiles_rh50, tiles_rh98, tiles_cc): + if tile_zg: + logger.info(f'Processing ground height tile {tile_zg.tile_id}') + npg_zg = tile_zg.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_zg.granule)) + else: + npg_zg = [] + + if tile_50: + logger.info(f'Processing mean vegetation height tile {tile_50.tile_id}') + npg_50 = tile_50.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_50.granule)) + else: + npg_50 = [] + + if tile_98: + logger.info(f'Processing canopy height tile {tile_98.tile_id}') + npg_98 = tile_98.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_98.granule)) + else: + npg_98 = [] + + if tile_cc: + logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') + npg_cc = tile_cc.nexus_point_generator(include_nan=include_nan) + sources.add(source_name_from_granule(tile_cc.granule, -4)) + else: + npg_cc = [] + + for np_zg, np_50, np_98, np_cc in zip_longest( + npg_zg, + npg_50, + npg_98, + npg_cc + ): + if np_zg: + p_zg = dict( + latitude=np_zg.latitude, + longitude=np_zg.longitude, + time=np_zg.time, + data=np_zg.data_vals, + source=source_name_from_granule(tile_zg.granule) + ) - if tile_cc: - logger.info(f'Processing canopy coverage tile {tile_cc.tile_id}') - npg_cc = tile_cc.nexus_point_generator(include_nan=include_nan) - sources.add(source_name_from_granule(tile_cc.granule, -4)) - else: - npg_cc = [] - - for np_zg, np_50, np_98, np_cc in zip_longest( - npg_zg, - npg_50, - npg_98, - npg_cc - ): - if np_zg: - p_zg = dict( - latitude=np_zg.latitude, - longitude=np_zg.longitude, - time=np_zg.time, - data=np_zg.data_vals, - source=source_name_from_granule(tile_zg.granule) - ) + if isinstance(p_zg['data'], list): + p_zg['data'] = p_zg['data'][0] - if isinstance(p_zg['data'], list): - p_zg['data'] = p_zg['data'][0] + points_zg.append(p_zg) - points_zg.append(p_zg) + if np_50: + p_50 = dict( + latitude=np_50.latitude, + longitude=np_50.longitude, + time=np_50.time, + data=np_50.data_vals, + source=source_name_from_granule(tile_50.granule) + ) - if np_50: - p_50 = dict( - latitude=np_50.latitude, - longitude=np_50.longitude, - time=np_50.time, - data=np_50.data_vals, - source=source_name_from_granule(tile_50.granule) - ) + if isinstance(p_50['data'], list): + p_50['data'] = p_50['data'][0] - if isinstance(p_50['data'], list): - p_50['data'] = p_50['data'][0] + points_50.append(p_50) - points_50.append(p_50) + if np_98: + p_98 = dict( + latitude=np_98.latitude, + longitude=np_98.longitude, + time=np_98.time, + data=np_98.data_vals, + source=source_name_from_granule(tile_98.granule) + ) - if np_98: - p_98 = dict( - latitude=np_98.latitude, - longitude=np_98.longitude, - time=np_98.time, - data=np_98.data_vals, - source=source_name_from_granule(tile_98.granule) - ) + if isinstance(p_98['data'], list): + p_98['data'] = p_98['data'][0] - if isinstance(p_98['data'], list): - p_98['data'] = p_98['data'][0] + points_98.append(p_98) - points_98.append(p_98) + if np_cc: + p_cc = dict( + latitude=np_cc.latitude, + longitude=np_cc.longitude, + time=np_cc.time, + data=np_cc.data_vals, + source=source_name_from_granule(tile_cc.granule, -4) + ) - if np_cc: - p_cc = dict( - latitude=np_cc.latitude, - longitude=np_cc.longitude, - time=np_cc.time, - data=np_cc.data_vals, - source=source_name_from_granule(tile_cc.granule, -4) - ) + if isinstance(p_cc['data'], list): + p_cc['data'] = p_cc['data'][0] - if isinstance(p_cc['data'], list): - p_cc['data'] = p_cc['data'][0] + p_cc['data'] = p_cc['data'] * 100 - p_cc['data'] = p_cc['data'] * 100 + points_cc.append(p_cc) - points_cc.append(p_cc) + # include_nan messes up masking to bbox, so filter out manually here + if include_nan: + min_lon, min_lat, max_lon, max_lat = bounding_polygon.bounds + + points_zg = [p for p in points_zg if min_lon <= p['longitude'] <= max_lon and + min_lat <= p['latitude'] <= max_lat] + points_50 = [p for p in points_50 if min_lon <= p['longitude'] <= max_lon and + min_lat <= p['latitude'] <= max_lat] + points_98 = [p for p in points_98 if min_lon <= p['longitude'] <= max_lon and + min_lat <= p['latitude'] <= max_lat] + points_cc = [p for p in points_cc if min_lon <= p['longitude'] <= max_lon and + min_lat <= p['latitude'] <= max_lat] if len(sources) == 1: logger.info('Only one source LIDAR scene, using simple mapping to grid') @@ -490,7 +594,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: lats = np.unique([p['latitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) lons = np.unique([p['longitude'] for p in chain(points_zg, points_50, points_98, points_cc)]) times = np.unique( - [datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + [datetime.utcfromtimestamp(p['time']) + for p in chain(points_zg, points_50, points_98, points_cc)] + ) vals_4d = np.full((len(times), len(lats), len(lons), 4), np.nan) @@ -556,7 +662,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: ) times = np.unique( - [datetime.utcfromtimestamp(p['time']) for p in chain(points_zg, points_50, points_98, points_cc)]) + [datetime.utcfromtimestamp(p['time']) + for p in chain(points_zg, points_50, points_98, points_cc)] + ) logger.debug('Building gridding coordinate meshes') @@ -592,7 +700,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info(f'Gridding ground heights for {src}') source_points = source_point_map[src] gridded_zg.append(griddata( - list(zip([p['longitude'] for p in source_points['zg']], [p['latitude'] for p in source_points['zg']])), + list(zip( + [p['longitude'] for p in source_points['zg']], [p['latitude'] for p in source_points['zg']] + )), np.array([p['data'] for p in source_points['zg']]), (X, Y), method='nearest', @@ -601,7 +711,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info(f'Gridding mean vegetation heights for {src}') gridded_50.append(griddata( - list(zip([p['longitude'] for p in source_points['50']], [p['latitude'] for p in source_points['50']])), + list(zip( + [p['longitude'] for p in source_points['50']], [p['latitude'] for p in source_points['50']] + )), np.array([p['data'] for p in source_points['50']]), (X, Y), method='nearest', @@ -610,7 +722,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info(f'Gridding canopy heights for {src}') gridded_98.append(griddata( - list(zip([p['longitude'] for p in source_points['98']], [p['latitude'] for p in source_points['98']])), + list(zip( + [p['longitude'] for p in source_points['98']], [p['latitude'] for p in source_points['98']] + )), np.array([p['data'] for p in source_points['98']]), (X, Y), method='nearest', @@ -619,7 +733,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info(f'Gridding canopy coverage for {src}') gridded_cc.append(griddata( - list(zip([p['longitude'] for p in source_points['cc']], [p['latitude'] for p in source_points['cc']])), + list(zip( + [p['longitude'] for p in source_points['cc']], [p['latitude'] for p in source_points['cc']] + )), np.array([p['data'] / 10000 for p in source_points['cc']]), (X, Y), method='nearest', @@ -633,9 +749,9 @@ def source_name_from_granule(granule: str, end=-3) -> str: logger.info('Flattening gridded flightlines to single 2D view') - # Reduce 3D arrays of gridded flight lines to single 2D array, with each value being the most recent non-nan - # value along axis 0. In other words, stack the flight lines on top of each other, with more recent flight - # lines covering over previous ones + # Reduce 3D arrays of gridded flight lines to single 2D array, with each value being the most recent + # non-nan value along axis 0. In other words, stack the flight lines on top of each other, with more + # recent flight lines covering over previous ones gridded_zg = np.choose((~np.isnan(gridded_zg)).cumsum(0).argmax(0), gridded_zg) gridded_50 = np.choose((~np.isnan(gridded_50)).cumsum(0).argmax(0), gridded_50) @@ -1043,7 +1159,8 @@ def toImage(self): slice_ax.set_title(f'Slice at {coord}={slice_point}\nHeights w.r.t. to reference ellipsoid (m)') slice_ax.ticklabel_format(useOffset=False) - slice_ax.set_xlim(x_lim) + # Commented out = plot is focused on the actual slice rather than the full subset bounds + # slice_ax.set_xlim(x_lim) slice_ax.legend([ 'Canopy Height', @@ -1056,7 +1173,8 @@ def toImage(self): cc_slice_ax.plot(x_pts, cc_pts) cc_slice_ax.ticklabel_format(useOffset=False) cc_slice_ax.set_ylim([0, 100]) - cc_slice_ax.set_xlim(x_lim) + # Commented out = plot is focused on the actual slice rather than the full subset bounds + # cc_slice_ax.set_xlim(x_lim) cc_slice_ax.set_title(f'Slice at {coord}={slice_point}\nCanopy coverage (%)') @@ -1362,7 +1480,9 @@ def toGif(self): with contextlib.ExitStack() as stack: logger.info('Combining frames into final GIF') - imgs = (stack.enter_context(Image.open(os.path.join(td, f'fr_{a}.png'))) for a in range(0, 360, orbit_step)) + imgs = ( + stack.enter_context(Image.open(os.path.join(td, f'fr_{a}.png'))) for a in range(0, 360, orbit_step) + ) img = next(imgs) img.save(buffer, format='GIF', append_images=imgs, save_all=True, duration=frame_duration, loop=0) diff --git a/data-access/nexustiles/model/nexusmodel.py b/data-access/nexustiles/model/nexusmodel.py index 7db4d614..f7415738 100644 --- a/data-access/nexustiles/model/nexusmodel.py +++ b/data-access/nexustiles/model/nexusmodel.py @@ -154,7 +154,8 @@ def nexus_point_generator(self, include_nan=False): def get_indices(self, include_nan=False): if include_nan: - return list(np.ndindex(self.data.shape)) + shape = self.data.shape if not self.is_multi else self.data.shape[1:] + return list(np.ndindex(shape)) if self.is_multi: combined_data_inv_mask = reduce(np.logical_and, [data.mask for data in self.data]) return np.argwhere(np.logical_not(combined_data_inv_mask)) diff --git a/data-access/nexustiles/nexustiles.py b/data-access/nexustiles/nexustiles.py index b4fd6bba..0e509924 100644 --- a/data-access/nexustiles/nexustiles.py +++ b/data-access/nexustiles/nexustiles.py @@ -40,8 +40,6 @@ from .exception import NexusTileServiceException -from requests.structures import CaseInsensitiveDict - EPOCH = timezone('UTC').localize(datetime(1970, 1, 1)) logging.basicConfig( @@ -189,6 +187,9 @@ def _get_backend(dataset_s) -> AbstractTileService: return b['backend'] + @staticmethod + def dataset_exists(ds): + return ds in NexusTileService.backends @staticmethod def _get_datasets_store(): From f356b42b4134a6cd70cb9164eadff8c97db2dd19 Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 6 May 2024 15:18:43 -0700 Subject: [PATCH 23/27] Disable Lidar 3d & minor updates --- analysis/webservice/algorithms/Lidar.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index 883a730a..e296b603 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -92,7 +92,8 @@ class LidarVegetation(NexusCalcHandler): "renderType": { "name": "Rendering type", "type": "string", - "description": "Type of rendering to perform. Must be either 2D or 3D. Default: 2D" + "description": "NOTE: 3D rendering has been disabled for now due to poor ablility of rendering library to " + "handle the data. Type of rendering to perform. Must be either 2D or 3D. Default: 2D" }, "output": { "name": "Output format", @@ -167,14 +168,16 @@ def parse_arguments(self, request): start_time = int((start_time - EPOCH).total_seconds()) except: raise NexusProcessingException( - reason="'startTime' argument is required. Can be int value seconds from epoch or string format YYYY-MM-DDTHH:mm:ssZ", + reason="'startTime' argument is required. Can be int value seconds from epoch or string format " + "YYYY-MM-DDTHH:mm:ssZ", code=400) try: end_time = request.get_end_datetime() end_time = int((end_time - EPOCH).total_seconds()) except: raise NexusProcessingException( - reason="'endTime' argument is required. Can be int value seconds from epoch or string format YYYY-MM-DDTHH:mm:ssZ", + reason="'endTime' argument is required. Can be int value seconds from epoch or string format " + "YYYY-MM-DDTHH:mm:ssZ", code=400) if start_time > end_time: @@ -203,6 +206,13 @@ def parse_arguments(self, request): code=400 ) + # Disable 3D support until we can find a better way to render the data in 3d than by using MPL + if render_type == '3D': + raise NexusProcessingException( + reason='3D rendering of LIDAR data is temporarily disabled due to poor backend support', + code=400 + ) + output = request.get_argument('output', '').upper() if (render_type == '2D' and output not in ['PNG', 'JSON', 'NETCDF', 'CSV', 'ZIP']) or \ @@ -407,7 +417,7 @@ def source_name_from_granule(granule: str, end=-3) -> str: continue if var.variable_name.lower() in ['cc', 'canopy_coverage', 'canopy_cover']: - if 'zg' in variable_indices: + if 'cc' in variable_indices: logger.warning('Canopy cover variable found more than once in the collection') variable_indices['cc'] = i continue @@ -1402,6 +1412,7 @@ def toImage(self): plt.savefig(buffer, format='png', facecolor='white') buffer.seek(0) + plt.close(fig) return buffer.read() def toGif(self): @@ -1487,4 +1498,5 @@ def toGif(self): img.save(buffer, format='GIF', append_images=imgs, save_all=True, duration=frame_duration, loop=0) buffer.seek(0) + plt.close(fig) return buffer.read() From 10750639d9a91cf1cf75846886ff17c4bdffb840 Mon Sep 17 00:00:00 2001 From: rileykk Date: Wed, 15 May 2024 15:38:39 -0700 Subject: [PATCH 24/27] Bugfix for lat/lon slices of custom size --- analysis/webservice/algorithms/Lidar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysis/webservice/algorithms/Lidar.py b/analysis/webservice/algorithms/Lidar.py index e296b603..c31ced65 100644 --- a/analysis/webservice/algorithms/Lidar.py +++ b/analysis/webservice/algorithms/Lidar.py @@ -994,7 +994,7 @@ def points_list(self): mean_vegetation_height=lat_slice.mean_veg_height.isel(lon=l).item(), canopy_height=lat_slice.canopy_height.isel(lon=l).item(), canopy_coverage=lat_slice.canopy_coverage.isel(lon=l).item() - ) for l in range(len(ds['lon']))] + ) for l in range(len(lat_slice['lon']))] slice_points['latitude'] = dict(latitude=lat_slice.lat.item(), slice=pts) @@ -1010,7 +1010,7 @@ def points_list(self): mean_vegetation_height=lon_slice.mean_veg_height.isel(lat=l).item(), canopy_height=lon_slice.canopy_height.isel(lat=l).item(), canopy_coverage=lon_slice.canopy_coverage.isel(lat=l).item() - ) for l in range(len(ds['lat']))] + ) for l in range(len(lon_slice['lat']))] slice_points['longitude'] = dict(longitude=lon_slice.lon.item(), slice=pts) From 62564860ce34f543d17cdab4c2626375ec828bf8 Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 18 Jun 2024 15:01:11 -0700 Subject: [PATCH 25/27] poetry re lock --- poetry.lock | 68 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index b65b465f..e8c7dde2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -343,32 +343,40 @@ numpy = ">1.13.3" [[package]] name = "cftime" -version = "1.6.3" +version = "1.6.4" description = "Time-handling functionality from netcdf4-python" optional = false python-versions = ">=3.8" files = [ - {file = "cftime-1.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b62d42546fa5c914dfea5b15a9aaed2087ea1211cc36d08c374502ef95892038"}, - {file = "cftime-1.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb6dd70b2ccabfe1a14b7fbb0bbdce0418e71697094373c0d573c880790fa291"}, - {file = "cftime-1.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9878bfd8c1c3f24184ecbd528f739ba46ebaceaf1c8a24d348d7befb117a285"}, - {file = "cftime-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:3cf6e216a4c06f9a628cdf8e9c9d5e8097fb3eb02dd087dd14ab3b18478a7271"}, - {file = "cftime-1.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d2c01456d9d7b46aa710a41d1c711a50d5ea259aff4a987d0e973d1093bc922"}, - {file = "cftime-1.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80eb1170ce1639016f55760847f4aadd04b0312496c5bac2797e930914bba48d"}, - {file = "cftime-1.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87dadd0824262bdd7493babd2a44447da0a22175ded8ae9e060a3aebec7c5d7"}, - {file = "cftime-1.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:0a38eb9f5c733a23e1714bd3ef2762ed5acee34f127670f8fb4ad6464946f6b3"}, - {file = "cftime-1.6.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2d113a01ab924445e61d65c26bbd95bc08e4a22878d3b947064bba056c884c4a"}, - {file = "cftime-1.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f11685663a6af97418908060492a07663c16d42519c139ca03c2ffb1377fd25"}, - {file = "cftime-1.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a98abb1d46d118e52b0611ce668a0b714b407be26177ef0581ecf5e95f894725"}, - {file = "cftime-1.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:4d6fbd5f41b322cfa7b0ac3aaadeceb4450100a164b5bccbbb9e7c5048489a88"}, - {file = "cftime-1.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bedb577bc8b8f3f10f5336c0792e5dae88605781890f50f36b45bb46907968e8"}, - {file = "cftime-1.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:022dabf1610cdd04a693e730fa8f71d307059717f29dba921e7486e553412bb4"}, - {file = "cftime-1.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbf782ab4ac0605bdec2b941952c897595613203942b7f8c2fccd17efa5147df"}, - {file = "cftime-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:9eb177a02db7cd84aa6962278e4bd2d3106a545de82e6aacd9404f1e153661db"}, - {file = "cftime-1.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b86be8c2f254147be4ba88f12099466dde457a4a3a21de6c69d52a7224c13ae"}, - {file = "cftime-1.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:523b9a6bf03f5e36407979e248381d0fcab2d225b915bbde77d00c6dde192b90"}, - {file = "cftime-1.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a14d2c7d22fd2a6dfa6ad563283b6d6679f1df95e0ed8d14b8f284dad402887"}, - {file = "cftime-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:d9b00c2844c7a1701d8ede5336b6321dfee256ceab81a34a1aff0483d56891a6"}, - {file = "cftime-1.6.3.tar.gz", hash = "sha256:d0a6b29f72a13f08e008b9becff247cc75c84acb213332ede18879c5b6aa4dfd"}, + {file = "cftime-1.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ee70074df4bae0d9ee98f201cf5f11fd302791cf1cdeb73c34f685d6b632e17d"}, + {file = "cftime-1.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e5456fd58d4cc6b8d7b4932b749617ee142b62a52bc5d8e3c282ce69ce3a20ba"}, + {file = "cftime-1.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1289e08617be350a6b26c6e4352a0cb088625ac33d25e95110df549c26d6ab8e"}, + {file = "cftime-1.6.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b132d9225b4a109929866200846c72302316db9069e2de3ec8d8ec377f567f"}, + {file = "cftime-1.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ca1a264570e68fbb611bba251641b8efd0cf88c0ad2dcab5fa784df264232b75"}, + {file = "cftime-1.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:6fc82928cbf477bebf233f41914e64bff7b9e894c7f0c34170784a48250f8da7"}, + {file = "cftime-1.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1558d9b477bd29626cd8bfc89e736635f72887d1a993e2834ab579bba7abb8c"}, + {file = "cftime-1.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:03494e7b66a2fbb6b04e364ab67185130dee0ced660abac5c1559070571d143d"}, + {file = "cftime-1.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dcb2a01d4e614437582af33b36db4fb441b7666758482864827a1f037d2b639"}, + {file = "cftime-1.6.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b47bf25195fb3889bbae34df0e80957eb69c48f66902f5d538c7a8ec34253f6"}, + {file = "cftime-1.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d4f2cc0d5c6ffba9c5b0fd1ecd0c7c1c426d0be7b8de1480e2a9fb857c1905e9"}, + {file = "cftime-1.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:76b8f1e5d1e424accdf760a43e0a1793a7b640bab83cb067273d5c9dbb336c44"}, + {file = "cftime-1.6.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c349a91fa7ac9ec50118b04a8746bdea967bd2fc525d87c776003040b8d3392"}, + {file = "cftime-1.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:588d073400798adc24ece759cd1cb24ef730f55d1f70e31a898e7686f9d763d8"}, + {file = "cftime-1.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e07b91b488570573bbeb6f815656a8974d13d15b2279c82de2927f4f692bbcd"}, + {file = "cftime-1.6.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f92f2e405eeda47b30ab6231d8b7d136a55f21034d394f93ade322d356948654"}, + {file = "cftime-1.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:567574df94d0de1101bb5da76e7fbc6eabfddeeb2eb8cf83286b3599a136bbf7"}, + {file = "cftime-1.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:5b5ad7559a16bedadb66af8e417b6805f758acb57aa38d2730844dfc63a1e667"}, + {file = "cftime-1.6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c072fe9e09925af66a9473edf5752ca1890ba752e7c1935d1f0245ad48f0977c"}, + {file = "cftime-1.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c05a71389f53d6340cb365b60f028c08268c72401660b9ef76108dee9f1cb5b2"}, + {file = "cftime-1.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0edeb1cb019d8155b2513cffb96749c0d7d459370e69bdf03077e0bee214aed8"}, + {file = "cftime-1.6.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f05d5d6bb4137f9783fa61ad330030fcea8dcc6946dea69a27774edbe480e7"}, + {file = "cftime-1.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:b32ac1278a2a111b066d5a1e6e5ce6f38c4c505993a6a3130873b56f99d7b56f"}, + {file = "cftime-1.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c20f03e12af39534c3450bba376272803bfb850b5ce6433c839bfaa99f8d835a"}, + {file = "cftime-1.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:90609b3c1a31a756a68ecdbc961a4ce0b22c1620f166c8dabfa3a4c337ac8b9e"}, + {file = "cftime-1.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbe11ad73b2a0ddc79995da21459fc2a3fd6b1593ca73f00a60e4d81c3e230f3"}, + {file = "cftime-1.6.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25f043703e785de0bd7cd8222c0a53317e9aeb3dfc062588b05e6f3ebb007468"}, + {file = "cftime-1.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f9acc272df1022f24fe7dbe9de43fa5d8271985161df14549e4d8d28c90dc9ea"}, + {file = "cftime-1.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e8467b6fbf8dbfe0be8c04d61180765fdd3b9ab0fe51313a0bbf87e63634a3d8"}, ] [package.dependencies] @@ -1660,13 +1668,13 @@ files = [ [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -2352,13 +2360,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2642,4 +2650,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~=3.8" -content-hash = "392c80ca8973c4868a1d3aefa7b836be85ae433de6464dd889a72cb320a6701e" +content-hash = "fbf7bc3bd1a573b0d651c983aa5d6cea8b8ac6e5d494b025ce86df7860285449" From 72d7beffbaf492c9c19d9cc880812dbbaed5d4e3 Mon Sep 17 00:00:00 2001 From: rileykk Date: Tue, 9 Jul 2024 16:13:31 -0700 Subject: [PATCH 26/27] poetry lock --- poetry.lock | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 690e672b..3b875a4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -947,6 +947,17 @@ files = [ {file = "futures-3.0.5.tar.gz", hash = "sha256:0542525145d5afc984c88f914a0c85c77527f65946617edb5274f72406f981df"}, ] +[[package]] +name = "geographiclib" +version = "2.0" +description = "The geodesic routines from GeographicLib" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"}, + {file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"}, +] + [[package]] name = "geomet" version = "0.2.1.post1" @@ -962,6 +973,29 @@ files = [ click = "*" six = "*" +[[package]] +name = "geopy" +version = "2.4.1" +description = "Python Geocoding Toolbox" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7"}, + {file = "geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1"}, +] + +[package.dependencies] +geographiclib = ">=1.52,<3" + +[package.extras] +aiohttp = ["aiohttp"] +dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"] +dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"] +requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"] +timezone = ["pytz"] + [[package]] name = "idna" version = "3.7" @@ -2645,4 +2679,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "~=3.8" -content-hash = "d5724c5c4ddd9411f2faf5519c3b3584bc9b8259e84d5c692911322ff67f4042" +content-hash = "54da7254c2fd73309097f13cd8149364be4761d401e09aa68ffbee480d9a9353" From 81450a226eef86a6fc8ae61f1382ac0e09eecc1e Mon Sep 17 00:00:00 2001 From: rileykk Date: Mon, 23 Sep 2024 11:18:01 -0700 Subject: [PATCH 27/27] Poetry re-lock --- poetry.lock | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index de1ef5d5..a37bde99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -977,6 +977,17 @@ files = [ {file = "futures-3.0.5.tar.gz", hash = "sha256:0542525145d5afc984c88f914a0c85c77527f65946617edb5274f72406f981df"}, ] +[[package]] +name = "geographiclib" +version = "2.0" +description = "The geodesic routines from GeographicLib" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"}, + {file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"}, +] + [[package]] name = "geomet" version = "0.2.1.post1" @@ -992,6 +1003,29 @@ files = [ click = "*" six = "*" +[[package]] +name = "geopy" +version = "2.4.1" +description = "Python Geocoding Toolbox" +optional = false +python-versions = ">=3.7" +files = [ + {file = "geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7"}, + {file = "geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1"}, +] + +[package.dependencies] +geographiclib = ">=1.52,<3" + +[package.extras] +aiohttp = ["aiohttp"] +dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"] +dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"] +dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"] +requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"] +timezone = ["pytz"] + [[package]] name = "idna" version = "3.10" @@ -2703,4 +2737,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "~=3.8" -content-hash = "2bdf3a4ef2a7f9b982ad43dd9806fcc8fa3c450c994fa48c8d776c3a963af56b" +content-hash = "fa333c49563b45aa94c732e1fa3695fa5f18cea90150fe4d7dd95cf59f8ea605"