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 +6 -0
- cryoswath/gis.py +167 -0
- cryoswath/l1b.py +905 -0
- cryoswath/l2.py +409 -0
- cryoswath/l3.py +641 -0
- cryoswath/l4.py +197 -0
- cryoswath/misc.py +1751 -0
- cryoswath/test_plots/__init__.py +2 -0
- cryoswath/test_plots/maps.py +41 -0
- cryoswath/test_plots/timeseries.py +55 -0
- cryoswath/test_plots/waveform.py +252 -0
- cryoswath-0.2.0.dist-info/LICENSE.txt +20 -0
- cryoswath-0.2.0.dist-info/METADATA +134 -0
- cryoswath-0.2.0.dist-info/RECORD +15 -0
- cryoswath-0.2.0.dist-info/WHEEL +4 -0
cryoswath/__init__.py
ADDED
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
|
+
|