From 47f8c7b20c7cdad1084ea298c1d72c4e8fc968aa Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 15:26:21 +0100 Subject: [PATCH 01/11] Fix docstrings and typos; other clean up --- geometric_features/__main__.py | 40 +++---- geometric_features/aggregation/__init__.py | 9 +- .../aggregation/ocean/moc_basins.py | 2 +- geometric_features/download.py | 16 +-- geometric_features/feature_collection.py | 103 +++++++++--------- geometric_features/geometric_features.py | 30 ++--- geometric_features/plot.py | 22 ++-- .../test/test_set_group_name.py | 9 -- geometric_features/utils.py | 10 +- 9 files changed, 112 insertions(+), 129 deletions(-) diff --git a/geometric_features/__main__.py b/geometric_features/__main__.py index a9054642..faa06f10 100644 --- a/geometric_features/__main__.py +++ b/geometric_features/__main__.py @@ -10,9 +10,9 @@ def combine_features(): - ''' + """ Entry point for combining features from a file - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -39,9 +39,9 @@ def combine_features(): def difference_features(): - ''' + """ Entry point for differencing features from a file - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -70,9 +70,9 @@ def difference_features(): def fix_features_at_antimeridian(): - ''' + """ Entry point for splitting features that cross +/- 180 degrees - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -95,9 +95,9 @@ def fix_features_at_antimeridian(): def merge_features(): - ''' + """ Entry point for merging features from the geometric_data cache - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -106,7 +106,7 @@ def merge_features(): metavar="FILE") parser.add_argument("-c", "--component", dest="component", help="The component (ocean, landice, etc.) from which " - "to retieve the geometric features", + "to retrieve the geometric features", metavar="COMP") parser.add_argument("-b", "--object_type", dest="object_type", help="The type of geometry to load, a point (0D), " @@ -154,9 +154,9 @@ def merge_features(): def plot_features(): - ''' + """ Entry point for plotting features from a file - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) @@ -202,9 +202,9 @@ def plot_features(): def set_group_name(): - ''' + """ Set the group name of the feature collection - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -228,9 +228,9 @@ def set_group_name(): def simplify_features(): - ''' + """ Features in the collection are simplified using ``shapely`` - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -239,7 +239,7 @@ def simplify_features(): parser.add_argument("-t", "--tolerance", dest="tolerance", type=float, default=0.0, help="A distance in deg lon/lat by which each point " - "in a feature can be moved during simpification", + "in a feature can be moved during simplification", metavar="TOLERANCE") parser.add_argument("-o", "--output", dest="output_file_name", help="Output file, e.g., features.geojson.", @@ -257,10 +257,10 @@ def simplify_features(): def split_features(): - ''' + """ Features in the collection are split into individual files in the geometric_data cache - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", @@ -283,9 +283,9 @@ def split_features(): def tag_features(): - ''' + """ Features in the collection are tagged with the given tag(s) - ''' + """ parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-f", "--feature_file", dest="feature_file", diff --git a/geometric_features/aggregation/__init__.py b/geometric_features/aggregation/__init__.py index b0ae5a5d..65177319 100644 --- a/geometric_features/aggregation/__init__.py +++ b/geometric_features/aggregation/__init__.py @@ -14,7 +14,8 @@ def get_aggregator_by_name(region_group): region_group : str The name of a region group to get mask features for, one of 'Antarctic Regions', 'Arctic Ocean Regions', 'Arctic Sea Ice Regions', - 'Ocean Basins', 'Ice Shelves', 'Ocean Subbasins', or 'ISMIP6 Regions' + 'Ocean Basins', 'Ice Shelves', 'Ocean Subbasins', 'ISMIP6 Regions', + 'MOC Basins', 'Transport Transects', or 'Arctic Transport Transects' Returns ------- @@ -60,11 +61,11 @@ def get_aggregator_by_name(region_group): 'date': '20210323', 'function': transport}, 'Arctic Transport Transects': {'prefix': 'arcticTransportTransects', - 'date': '20220926', - 'function': arctic_transport}} + 'date': '20220926', + 'function': arctic_transport}} if region_group not in regions: - raise ValueError('Unknown region group {}'.format(region_group)) + raise ValueError(f'Unknown region group {region_group}') region = regions[region_group] diff --git a/geometric_features/aggregation/ocean/moc_basins.py b/geometric_features/aggregation/ocean/moc_basins.py index fb5438b7..0c93fbb7 100644 --- a/geometric_features/aggregation/ocean/moc_basins.py +++ b/geometric_features/aggregation/ocean/moc_basins.py @@ -13,7 +13,7 @@ def moc(gf): Parameters ---------- gf : ``GeometricFeatures`` - An object that knows how to download and read geometric featuers + An object that knows how to download and read geometric features Returns ------- diff --git a/geometric_features/download.py b/geometric_features/download.py index 07e602fd..ca77d303 100644 --- a/geometric_features/download.py +++ b/geometric_features/download.py @@ -1,11 +1,3 @@ -""" -Utilities for downloading files -""" -# Authors -# ------- -# Milena Veneziani -# Xylar Asay-Davis - from __future__ import absolute_import, division, print_function, \ unicode_literals @@ -18,9 +10,9 @@ # From https://stackoverflow.com/a/1094933/7728169 def sizeof_fmt(num, suffix='B'): - ''' + """ Covert a number of bytes to a human-readable file size - ''' + """ for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) @@ -29,9 +21,9 @@ def sizeof_fmt(num, suffix='B'): def download_files(fileList, urlBase, outDir): - ''' + """ Download a list of files from a URL to a directory - ''' + """ # Authors # ------- # Milena Veneziani diff --git a/geometric_features/feature_collection.py b/geometric_features/feature_collection.py index 97798f39..78529b65 100644 --- a/geometric_features/feature_collection.py +++ b/geometric_features/feature_collection.py @@ -8,7 +8,7 @@ except ImportError: # maybe no xwindows, so let's use the 'Agg' backend import matplotlib as mpl - mpl.use('Agg', warn=False, force=True) + mpl.use('Agg', force=True) import matplotlib.pyplot as plt import shapely.geometry @@ -25,7 +25,7 @@ def read_feature_collection(fileName): - ''' + """ Read a feature collection from a geojson file. Parameters @@ -37,7 +37,7 @@ def read_feature_collection(fileName): ------- fc : geometric_features.FeatureCollection The feature collection read in - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -53,7 +53,7 @@ def read_feature_collection(fileName): class FeatureCollection(object): - ''' + """ An object for representing and manipulating a collection of geoscientific geometric features. @@ -66,13 +66,13 @@ class FeatureCollection(object): otherProperties : dict Other properties of the feature collection such as ``type`` and ``groupName`` - ''' + """ # Authors # ------- # Xylar Asay-Davis def __init__(self, features=None, otherProperties=None): - ''' + """ Construct a new feature collection Parameters @@ -81,10 +81,10 @@ def __init__(self, features=None, otherProperties=None): A list of python dictionaries describing each feature, following the geojson convention - otherProperties : dict + otherProperties : dict, optional Other properties of the feature collection such as ``type`` and ``groupName`` - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -99,14 +99,14 @@ def __init__(self, features=None, otherProperties=None): self.otherProperties.update(otherProperties) def add_feature(self, feature): - ''' - Add a feature to the feature collection if it isn't alerady present + """ + Add a feature to the feature collection if it isn't already present Parameters ---------- feature : dict The feature to add - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -116,14 +116,14 @@ def add_feature(self, feature): self.features.append(feature) def merge(self, other): - ''' + """ Merge another feature collection into this one Parameters ---------- other : geometric_features.FeatureCollection The other feature collection - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -137,14 +137,17 @@ def merge(self, other): self.otherProperties[key] = other.otherProperties[key] def tag(self, tags, remove=False): - ''' + """ Add tags to all features in the collection Parameters ---------- tags : list of str Tags to be added - ''' + + remove : bool, optional + Whether to remove the tag rather than adding it + """ # Authors # ------- # Xylar Asay-Davis @@ -159,14 +162,14 @@ def tag(self, tags, remove=False): feature['properties']['tags'] = ';'.join(featureTags) def set_group_name(self, groupName): - ''' + """ Set the group name of a feature collection Parameters ---------- groupName : str The group name - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -174,7 +177,7 @@ def set_group_name(self, groupName): self.otherProperties['groupName'] = groupName def combine(self, featureName): - ''' + """ Combines the geometry of the feature collection into a single feature Parameters @@ -193,7 +196,7 @@ def combine(self, featureName): ValueError If the combined geometry is of an unsupported type (typically ``GeometryCollection``) - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -216,7 +219,7 @@ def combine(self, featureName): raise ValueError('combined geometry is of unsupported type ' '{}. Most likely cause is that ' 'multiple feature types (regions, points and ' - 'transects) are being cobined.'.format( + 'transects) are being combined.'.format( geometry['type'])) feature = {} @@ -236,7 +239,7 @@ def combine(self, featureName): return fc def difference(self, maskingFC, show_progress=False): - ''' + """ Use features from a masking collection to mask out (remove part of the geometry from) this collection. @@ -253,7 +256,7 @@ def difference(self, maskingFC, show_progress=False): fc : geometric_features.FeatureCollection A new feature collection with a single feature with the geometry masked - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -294,9 +297,9 @@ def difference(self, maskingFC, show_progress=False): add = False break - if(add): + if add: newFeature = copy.deepcopy(feature) - if(masked): + if masked: maskedCount += 1 newFeature['geometry'] = \ shapely.geometry.mapping(featureShape) @@ -315,7 +318,7 @@ def difference(self, maskingFC, show_progress=False): return fc def fix_antimeridian(self): - ''' + """ Split features at +/-180 degrees (the antimeridian) to make them valid geojson geometries @@ -323,7 +326,7 @@ def fix_antimeridian(self): ------- fc : geometric_features.FeatureCollection A new feature collection with the antimeridian handled correctly - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -345,7 +348,7 @@ def fix_antimeridian(self): return fc def simplify(self, tolerance=0.0): - ''' + """ Features in the collection are simplified using ``shapely`` Parameters @@ -357,7 +360,7 @@ def simplify(self, tolerance=0.0): ------- fc : geometric_features.FeatureCollection A new feature collection with simplified geometries - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -375,7 +378,7 @@ def simplify(self, tolerance=0.0): return fc def feature_in_collection(self, feature): - ''' + """ Is this feature already in the collection? Parameters @@ -387,7 +390,7 @@ def feature_in_collection(self, feature): ------- inCollection : bool Whether the feature is in the collection - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -396,7 +399,7 @@ def feature_in_collection(self, feature): return feature['properties']['name'] in featureNames def to_geojson(self, fileName, stripHistory=False, indent=4): - ''' + """ Write the feature collection to a geojson file Parameters @@ -411,7 +414,7 @@ def to_geojson(self, fileName, stripHistory=False, indent=4): indent : int, optional The number of spaces to use for indentation when formatting the geojson file - ''' + """ # Authors # ------- # Douglas Jacobsen, Xylar Asay-Davis, Phillip J. Wolfram @@ -445,7 +448,7 @@ def to_geojson(self, fileName, stripHistory=False, indent=4): def plot(self, projection, maxLength=4.0, figsize=None, colors=None, dpi=200): - ''' + """ Plot the features on a map using cartopy. Parameters @@ -475,7 +478,7 @@ def plot(self, projection, maxLength=4.0, figsize=None, colors=None, ------- fig : ``matplotlib.figure.Figure`` The figure - ''' + """ # Authors # ------- # Xylar Asay-Davis, `Phillip J. Wolfram @@ -508,7 +511,7 @@ def plot(self, projection, maxLength=4.0, figsize=None, colors=None, for featureIndex, feature in enumerate(self.features): geomType = feature['geometry']['type'] shape = shapely.geometry.shape(feature['geometry']) - if(maxLength > 0.0): + if maxLength > 0.0: shape = subdivide_geom(shape, geomType, maxLength) refProjection = cartopy.crs.PlateCarree() @@ -537,7 +540,7 @@ def plot(self, projection, maxLength=4.0, figsize=None, colors=None, ax.add_geometries((shape,), crs=refProjection, **props) box = shapely.geometry.box(*bounds) - if(maxLength > 0.0): + if maxLength > 0.0: box = subdivide_geom(box, 'Polygon', maxLength) boxProjected = projection.project_geometry(box, src_crs=refProjection) @@ -558,9 +561,9 @@ def plot(self, projection, maxLength=4.0, figsize=None, colors=None, def _get_geom_object_type(geomType): - ''' + """ Get the object type for a given geometry type - ''' + """ geomObjectTypes = {'Polygon': 'region', 'MultiPolygon': 'region', 'LineString': 'transect', @@ -571,7 +574,7 @@ def _get_geom_object_type(geomType): def _validate_feature(feature): - ''' + """ Validate the geometric feature to ensure that it has all required keys: - properties - name @@ -595,7 +598,7 @@ def _validate_feature(feature): ValueError If the geometry type doesn't match the object type - ''' + """ # Authors # ------- # Xylar Asay-Davis, Phillip J. Wolfram @@ -682,14 +685,14 @@ def _to_polar(lon, lat): x = radius*np.sin(phi) y = radius*np.cos(phi) - if(isinstance(lon, list)): + if isinstance(lon, list): x = x.tolist() y = y.tolist() - elif(isinstance(lon, tuple)): + elif isinstance(lon, tuple): x = tuple(x) y = tuple(y) - return (x, y) + return x, y def _from_polar(x, y): radius = np.sqrt(np.array(x)**2+np.array(y)**2) @@ -704,13 +707,13 @@ def _from_polar(x, y): lon = 180./np.pi*phi lat = sign*(90. - 180./np.pi*radius) - if(isinstance(x, list)): + if isinstance(x, list): lon = lon.tolist() lat = lat.tolist() - elif(isinstance(x, tuple)): + elif isinstance(x, tuple): lon = tuple(lon) lat = tuple(lat) - return (lon, lat) + return lon, lat epsilon = 1e-14 antimeridianWedge = shapely.geometry.Polygon([(epsilon, -np.pi), @@ -725,8 +728,8 @@ def _from_polar(x, y): 1.) polarShape = shapely.ops.transform(_to_polar, featureShape) - if(not polarShape.intersects(antimeridianWedge)): - # this feature doesn't corss the antimeridian + if not polarShape.intersects(antimeridianWedge): + # this feature doesn't cross the antimeridian return difference = polarShape.difference(antimeridianWedge) @@ -737,9 +740,9 @@ def _from_polar(x, y): def _round_coords(coordinates, digits=6): - ''' + """ Round the coordinates of geojson geometry data before writing to a file - ''' + """ if isinstance(coordinates, float): return round(coordinates, digits) if isinstance(coordinates, int): diff --git a/geometric_features/geometric_features.py b/geometric_features/geometric_features.py index f052433e..393aec6b 100644 --- a/geometric_features/geometric_features.py +++ b/geometric_features/geometric_features.py @@ -14,7 +14,7 @@ class GeometricFeatures(object): - ''' + """ An object for keeping track of where geometric features are cached and downloading missing features as needed. @@ -22,20 +22,20 @@ class GeometricFeatures(object): ---------- allFeaturesAndTags : dict of dict A cache of all the feature names and tags in the ``geometric_features`` - repo used to determine which featues need to be downloaded into the + repo used to determine which features need to be downloaded into the local cache remoteBranch : str, optional The branch or tag from the ``geometric_features`` repo to download from if files are missing from the local cache - ''' + """ # Authors # ------- # Xylar Asay-Davis def __init__(self, cacheLocation=None, remoteBranchOrTag=None): - ''' + """ The constructor for the GeometricFeatures object Parameters @@ -49,7 +49,7 @@ def __init__(self, cacheLocation=None, remoteBranchOrTag=None): The branch or tag from the ``geometric_features`` repo to download from if files are missing from the local cache, with default to a tag the same as this version of ``geometric_features`` - ''' + """ if cacheLocation is None: if 'GEOMETRIC_DATA_DIR' in os.environ: @@ -71,7 +71,7 @@ def __init__(self, cacheLocation=None, remoteBranchOrTag=None): def read(self, componentName, objectType, featureNames=None, tags=None, allTags=True): - ''' + """ Read one or more features from the cached collection of geometric features. If any of the requested features have not been cached, they are downloaded from the ``geometric_features`` GitHub repository. If @@ -102,7 +102,7 @@ def read(self, componentName, objectType, featureNames=None, tags=None, ------- fc : geometric_features.FeatureCollection The feature collection read in - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -119,7 +119,7 @@ def read(self, componentName, objectType, featureNames=None, tags=None, return fc def split(self, fc, destinationDir=None): - ''' + """ Split a feature collection into individual files for each feature. This is how new geometry should be added to the ``geometric_features`` repo. @@ -136,7 +136,7 @@ def split(self, fc, destinationDir=None): ------- fc : geometric_features.FeatureCollection The feature collection read in - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -167,7 +167,7 @@ def split(self, fc, destinationDir=None): def _download_geometric_features(self, componentName, objectType, featureNames): - ''' + """ Determine a list of requested files and download the any that are missing from the repo @@ -189,7 +189,7 @@ def _download_geometric_features(self, componentName, objectType, File names of the features - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -218,7 +218,7 @@ def _download_geometric_features(self, componentName, objectType, def _get_feature_names(self, componentName, objectType, featureNames, tags, allTags): - ''' + """ Find features by name or tags, reporting errors in the process Parameters @@ -251,7 +251,7 @@ def _get_feature_names(self, componentName, objectType, featureNames, If the component is not in the geometric features repo, if the object type is not in the component, or if one or more feature names are not found - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -292,7 +292,7 @@ def _get_feature_names(self, componentName, objectType, featureNames, def _get_file_name(componentName, objectType, featureName): - ''' + """ Get the relative path of a cached geometric feature from its component, object type and feature name. @@ -312,7 +312,7 @@ def _get_file_name(componentName, objectType, featureName): ------- fileName : str The relative path to that feature - ''' + """ # Authors # ------- # Douglas Jacobsen, Xylar Asay-Davis diff --git a/geometric_features/plot.py b/geometric_features/plot.py index 0c925baa..0ac73266 100644 --- a/geometric_features/plot.py +++ b/geometric_features/plot.py @@ -9,17 +9,13 @@ def build_projections(): - ''' + """ Create a list of projections for plotting features. Possible map types are 'cyl', 'merc', 'mill', 'mill2', 'moll', 'moll2', 'robin', 'robin2', 'ortho', 'northpole', 'southpole', 'atlantic', 'pacific', 'americas', 'asia' - ''' - # Authors - # ------- - # Xylar Asay-Davis, `Phillip J. Wolfram - - projections = {} + """ + projections = dict() projections['cyl'] = cartopy.crs.PlateCarree() projections['merc'] = cartopy.crs.Mercator() projections['mill'] = cartopy.crs.Miller() @@ -48,9 +44,9 @@ def build_projections(): def plot_base(mapType, projection): - ''' + """ Plot the background map on which to plot a feature collection - ''' + """ # Authors # ------- # Xylar Asay-Davis, `Phillip J. Wolfram @@ -71,14 +67,14 @@ def plot_base(mapType, projection): ax.gridlines(crs=cartopy.crs.PlateCarree(), draw_labels=draw_labels, linewidth=0.5, color='gray', linestyle='--') - return (ax, projection) + return ax, projection def subdivide_geom(geometry, geomtype, maxLength): - ''' + """ Subdivide the line segments for a given set of geometry so plots are smoother - ''' + """ # Authors # ------- # Xylar Asay-Davis, `Phillip J. Wolfram @@ -93,7 +89,7 @@ def subdivide_line_string(lineString, periodic=False): for iVert in range(len(coords)-1): segment = shapely.geometry.LineString([coords[iVert], coords[iVert+1]]) - if(segment.length < maxLength): + if segment.length < maxLength: outCoords.append(coords[iVert+1]) else: # we need to subdivide this segment diff --git a/geometric_features/test/test_set_group_name.py b/geometric_features/test/test_set_group_name.py index 7e117c27..2a2429fc 100644 --- a/geometric_features/test/test_set_group_name.py +++ b/geometric_features/test/test_set_group_name.py @@ -1,9 +1,3 @@ -""" -Unit test infrastructure for FeatureCollection.set_group_name - -Phillip J. Wolfram -""" - import pytest import json @@ -60,6 +54,3 @@ def verify_groupName(destfile, groupName): destfile = str(self.datadir.join('test.geojson')) fc.to_geojson(destfile) verify_groupName(destfile, groupName) - - -# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python diff --git a/geometric_features/utils.py b/geometric_features/utils.py index 938a0b43..b475a9a3 100644 --- a/geometric_features/utils.py +++ b/geometric_features/utils.py @@ -11,8 +11,8 @@ def write_feature_names_and_tags(cacheLocation='./geometry_data'): - ''' - Make a json file with all the availabe features and tags by component + """ + Make a json file with all the available features and tags by component and object type, used to update the file when new geometric features are added to the repo @@ -20,7 +20,7 @@ def write_feature_names_and_tags(cacheLocation='./geometry_data'): ---------- cacheLocation : str, optional The location of the geometric features cache - ''' + """ # Authors # ------- # Xylar Asay-Davis @@ -50,9 +50,9 @@ def write_feature_names_and_tags(cacheLocation='./geometry_data'): def provenance_command(): - ''' + """ Get a string to use for provenance in each feature - ''' + """ # Authors # ------- # Phillip J. Wolfram, Xylar Asay-Davis From b7863a3cdfe7ca130dedbec25d1ba9cd1dc32feb Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 15:53:18 +0100 Subject: [PATCH 02/11] Switch from OrderedDict to dict --- geometric_features/feature_collection.py | 31 +++++++++++------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/geometric_features/feature_collection.py b/geometric_features/feature_collection.py index 78529b65..90a0866e 100644 --- a/geometric_features/feature_collection.py +++ b/geometric_features/feature_collection.py @@ -2,7 +2,6 @@ unicode_literals import json -from collections import OrderedDict try: import matplotlib.pyplot as plt except ImportError: @@ -92,7 +91,7 @@ def __init__(self, features=None, otherProperties=None): self.features = [] else: self.features = features - self.otherProperties = OrderedDict() + self.otherProperties = dict() self.otherProperties['type'] = 'FeatureCollection' self.otherProperties['groupName'] = 'enterGroupName' if otherProperties is not None: @@ -338,10 +337,10 @@ def fix_antimeridian(self): feature['geometry']) if geometry is None: # no change - newFeature = OrderedDict(feature) + newFeature = dict(feature) else: - newFeature = OrderedDict() - newFeature['properties'] = OrderedDict(feature['properties']) + newFeature = dict() + newFeature['properties'] = dict(feature['properties']) newFeature['geometry'] = geometry fc.add_feature(newFeature) @@ -419,7 +418,7 @@ def to_geojson(self, fileName, stripHistory=False, indent=4): # ------- # Douglas Jacobsen, Xylar Asay-Davis, Phillip J. Wolfram - outFeatures = OrderedDict(self.otherProperties) + outFeatures = dict(self.otherProperties) # features go last for readability outFeatures['features'] = copy.deepcopy(self.features) @@ -637,29 +636,27 @@ def _validate_feature(feature): tags = '' # Make the properties an ordered dictionary so they can be sorted - outProperties = OrderedDict( - (('name', feature['properties']['name']), - ('tags', tags), - ('object', feature['properties']['object']), - ('component', feature['properties']['component']), - ('author', author))) + outProperties = {'name': feature['properties']['name'], + 'tags': tags, + 'object': feature['properties']['object'], + 'component': feature['properties']['component'], + 'author': author} for key in sorted(feature['properties']): if key not in outProperties.keys(): outProperties[key] = feature['properties'][key] # Make the geometry an ordered dictionary so they can keep it in the # desired order - outGeometry = OrderedDict( - (('type', feature['geometry']['type']), - ('coordinates', feature['geometry']['coordinates']))) + outGeometry = {'type': feature['geometry']['type'], + 'coordinates': feature['geometry']['coordinates']} for key in sorted(feature['geometry']): if key not in outGeometry.keys(): outGeometry[key] = feature['geometry'][key] # Make the feature an ordered dictionary so properties come before geometry # (easier to read) - outFeature = OrderedDict((('type', 'Feature'), - ('properties', outProperties))) + outFeature = {'type': 'Feature', + 'properties': outProperties} # Add the rest for key in sorted(feature): if key not in ['geometry', 'type', 'properties']: From d01e814da5c1f73247189aae013f19ec946a99af Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 16:27:58 +0100 Subject: [PATCH 03/11] Add pytest to dev spec file --- dev-spec.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-spec.txt b/dev-spec.txt index c8a30820..49cdc383 100644 --- a/dev-spec.txt +++ b/dev-spec.txt @@ -12,6 +12,7 @@ shapely # Development pip +pytest # Documentation sphinx From ac5986b13f7f59116e6f7909f7e6b13affd31775 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 16:52:12 +0100 Subject: [PATCH 04/11] Fix bug in removing tags --- geometric_features/feature_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometric_features/feature_collection.py b/geometric_features/feature_collection.py index 90a0866e..d00364d0 100644 --- a/geometric_features/feature_collection.py +++ b/geometric_features/feature_collection.py @@ -155,7 +155,7 @@ def tag(self, tags, remove=False): featureTags = feature['properties']['tags'].split(';') for tag in tags: if remove and tag in featureTags: - featureTags.pop(tag) + featureTags.remove(tag) elif not remove and tag not in featureTags: featureTags.append(tag) feature['properties']['tags'] = ';'.join(featureTags) From a1d85cf7e82e41f82df233a4c29e59a4cad9205d Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 20:41:41 +0100 Subject: [PATCH 05/11] Add tests for all FeatureCollection methods --- .../test/test_feature_collection.py | 399 ++++++++++++++++++ .../test/test_set_group_name.py | 56 --- 2 files changed, 399 insertions(+), 56 deletions(-) create mode 100644 geometric_features/test/test_feature_collection.py delete mode 100644 geometric_features/test/test_set_group_name.py diff --git a/geometric_features/test/test_feature_collection.py b/geometric_features/test/test_feature_collection.py new file mode 100644 index 00000000..1a760b60 --- /dev/null +++ b/geometric_features/test/test_feature_collection.py @@ -0,0 +1,399 @@ +import os +import json +import pytest +import shapely +import shapely.geometry + +from geometric_features.test import TestCase, loaddatadir +from geometric_features import GeometricFeatures, FeatureCollection, \ + read_feature_collection + + +@pytest.mark.usefixtures('loaddatadir') +class TestFeatureCollection(TestCase): + + @staticmethod + def read_feature(region='Adriatic_Sea'): + """ + Read an example feature collection for testing + + Parameters + ---------- + region : str, optional + The name of an ocean region to read + + Returns + ------- + fc : geometric_features.FeatureCollection + The feature collection + """ + if 'GEOMETRIC_DATA_DIR' in os.environ: + cache_location = os.environ['GEOMETRIC_DATA_DIR'] + else: + cache_location = './geometric_data' + + filename = f'{cache_location}/ocean/region/{region}/region.geojson' + + fc = read_feature_collection(filename) + return fc + + @staticmethod + def check_feature(feature, expected_name='Adriatic Sea', + expected_type='Polygon'): + """ + Check some properties of the feature + + Parameters + ---------- + feature : dict + A geojson feature to check + + expected_name : str + The expected name of the feature + + expected_type : str + The expected geometry type of the feature + """ + assert feature['properties']['name'] == expected_name + assert feature['properties']['component'] == 'ocean' + assert feature['geometry']['type'] == expected_type + + def test_read_feature_collection(self): + """ + Test reading a feature collection from a file + """ + fc = self.read_feature() + assert len(fc.features) == 1 + feature = fc.features[0] + self.check_feature(feature) + + def test_copy_features(self): + """ + Test copying the features in a feature collection + """ + fc = self.read_feature() + other = FeatureCollection(features=fc.features, + otherProperties=fc.otherProperties) + assert len(other.features) == 1 + feature = other.features[0] + + self.check_feature(feature) + + def test_add_feature(self): + """ + Test adding a feature to a collection + """ + fc1 = self.read_feature() + fc2 = self.read_feature('Aegean_Sea') + + # add a feature already in the feature collection + fc1.add_feature(fc1.features[0]) + assert len(fc1.features) == 1 + + # add a new feature to the feature collection + fc1.add_feature(fc2.features[0]) + assert len(fc1.features) == 2 + + self.check_feature(fc1.features[0]) + self.check_feature(fc1.features[1], expected_name='Aegean Sea') + + def test_merge(self): + """ + Test merging 2 feature collections + """ + fc1 = self.read_feature() + fc2 = self.read_feature('Aegean_Sea') + + # add a feature already in the feature collection + fc1.merge(fc1) + assert len(fc1.features) == 1 + + # add a new feature to the feature collection + fc1.merge(fc2) + assert len(fc1.features) == 2 + + self.check_feature(fc1.features[0]) + self.check_feature(fc1.features[1], expected_name='Aegean Sea') + + def test_add_tag(self): + """ + Test adding a tag to the features in a collection + """ + fc = self.read_feature(region='Adriatic_Sea') + + fc.tag(tags=['tag1', 'tag2', 'Mediterranean_Basin']) + assert (fc.features[0]['properties']['tags'] == + 'Adriatic_Sea;Mediterranean_Basin;tag1;tag2') + + self.check_feature(fc.features[0]) + + def test_remove_tag(self): + """ + Test removing a tag from the features in a collection + """ + fc = self.read_feature(region='Adriatic_Sea') + + fc.tag(tags=['Mediterranean_Basin', 'tag1'], remove=True) + assert (fc.features[0]['properties']['tags'] == 'Adriatic_Sea') + + self.check_feature(fc.features[0]) + + def test_set_group_name(self, componentName='ocean', objectType='region', + featureName='Celtic Sea', + groupName='testGroupName'): + """ + Write example file to test groupName functionality. + + Parameters + ---------- + componentName : str, optional + The component from which to retrieve the feature + + objectType : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + + featureName : str, optional + The name of a geometric feature to read + + groupName : str, optional + The group name to assign + """ + # Authors + # ------- + # Phillip J. Wolfram + # Xylar Asay-Davis + + # verification that groupName is in file + def verify_groupName(destfile, groupName): + with open(destfile) as f: + filevals = json.load(f) + assert 'groupName' in filevals, \ + 'groupName does not exist in {}'.format(destfile) + assert filevals['groupName'] == groupName, \ + 'Incorrect groupName of {} specified instead of ' \ + '{}.'.format(filevals['groupName'], groupName) + + gf = GeometricFeatures() + fc = gf.read(componentName, objectType, [featureName]) + fc.set_group_name(groupName) + assert fc.otherProperties['groupName'] == groupName, \ + 'groupName not assigned to FeatureCollection' + destfile = str(self.datadir.join('test.geojson')) + fc.to_geojson(destfile) + verify_groupName(destfile, groupName) + + def test_combine(self): + """ + Test combining the features in a collection into a single feature + """ + fc1 = self.read_feature() + fc2 = self.read_feature('Aegean_Sea') + fc1.merge(fc2) + name = 'Weird Disjoint Regions' + combined = fc1.combine(name) + assert len(combined.features) == 1 + self.check_feature(combined.features[0], expected_name=name, + expected_type='MultiPolygon') + + def test_difference(self): + """ + Test removing a mask feature from another feature + """ + fc = self.read_feature('Global_Ocean') + mask = self.read_feature() + difference = fc.difference(maskingFC=mask) + assert len(difference.features) == 1 + self.check_feature(difference.features[0], + expected_name='Global Ocean', + expected_type='Polygon') + + # make sure the original global ocean and mask have no holes + for fc_test in [fc, mask]: + geom = fc_test.features[0]['geometry'] + shape = shapely.geometry.shape(geom) + assert isinstance(shape, shapely.geometry.Polygon) + assert len(shape.interiors) == 0 + + geom = difference.features[0]['geometry'] + shape = shapely.geometry.shape(geom) + assert isinstance(shape, shapely.geometry.Polygon) + assert len(shape.interiors) == 1 + + def test_fix_antimeridian(self): + """ + Test splitting a feature that crosses the antimeridian (date line) into + multiple polygons + """ + globe = self.read_feature('Global_Ocean') + fc = FeatureCollection() + name = 'Antarctic Box' + feature = { + 'type': 'Feature', + 'properties': { + 'name': name, + 'tags': '', + 'object': 'region', + 'component': 'ocean', + 'author': 'Xylar Asay-Davis' + }, + 'geometry': { + 'type': 'Polygon', + 'coordinates': [ + [ + [ + 190.000000, + -70.000000 + ], + [ + 190.000000, + -90.000000 + ], + [ + 180.000000, + -90.000000 + ], + [ + 170.000000, + -90.000000 + ], + [ + 170.000000, + -70.000000 + ], + [ + 180.000000, + -70.000000 + ], + [ + 190.000000, + -70.000000 + ] + ] + ] + } + } + fc.add_feature(feature=feature) + self.check_feature(fc.features[0], + expected_name=name, + expected_type='Polygon') + fixed = fc.fix_antimeridian() + self.check_feature(fixed.features[0], + expected_name=name, + expected_type='MultiPolygon') + + geom = globe.features[0]['geometry'] + globe_shape = shapely.geometry.shape(geom) + + geom = fixed.features[0]['geometry'] + shape = shapely.geometry.shape(geom) + assert isinstance(shape, shapely.geometry.MultiPolygon) + # make sure the fixed polygons are within -180 to 180 deg. lon. + assert shapely.covers(globe_shape, shape) + + def test_simplify(self): + """ + Test simplifying a feature to remove redundant points + """ + name = 'Antarctic Box' + feature = { + 'type': 'Feature', + 'properties': { + 'name': name, + 'tags': '', + 'object': 'region', + 'component': 'ocean', + 'author': 'Xylar Asay-Davis' + }, + 'geometry': { + 'type': 'Polygon', + 'coordinates': [ + [ + [ + 90.000000, + -70.000000 + ], + [ + 90.000000, + -80.000000 + ], + [ + 90.000000, + -80.000000 + ], + [ + 70.000000, + -80.000000 + ], + [ + 70.000000, + -70.000000 + ], + [ + 90.000000, + -70.000000 + ], + [ + 90.000000, + -70.000000 + ] + ] + ] + } + } + fc = FeatureCollection() + fc.add_feature(feature=feature) + self.check_feature(fc.features[0], + expected_name=name, + expected_type='Polygon') + + # verify that the original shape has 7 coordinates (with 2 redundant + # points) + geom = fc.features[0]['geometry'] + orig_shape = shapely.geometry.shape(geom) + assert len(orig_shape.exterior.coords) == 7 + + simplified = fc.simplify(tolerance=0.0) + + # verify that the simplified shape has 5 coordinates (with the 2 + # redundant points removed) + geom = simplified.features[0]['geometry'] + simplified_shape = shapely.geometry.shape(geom) + assert len(simplified_shape.exterior.coords) == 5 + + def test_feature_in_collection(self): + """ + Test whether a given feature is in a feature collection + """ + fc1 = self.read_feature() + fc2 = self.read_feature('Aegean_Sea') + + feature = fc1.features[0] + assert fc1.feature_in_collection(feature) + + feature = fc2.features[0] + assert not fc1.feature_in_collection(feature) + + def test_to_geojson(self): + """ + Test writing a feature to a geojson file and reading it back + """ + fc = self.read_feature() + dest_filename = str(self.datadir.join('test.geojson')) + fc.to_geojson(dest_filename) + fc_check = read_feature_collection(dest_filename) + self.check_feature(fc_check.features[0]) + + def test_plot(self): + fc = self.read_feature() + + colors = ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', + '#f0027f', '#bf5b17'] + + projection = 'cyl' + + fig = fc.plot(projection, maxLength=4.0, figsize=(12,12), + colors=colors, dpi=200) + + dest_filename = str(self.datadir.join('plot.png')) + fig.savefig(dest_filename) diff --git a/geometric_features/test/test_set_group_name.py b/geometric_features/test/test_set_group_name.py deleted file mode 100644 index 2a2429fc..00000000 --- a/geometric_features/test/test_set_group_name.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest -import json - -from geometric_features.test import TestCase, loaddatadir -from geometric_features import GeometricFeatures - - -@pytest.mark.usefixtures("loaddatadir") -class TestSetGroupName(TestCase): - - def test_assign_groupname(self, componentName='ocean', objectType='region', - featureName='Celtic Sea', - groupName='testGroupName'): - """ - Write example file to test groupName functionality. - - Parameters - ---------- - componentName : {'bedmachine', 'bedmap2', 'iceshelves', 'landice', 'natural_earth', 'ocean'}, optional - The component from which to retieve the feature - - objectType : {'point', 'transect', 'region'}, optional - The type of geometry to load, a point (0D), transect (1D) or region - (2D) - - featureName : str, optional - The names of geometric feature to read - - groupName : str, optional - The group name to assign - """ - # Authors - # ------- - # Phillip J. Wolfram - # Xylar Asay-Davis - - # verification that groupName is in file - def verify_groupName(destfile, groupName): - with open(destfile) as f: - filevals = json.load(f) - assert 'groupName' in filevals, \ - 'groupName does not exist in {}'.format(destfile) - assert filevals['groupName'] == groupName, \ - 'Incorrect groupName of {} specified instead of ' \ - '{}.'.format(filevals['groupName'], groupName) - - # test via shell script - gf = GeometricFeatures(cacheLocation=str(self.datadir), - remoteBranchOrTag='main') - fc = gf.read(componentName, objectType, [featureName]) - fc.set_group_name(groupName) - assert fc.otherProperties['groupName'] == groupName, \ - 'groupName not assigned to FeatureCollection' - destfile = str(self.datadir.join('test.geojson')) - fc.to_geojson(destfile) - verify_groupName(destfile, groupName) From 83da503386207326d5a5d0b2bae6ddd311569830 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 Dec 2022 07:35:29 +0100 Subject: [PATCH 06/11] Add tests for all GeometricFeatures methods --- .../test/test_geometric_features.py | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 geometric_features/test/test_geometric_features.py diff --git a/geometric_features/test/test_geometric_features.py b/geometric_features/test/test_geometric_features.py new file mode 100644 index 00000000..a5cec2a5 --- /dev/null +++ b/geometric_features/test/test_geometric_features.py @@ -0,0 +1,141 @@ +import os +import pytest + +from geometric_features.test import TestCase, loaddatadir +from geometric_features import GeometricFeatures + + +@pytest.mark.usefixtures('loaddatadir') +class TestGeometricFeatures(TestCase): + + @staticmethod + def check_feature(feature, expected_name='Celtic Sea', + expected_component='ocean', + expected_type='region'): + """ + Check some properties of the feature + + Parameters + ---------- + feature : dict + A geojson feature to check + + expected_name : str + The expected name of the feature + + expected_component : str, optional + The component from which to retrieve the feature + + expected_type : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + """ + assert feature['properties']['name'] == expected_name + assert feature['properties']['component'] == expected_component + assert feature['properties']['object'] == expected_type + + def test_read_by_name(self, component='ocean', object_type='region', + feature='Celtic Sea'): + """ + Read an example feature by name and test for a few expected + attributes. + + Parameters + ---------- + component : str, optional + The component from which to retrieve the feature + + object_type : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + + feature : str, optional + The name of a geometric feature to read + """ + gf = GeometricFeatures() + fc = gf.read(componentName=component, objectType=object_type, + featureNames=[feature]) + self.check_feature(fc.features[0], expected_name=feature, + expected_component=component, + expected_type=object_type) + + def test_read_by_tag(self, component='ocean', object_type='region', + tag='Adriatic_Sea'): + """ + Read an example feature by name and test for a few expected + attributes. + + Parameters + ---------- + component : str, optional + The component from which to retrieve the feature + + object_type : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + + tag : str, optional + The name of a tag to read + """ + gf = GeometricFeatures() + fc = gf.read(componentName=component, objectType=object_type, + tags=[tag]) + self.check_feature(fc.features[0], expected_name='Adriatic Sea', + expected_component=component, + expected_type=object_type) + + def test_read_all_tag(self, component='ocean', object_type='region', + tags=('Adriatic_Sea', 'Mediterranean_Basin')): + """ + Read an example feature by name and test for a few expected + attributes. + + Parameters + ---------- + component : str, optional + The component from which to retrieve the feature + + object_type : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + + tags : list of str, optional + The names of tags to read + """ + gf = GeometricFeatures() + fc = gf.read(componentName=component, objectType=object_type, + tags=tags, allTags=True) + assert len(fc.features) == 1 + self.check_feature(fc.features[0], expected_name='Adriatic Sea', + expected_component=component, + expected_type=object_type) + + def test_split(self, component='ocean', object_type='region', + tag='Mediterranean_Basin'): + """ + Read an example feature by name and test for a few expected + attributes. + + Parameters + ---------- + component : str, optional + The component from which to retrieve the feature + + object_type : {'point', 'transect', 'region'}, optional + The type of geometry to load, a point (0D), transect (1D) or region + (2D) + + tag : str, optional + The name of a tag to read + """ + gf = GeometricFeatures() + fc = gf.read(componentName=component, objectType=object_type, + tags=[tag]) + + gf.split(fc, destinationDir=self.datadir) + + for feature in fc.features: + name = feature['properties']['name'] + subdir = name.replace(' ', '_') + path = f'{self.datadir}/{component}/{object_type}/{subdir}/{object_type}.geojson' + assert os.path.exists(path) From 78cffb2e08b7bca5025fa8ce0d8ea0e2da393f6d Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 Dec 2022 07:49:17 +0100 Subject: [PATCH 07/11] Add tests for all aggregation functions --- geometric_features/test/test_aggregation.py | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 geometric_features/test/test_aggregation.py diff --git a/geometric_features/test/test_aggregation.py b/geometric_features/test/test_aggregation.py new file mode 100644 index 00000000..5886dc12 --- /dev/null +++ b/geometric_features/test/test_aggregation.py @@ -0,0 +1,62 @@ +import pytest + +from geometric_features.test import TestCase, loaddatadir +from geometric_features import GeometricFeatures +from geometric_features.aggregation import get_aggregator_by_name, basins, \ + subbasins, antarctic, ice_shelves, ismip6, arctic_ocean, transport, \ + arctic_transport, moc, arctic_seaice + + +@pytest.mark.usefixtures('loaddatadir') +class TestAggregation(TestCase): + + def test_get_aggregator_by_name(self): + gf = GeometricFeatures() + names = ['Antarctic Regions', 'Arctic Ocean Regions', + 'Arctic Sea Ice Regions', 'Ocean Basins', 'Ice Shelves', + 'Ocean Subbasins', 'ISMIP6 Regions', 'MOC Basins', + 'Transport Transects', 'Arctic Transport Transects'] + + for name in names: + function, prefix, date = get_aggregator_by_name(name) + function(gf) + + def test_antarctic(self): + gf = GeometricFeatures() + antarctic(gf) + + def test_arctic_ocean(self): + gf = GeometricFeatures() + arctic_ocean(gf) + + def test_arctic_seaice(self): + gf = GeometricFeatures() + arctic_seaice(gf) + + def test_basins(self): + gf = GeometricFeatures() + basins(gf) + + def test_ice_shelves(self): + gf = GeometricFeatures() + ice_shelves(gf) + + def test_subbasins(self): + gf = GeometricFeatures() + subbasins(gf) + + def test_ismip6(self): + gf = GeometricFeatures() + ismip6(gf) + + def test_moc(self): + gf = GeometricFeatures() + moc(gf) + + def test_transport(self): + gf = GeometricFeatures() + transport(gf) + + def test_arctic_transport(self): + gf = GeometricFeatures() + arctic_transport(gf) From bdbd9864892217eb25d8d04556ce9ddf66ce2abd Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 17:13:29 +0100 Subject: [PATCH 08/11] Require shapely 2.x --- dev-spec.txt | 2 +- recipe/meta.yaml | 2 +- setup.py | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dev-spec.txt b/dev-spec.txt index 49cdc383..90f7f16d 100644 --- a/dev-spec.txt +++ b/dev-spec.txt @@ -8,7 +8,7 @@ matplotlib-base numpy progressbar2 requests -shapely +shapely>=2.0,<3.0 # Development pip diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 3a1d35d3..9710744f 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -34,7 +34,7 @@ requirements: - numpy - progressbar2 - requests - - shapely + - shapely >=2.0,<3.0 test: requires: diff --git a/setup.py b/setup.py index d0ff30c9..c6a49e73 100644 --- a/setup.py +++ b/setup.py @@ -35,8 +35,12 @@ packages=find_packages(include=['geometric_features', 'geometric_features.*']), package_data={'geometric_features': ['features_and_tags.json']}, - install_requires=['numpy', 'matplotlib', 'cartopy', 'shapely', - 'requests', 'progressbar2'], + install_requires=['numpy', + 'matplotlib', + 'cartopy', + 'shapely >=2.0,<3.0', + 'requests', + 'progressbar2'], entry_points={'console_scripts': ['combine_features = ' 'geometric_features.__main__:combine_features', From 6b1b347c792cdb6996999b21c7db3cfc86543aad Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Wed, 28 Dec 2022 16:58:47 +0100 Subject: [PATCH 09/11] Update example script for shapely 2.0 --- examples/setup_ocean_region_groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/setup_ocean_region_groups.py b/examples/setup_ocean_region_groups.py index 4f8ff001..50d18f37 100755 --- a/examples/setup_ocean_region_groups.py +++ b/examples/setup_ocean_region_groups.py @@ -207,7 +207,7 @@ def remove_small_polygons(fc, minArea): else: # a MultiPolygon outPolygons = [] - for polygon in featureShape: + for polygon in featureShape.geoms: if polygon.area > minArea: outPolygons.append(polygon) else: From 771b1920f888c1e1332d23afd5ef109be2141b43 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 Dec 2022 07:51:47 +0100 Subject: [PATCH 10/11] Update MOC basins aggregator for shapely 2.0 --- geometric_features/aggregation/ocean/moc_basins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometric_features/aggregation/ocean/moc_basins.py b/geometric_features/aggregation/ocean/moc_basins.py index 0c93fbb7..241dd26f 100644 --- a/geometric_features/aggregation/ocean/moc_basins.py +++ b/geometric_features/aggregation/ocean/moc_basins.py @@ -99,7 +99,7 @@ def _remove_small_polygons(fc, minArea): fcOut.add_feature(copy.deepcopy(feature)) else: featureShape = shapely.geometry.shape(geom) - if featureShape.type == 'Polygon': + if featureShape.geom_type == 'Polygon': if featureShape.area > minArea: add = True else: From 4267256196b5ac692731f4490fb20ac7a1eb644c Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Thu, 29 Dec 2022 08:06:00 +0100 Subject: [PATCH 11/11] Switch from pkg_resources to importlib.resources --- geometric_features/geometric_features.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/geometric_features/geometric_features.py b/geometric_features/geometric_features.py index 393aec6b..3b89fbf6 100644 --- a/geometric_features/geometric_features.py +++ b/geometric_features/geometric_features.py @@ -2,7 +2,7 @@ unicode_literals import json -import pkg_resources +import importlib.resources import os import geometric_features @@ -63,11 +63,10 @@ def __init__(self, cacheLocation=None, remoteBranchOrTag=None): else: self.remoteBranch = remoteBranchOrTag - featuresAndTagsFileName = pkg_resources.resource_filename( - 'geometric_features', 'features_and_tags.json') - - with open(featuresAndTagsFileName) as f: - self.allFeaturesAndTags = json.load(f) + features_file = (importlib.resources.files('geometric_features') / + 'features_and_tags.json') + with features_file.open('r') as file: + self.allFeaturesAndTags = json.load(file) def read(self, componentName, objectType, featureNames=None, tags=None, allTags=True):