cryoswath 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cryoswath/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from . import gis, misc, l1b, l2, l3, l4, test_plots
2
+
3
+
4
+ __all__ = ["misc", "gis", "l1b", "l2", "l3", "l4"
5
+ "test_plots", # subpackage
6
+ ]
cryoswath/gis.py ADDED
@@ -0,0 +1,167 @@
1
+ import geopandas as gpd
2
+ import numpy as np
3
+ import os
4
+ import pandas as pd
5
+ from pyproj import Transformer
6
+ from pyproj.crs import CRS
7
+ import rasterio
8
+ import shapely
9
+ import warnings
10
+
11
+ from .misc import *
12
+
13
+ __all__ = list()
14
+
15
+ # ! tbi:
16
+ rgi_o1_epsg_dict = dict()
17
+
18
+
19
+ def buffer_4326_shp(shp: shapely.Geometry, radius: float, simplify: bool = True) -> shapely.MultiPolygon:
20
+ # the algorithm splits a multi-geomerty like MultiPolygon into its
21
+ # parts, simplifies them if requested, buffers them, and joins them
22
+ # finally.
23
+ # the splitting is necessary to work around issue #13
24
+ if shp is None: # this will occasionally fail, as it will be 'GEOMETRYCOLLECTION EMPTY'
25
+ warnings.warn("shp=None passed to buffer_4326_shp, returning empty MultiPolygon.")
26
+ return shapely.MultiPolygon()
27
+ # # below does not take the geopandas detour. while more straight forward,
28
+ # # geopandas used to be more stable. however the below was improved and
29
+ # # may be equally good now.
30
+ # transformer = Transformer.from_crs("EPSG:4326", planar_crs)
31
+ # shp = shapely.ops.transform(transformer.invert().transform, shapely.ops.transform(transformer.transform, shp).simplify(100).buffer(radius)).make_valid()
32
+ if isinstance(shp, shapely.geometry.base.BaseMultipartGeometry):
33
+ shp = list(shp.geoms)
34
+ elif isinstance(shp, shapely.geometry.base.BaseGeometry):
35
+ # assuming single shaply geometry as it is not a collection
36
+ shp = [shp]
37
+ elif isinstance(shp, gpd.GeoDataFrame):
38
+ shp = shp.geometry.values
39
+ elif isinstance(shp, gpd.GeoSeries):
40
+ shp = shp.values
41
+ elif isinstance(shp, gpd.array.GeometryArray):
42
+ # nothing to do
43
+ pass
44
+ planar_crs = find_planar_crs(shp=shp[0])
45
+ buffered_planar = []
46
+ for poly in shp:
47
+ buffered_planar.append(
48
+ gpd.GeoSeries(poly, crs=4326).to_crs(planar_crs).make_valid().simplify(100).buffer(radius))
49
+ buffered_planar = pd.concat(buffered_planar)
50
+ if simplify:
51
+ buffered_planar = buffered_planar.simplify(radius/3)
52
+ buffered_planar = buffered_planar.to_crs(4326)
53
+ return shapely.make_valid(buffered_planar.union_all(method="unary"))
54
+ __all__.append("buffer_4326_shp")
55
+
56
+
57
+ def ensure_pyproj_crs(crs: CRS) -> CRS:
58
+ # Token function to convert (any?) CRS object to a pyproj.crs.CRS
59
+ # For now using from_epsg as it smells safest.
60
+ if not isinstance(crs, CRS):
61
+ try:
62
+ epsg = crs.to_epsg()
63
+ except AttributeError:
64
+ epsg = crs
65
+ crs = CRS.from_epsg(epsg)
66
+ return crs
67
+ __all__.append("ensure_pyproj_crs")
68
+
69
+
70
+ def esri_to_feather(file_path: str = None) -> None:
71
+ if file_path.split(os.path.extsep)[-1].lower() == "shp":
72
+ basename = os.path.extsep.join(file_path.split(os.path.extsep)[:-1])
73
+ gpd.read_file(file_path).to_feather(basename+os.path.extsep+"feather")
74
+ __all__.append("esri_to_feather")
75
+
76
+
77
+ def find_planar_crs(*, shp: shapely.Geometry = None, lat: float = None, lon: float = None, region_id: str = None):
78
+ if region_id is not None:
79
+ with warnings.catch_warnings(action="ignore"):
80
+ shp = load_glacier_outlines(region_id)
81
+ elif shp is None:
82
+ shp = shapely.MultiPoint([(lon, lat) for lon, lat in zip(lon, lat)])
83
+ shp = shp.centroid
84
+ if shp.y > 75:
85
+ return CRS.from_epsg(3413)
86
+ elif shp.y < -75:
87
+ return CRS.from_epsg(3976)
88
+ else:
89
+ return gpd.GeoSeries(shp, crs=4326).to_frame().estimate_utm_crs()
90
+ __all__.append("find_planar_crs")
91
+
92
+ def get_lon_origin(crs):
93
+ # Extract Longitude of origin
94
+ # May turn out not to be very robust.
95
+ return ensure_pyproj_crs(crs).coordinate_operation.params[1].value
96
+ __all__.append("get_lon_origin")
97
+
98
+
99
+ def get_4326_to_dem_Transformer(dem_reader: rasterio.DatasetReader) -> Transformer:
100
+ return Transformer.from_crs("EPSG:4326", ensure_pyproj_crs(dem_reader.crs))
101
+ __all__.append("get_4326_to_dem_Transformer")
102
+
103
+
104
+ def points_on_glacier(points: gpd.GeoSeries) -> pd.Index:
105
+ o2regions = gpd.read_feather(os.path.join(rgi_path, "RGI2000-v7.0-o2regions.feather"))
106
+ o2code = o2regions[o2regions.geometry.contains(shapely.box(*points.total_bounds))]["o2region"].values[0]
107
+ buffered_glaciered_area_polygon = load_o2region(o2code)
108
+ import time
109
+ print(time.time(), "building union")
110
+ union = buffered_glaciered_area_polygon.unary_union
111
+ print(time.time(), "building geoseries")
112
+ buffered_glaciered_area_polygon = gpd.GeoSeries(union,
113
+ # ! the buffering below works only for the arctic
114
+ crs=buffered_glaciered_area_polygon.crs)
115
+ print(time.time(), "buffering")
116
+ buffered_glaciered_area_polygon = buffered_glaciered_area_polygon.to_crs(3413).buffer(30_000)
117
+ print(time.time(), "simplifying")
118
+ buffered_glaciered_area_polygon = buffered_glaciered_area_polygon.simplify(1000).to_crs(4326)
119
+ return points[points.within(buffered_glaciered_area_polygon[0])].index
120
+ __all__.append("points_on_glacier")
121
+
122
+
123
+ def simplify_4326_shp(shp: shapely.Geometry, tolerance: float = None) -> shapely.Geometry:
124
+ if tolerance is None:
125
+ if shp.length >= 20_000: # 5 x 5 km
126
+ tolerance = 1000
127
+ else:
128
+ tolerance = 300
129
+ planar_crs = find_planar_crs(shp=shp)
130
+ # simplify can create holes outside of the polygon. this is fixed by buffer(0) or make_valid()
131
+ if isinstance(shp, shapely.Geometry):
132
+ shp = gpd.GeoSeries(shp, crs=4326)
133
+ return shp.to_crs(planar_crs).simplify(tolerance).to_crs(4326).make_valid().union_all(method="unary")
134
+ __all__.append("simplify_4326_shp")
135
+
136
+
137
+ def subdivide_region(basin_gdf: gpd.GeoDataFrame,
138
+ lat_bin_width_degree: float = 1,
139
+ lon_bin_width_degree: float = 1,
140
+ ) -> list[gpd.GeoDataFrame]:
141
+ """Devides GeoDataFrame of basins into smaller GeoDataFrame based on
142
+ their central lat/lon coords.
143
+
144
+ Args:
145
+ basin_gdf (gpd.GeoDataFrame, optional): Basins to subdivide.
146
+ lat_bin_width_degree (float, optional): Requested sub-region latitude
147
+ range in degrees. Defaults to 1.
148
+ lon_bin_width_degree (float, optional): Requested sub-region longitude
149
+ range in degrees.. Defaults to 1.
150
+
151
+ Returns:
152
+ list[gpd.GeoDataFrame]: List of region parts.
153
+ """
154
+ return_list = []
155
+ # cut latitude into degree slices
156
+ n_lat_bins = max(1, round((basin_gdf.cenlat.max()-basin_gdf.cenlat.min())/lat_bin_width_degree))
157
+ # below, `observed=True` to grant compatibility with future pandas versions.
158
+ for lat_label, lat_group in basin_gdf.groupby(pd.cut(basin_gdf.cenlat, bins=n_lat_bins), observed=True):
159
+ # similarly, cut longitude
160
+ n_lon_bins = max(1, round((lat_group.cenlon.max()-lat_group.cenlon.min())
161
+ / lon_bin_width_degree
162
+ * np.cos(np.deg2rad(lat_label.mid))))
163
+ for _, lon_group in lat_group.groupby(pd.cut(lat_group.cenlon, bins=n_lon_bins), observed=True):
164
+ return_list.append(lon_group)
165
+ return return_list
166
+ __all__.append("subdivide_region")
167
+