class TestVector: glacier_outlines = gu.Vector(GLACIER_OUTLINES_URL) def test_init(self): vector = gu.Vector(GLACIER_OUTLINES_URL) assert isinstance(vector, gu.Vector) def test_copy(self): vector2 = self.glacier_outlines.copy() assert vector2 is not self.glacier_outlines vector2.ds = vector2.ds.query("NAME == 'Ayerbreen'") assert vector2.ds.shape[0] < self.glacier_outlines.ds.shape[0] def test_query(self): vector2 = self.glacier_outlines.query("NAME == 'Ayerbreen'") assert vector2 is not self.glacier_outlines assert vector2.ds.shape[0] < self.glacier_outlines.ds.shape[0]
def test_merge_bounds(self) -> None: """ Check that merge_bounds and bounds2poly work as expected for all kinds of bounds objects. """ img1 = gu.Raster(gu.datasets.get_path("landsat_B4")) img2 = gu.Raster(gu.datasets.get_path("landsat_B4_crop")) # Check union (default) - with Raster objects out_bounds = pt.merge_bounds((img1, img2)) assert out_bounds[0] == min(img1.bounds.left, img2.bounds.left) assert out_bounds[1] == min(img1.bounds.bottom, img2.bounds.bottom) assert out_bounds[2] == max(img1.bounds.right, img2.bounds.right) assert out_bounds[3] == max(img1.bounds.top, img2.bounds.top) # Check intersection - with Raster objects out_bounds = pt.merge_bounds((img1, img2), merging_algorithm="intersection") assert out_bounds[0] == max(img1.bounds.left, img2.bounds.left) assert out_bounds[1] == max(img1.bounds.bottom, img2.bounds.bottom) assert out_bounds[2] == min(img1.bounds.right, img2.bounds.right) assert out_bounds[3] == min(img1.bounds.top, img2.bounds.top) # Check that the results is the same with rio.BoundingBoxes out_bounds2 = pt.merge_bounds((img1.bounds, img2.bounds), merging_algorithm="intersection") assert out_bounds2 == out_bounds # Check that the results is the same with a list out_bounds2 = pt.merge_bounds((list(img1.bounds), list(img2.bounds)), merging_algorithm="intersection") assert out_bounds2 == out_bounds # Check with gpd.GeoDataFrame outlines = gu.Vector(gu.datasets.get_path("glacier_outlines")) outlines = gu.Vector(outlines.ds.to_crs( img1.crs)) # reproject to img1's CRS out_bounds = pt.merge_bounds((img1, outlines.ds)) assert out_bounds[0] == min(img1.bounds.left, outlines.ds.total_bounds[0]) assert out_bounds[1] == min(img1.bounds.bottom, outlines.ds.total_bounds[1]) assert out_bounds[2] == max(img1.bounds.right, outlines.ds.total_bounds[2]) assert out_bounds[3] == max(img1.bounds.top, outlines.ds.total_bounds[3])
def test_crop(self) -> None: r = gr.Raster(datasets.get_path("landsat_B4")) r2 = gr.Raster(datasets.get_path("landsat_B4_crop")) # Read a vector and extract only the largest outline within the extent of r outlines = gu.Vector(datasets.get_path("glacier_outlines")) outlines.ds = outlines.ds.to_crs(r.crs) outlines.crop2raster(r) outlines = outlines.query( f"index == {np.argmax(outlines.ds.geometry.area)}") # Crop the raster to the outline and validate that it got smaller r_outline_cropped = r.crop(outlines, inplace=False) assert r.data.size > r_outline_cropped.data.size # type: ignore b = r.bounds b2 = r2.bounds b_minmax = (max(b[0], b2[0]), max(b[1], b2[1]), min(b[2], b2[2]), min(b[3], b2[3])) r_init = r.copy() # Cropping overwrites the current Raster object r.crop(r2) b_crop = tuple(r.bounds) if DO_PLOT: fig1, ax1 = plt.subplots() r_init.show(ax=ax1, title="Raster 1") fig2, ax2 = plt.subplots() r2.show(ax=ax2, title="Raster 2") fig3, ax3 = plt.subplots() r.show(ax=ax3, title="Raster 1 cropped to Raster 2") plt.show() assert b_minmax == b_crop
class TestVector: glacier_outlines = gu.Vector(GLACIER_OUTLINES_URL) def test_init(self) -> None: vector = gu.Vector(GLACIER_OUTLINES_URL) assert isinstance(vector, gu.Vector) def test_copy(self) -> None: vector2 = self.glacier_outlines.copy() assert vector2 is not self.glacier_outlines vector2.ds = vector2.ds.query("NAME == 'Ayerbreen'") assert vector2.ds.shape[0] < self.glacier_outlines.ds.shape[0] def test_query(self) -> None: vector2 = self.glacier_outlines.query("NAME == 'Ayerbreen'") assert vector2 is not self.glacier_outlines assert vector2.ds.shape[0] < self.glacier_outlines.ds.shape[0] def test_bounds(self) -> None: bounds = self.glacier_outlines.bounds assert bounds.left < bounds.right assert bounds.bottom < bounds.top assert bounds.left == self.glacier_outlines.ds.total_bounds[0] assert bounds.bottom == self.glacier_outlines.ds.total_bounds[1] assert bounds.right == self.glacier_outlines.ds.total_bounds[2] assert bounds.top == self.glacier_outlines.ds.total_bounds[3]
"""Plot an example of spatial interpolation of randomly generated errors.""" import geoutils as gu import matplotlib.pyplot as plt import numpy as np import xdem dem_2009 = xdem.DEM(xdem.examples.get_path("longyearbyen_ref_dem")) dem_1990 = xdem.DEM(xdem.examples.get_path("longyearbyen_tba_dem")) outlines_1990 = gu.Vector( xdem.examples.get_path("longyearbyen_glacier_outlines")) ddem = xdem.dDEM(dem_2009 - dem_1990, start_time=np.datetime64("1990-08-01"), end_time=np.datetime64("2009-08-01")) # The example DEMs are void-free, so let's make some random voids. ddem.data.mask = np.zeros_like(ddem.data, dtype=bool) # Reset the mask # Introduce 50000 nans randomly throughout the dDEM. ddem.data.mask.ravel()[np.random.choice(ddem.data.size, 50000, replace=False)] = True ddem.interpolate(method="linear") ylim = (300, 100) xlim = (800, 1050) plt.figure(figsize=(8, 5)) plt.subplot(121) plt.imshow(ddem.data.squeeze(), cmap="coolwarm_r", vmin=-50, vmax=50) plt.ylim(ylim) plt.xlim(xlim)
def test_read_paths_vector(test_dataset: str) -> None: assert isinstance(gu.Vector(datasets.get_path(test_dataset)), gu.Vector)
def test_init(self): timestamps = [ datetime.datetime(1990, 8, 1), datetime.datetime(2009, 8, 1), datetime.datetime(2060, 8, 1) ] scott_1990 = gu.Vector(self.outlines_1990.ds.loc[ self.outlines_1990.ds["NAME"] == "Scott Turnerbreen"]) scott_2010 = gu.Vector(self.outlines_2010.ds.loc[ self.outlines_2010.ds["NAME"] == "Scott Turnerbreen"]) # Make sure the glacier was bigger in 1990, since this is assumed later. assert scott_1990.ds.area.sum() > scott_2010.ds.area.sum() mask_2010 = scott_2010.create_mask(self.dem_2009).reshape( self.dem_2009.data.shape) dem_2060 = self.dem_2009.copy() dem_2060.data[mask_2010] -= 30 dems = xdem.DEMCollection( [self.dem_1990, self.dem_2009, dem_2060], timestamps=timestamps, outlines=dict( zip(timestamps[:2], [self.outlines_1990, self.outlines_2010])), reference_dem=1) # Check that the first raster is the oldest one and assert dems.dems[0].data.max() == self.dem_1990.data.max() assert dems.reference_dem.data.max() == self.dem_2009.data.max() dems.subtract_dems(resampling_method="nearest") assert np.mean(dems.ddems[0].data) < 0 scott_filter = "NAME == 'Scott Turnerbreen'" dh_series = dems.get_dh_series(outlines_filter=scott_filter) # The 1990-2009 area should be the union of those years. The 2009-2060 area should just be the 2010 area. assert dh_series.iloc[0]["area"] > dh_series.iloc[-1]["area"] cumulative_dh = dems.get_cumulative_series( kind="dh", outlines_filter=scott_filter) cumulative_dv = dems.get_cumulative_series( kind="dv", outlines_filter=scott_filter) # Simple check that the cumulative_dh is overall negative. assert cumulative_dh.iloc[0] > cumulative_dh.iloc[-1] # Simple check that the dV number is of a greater magnitude than the dH number. assert abs(cumulative_dv.iloc[-1]) > abs(cumulative_dh.iloc[-1]) # Generate 10000 NaN values randomly in one of the dDEMs dems.ddems[0].data[ np.random.randint(0, dems.ddems[0].data.shape[0], 100), np.random.randint(0, dems.ddems[0].data.shape[1], 100)] = np.nan # Check that the cumulative_dh function warns for NaNs with warnings.catch_warnings(): warnings.simplefilter("error") try: dems.get_cumulative_series(nans_ok=False) except UserWarning as exception: if "NaNs found in dDEM" not in str(exception): raise exception
class TestDEMCollection: dem_2009 = xdem.DEM(xdem.examples.get_path("longyearbyen_ref_dem")) dem_1990 = xdem.DEM(xdem.examples.get_path("longyearbyen_tba_dem")) outlines_1990 = gu.Vector( xdem.examples.get_path("longyearbyen_glacier_outlines")) outlines_2010 = gu.Vector( xdem.examples.get_path("longyearbyen_glacier_outlines_2010")) def test_init(self): timestamps = [ datetime.datetime(1990, 8, 1), datetime.datetime(2009, 8, 1), datetime.datetime(2060, 8, 1) ] scott_1990 = gu.Vector(self.outlines_1990.ds.loc[ self.outlines_1990.ds["NAME"] == "Scott Turnerbreen"]) scott_2010 = gu.Vector(self.outlines_2010.ds.loc[ self.outlines_2010.ds["NAME"] == "Scott Turnerbreen"]) # Make sure the glacier was bigger in 1990, since this is assumed later. assert scott_1990.ds.area.sum() > scott_2010.ds.area.sum() mask_2010 = scott_2010.create_mask(self.dem_2009).reshape( self.dem_2009.data.shape) dem_2060 = self.dem_2009.copy() dem_2060.data[mask_2010] -= 30 dems = xdem.DEMCollection( [self.dem_1990, self.dem_2009, dem_2060], timestamps=timestamps, outlines=dict( zip(timestamps[:2], [self.outlines_1990, self.outlines_2010])), reference_dem=1) # Check that the first raster is the oldest one and assert dems.dems[0].data.max() == self.dem_1990.data.max() assert dems.reference_dem.data.max() == self.dem_2009.data.max() dems.subtract_dems(resampling_method="nearest") assert np.mean(dems.ddems[0].data) < 0 scott_filter = "NAME == 'Scott Turnerbreen'" dh_series = dems.get_dh_series(outlines_filter=scott_filter) # The 1990-2009 area should be the union of those years. The 2009-2060 area should just be the 2010 area. assert dh_series.iloc[0]["area"] > dh_series.iloc[-1]["area"] cumulative_dh = dems.get_cumulative_series( kind="dh", outlines_filter=scott_filter) cumulative_dv = dems.get_cumulative_series( kind="dv", outlines_filter=scott_filter) # Simple check that the cumulative_dh is overall negative. assert cumulative_dh.iloc[0] > cumulative_dh.iloc[-1] # Simple check that the dV number is of a greater magnitude than the dH number. assert abs(cumulative_dv.iloc[-1]) > abs(cumulative_dh.iloc[-1]) # Generate 10000 NaN values randomly in one of the dDEMs dems.ddems[0].data[ np.random.randint(0, dems.ddems[0].data.shape[0], 100), np.random.randint(0, dems.ddems[0].data.shape[1], 100)] = np.nan # Check that the cumulative_dh function warns for NaNs with warnings.catch_warnings(): warnings.simplefilter("error") try: dems.get_cumulative_series(nans_ok=False) except UserWarning as exception: if "NaNs found in dDEM" not in str(exception): raise exception # print(cumulative_dh) #raise NotImplementedError def test_dem_datetimes(self): """Try to create the DEMCollection without the timestamps argument (instead relying on datetime attributes).""" self.dem_1990.datetime = datetime.datetime(1990, 8, 1) self.dem_2009.datetime = datetime.datetime(2009, 8, 1) dems = xdem.DEMCollection([self.dem_1990, self.dem_2009]) assert len(dems.timestamps) > 0 def test_ddem_interpolation(self): """Test that dDEM interpolation works as it should.""" # All warnings should raise errors from now on warnings.simplefilter("error") # Create a DEMCollection object dems = xdem.DEMCollection([self.dem_2009, self.dem_1990], timestamps=[ datetime.datetime(year, 8, 1) for year in (2009, 1990) ]) # Create dDEMs dems.subtract_dems(resampling_method="nearest") # The example data does not have NaNs, so filled_data should exist. assert dems.ddems[0].filled_data is not None # Try to set the filled_data property with an invalid size. try: dems.ddems[0].filled_data = np.zeros(3) except AssertionError as exception: if "differs from the data shape" not in str(exception): raise exception # Generate 10000 NaN values randomly in one of the dDEMs dems.ddems[0].data[ np.random.randint(0, dems.ddems[0].data.shape[0], 100), np.random.randint(0, dems.ddems[0].data.shape[1], 100)] = np.nan # Make sure that filled_data is not available anymore, since the data now has nans assert dems.ddems[0].filled_data is None # Interpolate the nans dems.ddems[0].interpolate(method="linear") # Make sure that the filled_data is available again assert dems.ddems[0].filled_data is not None
class TestdDEM: dem_2009 = xdem.DEM(xdem.examples.get_path("longyearbyen_ref_dem")) dem_1990 = xdem.DEM(xdem.examples.get_path("longyearbyen_tba_dem")) outlines_1990 = gu.Vector(xdem.examples.get_path("longyearbyen_glacier_outlines")) ddem = xdem.dDEM( dem_2009 - dem_1990, start_time=np.datetime64("1990-08-01"), end_time=np.datetime64("2009-08-01") ) def test_init(self): """Test that the dDEM object was instantiated correctly.""" assert isinstance(self.ddem, xdem.dDEM) assert isinstance(self.ddem.data, np.ma.masked_array) def test_copy(self): """Test that copying works as it should.""" ddem2 = self.ddem.copy() assert isinstance(ddem2, xdem.dDEM) ddem2.data += 1 assert self.ddem != ddem2 def test_filled_data(self): """Test that the filled_data property points to the right data.""" ddem2 = self.ddem.copy() assert not np.any(np.isnan(ddem2.data)) or np.all(~ddem2.data.mask) assert ddem2.filled_data is not None assert np.count_nonzero(np.isnan(ddem2.data)) == 0 ddem2.data.ravel()[0] = np.nan assert np.count_nonzero(np.isnan(ddem2.data)) == 1 assert ddem2.filled_data is None ddem2.interpolate(method="linear") assert ddem2.fill_method is not None def test_regional_hypso(self): """Test the regional hypsometric approach.""" ddem = self.ddem.copy() ddem.data.mask = np.zeros_like(ddem.data, dtype=bool) ddem.data.mask.ravel()[np.random.choice(ddem.data.size, 50000, replace=False)] = True assert np.count_nonzero(ddem.data.mask) > 0 assert ddem.filled_data is None ddem.interpolate( method="regional_hypsometric", reference_elevation=self.dem_2009, mask=self.outlines_1990 ) assert ddem._filled_data is not None assert type(ddem.filled_data) == np.ndarray assert ddem.filled_data.shape == ddem.data.shape assert np.abs(np.nanmean(self.ddem.data - ddem.filled_data)) < 1 def test_local_hypso(self): """Test the local hypsometric approach.""" ddem = self.ddem.copy() scott_1990 = self.outlines_1990.query("NAME == 'Scott Turnerbreen'") ddem.data.mask = np.zeros_like(ddem.data, dtype=bool) ddem.data.mask.ravel()[np.random.choice(ddem.data.size, 50000, replace=False)] = True assert np.count_nonzero(ddem.data.mask) > 0 assert ddem.filled_data is None ddem.interpolate( method="local_hypsometric", reference_elevation=self.dem_2009.data, mask=self.outlines_1990 ) assert np.abs(np.mean(self.ddem.data - ddem.filled_data)) < 1
and quality of stereo-correlation (Equation 1, Extended Data Fig. 3a). """ # sphinx_gallery_thumbnail_number = 4 import matplotlib.pyplot as plt import numpy as np import xdem import geoutils as gu # %% # We start by estimating the non-stationarities and deriving a terrain-dependent measurement error as a function of both # slope and maximum curvature, as shown in the :ref:`sphx_glr_auto_examples_plot_nonstationary_error.py` example. # Load the data ref_dem = xdem.DEM(xdem.examples.get_path("longyearbyen_ref_dem")) dh = xdem.DEM(xdem.examples.get_path("longyearbyen_ddem")) glacier_outlines = gu.Vector(xdem.examples.get_path("longyearbyen_glacier_outlines")) mask_glacier = glacier_outlines.create_mask(dh) # Compute the slope and maximum curvature slope, planc, profc = \ xdem.terrain.get_terrain_attribute(dem=ref_dem.data, attribute=['slope', 'planform_curvature', 'profile_curvature'], resolution=ref_dem.res) # Remove values on unstable terrain dh_arr = dh.data[~mask_glacier] slope_arr = slope[~mask_glacier] planc_arr = planc[~mask_glacier] profc_arr = profc[~mask_glacier] maxc_arr = np.maximum(np.abs(planc_arr),np.abs(profc_arr))
def interpolate(self, method: str = "linear", reference_elevation: Optional[Union[np.ndarray, np.ma.masked_array, xdem.DEM]] = None, mask: Optional[Union[np.ndarray, xdem.DEM, gu.Vector]] = None): """ Interpolate the dDEM using the given method. :param method: The method to use for interpolation. :param reference_elevation: Reference DEM. Only required for hypsometric approaches. """ if reference_elevation is not None: try: reference_elevation = reference_elevation.reproject( self, silent=True) # type: ignore except AttributeError as exception: if "object has no attribute 'reproject'" not in str(exception): raise exception if isinstance(reference_elevation, np.ndarray): reference_elevation = np.ma.masked_array( reference_elevation, mask=np.isnan(reference_elevation)) assert reference_elevation.data.shape == self.data.shape, ( f"'reference_elevation' shape ({reference_elevation.data.shape})" f" different from 'self' ({self.data.shape})") if method == "linear": self.filled_data = xdem.volume.linear_interpolation(self.data) elif method == "local_hypsometric": assert reference_elevation is not None assert mask is not None if not isinstance(mask, gu.Vector): mask = gu.Vector(mask) interpolated_ddem, nans = xdem.spatial_tools.get_array_and_mask( self.data.copy()) entries = mask.ds[mask.ds.intersects( shapely.geometry.box(*self.bounds))] ddem_mask = nans.copy().squeeze() for i in entries.index: feature_mask = (gu.Vector( entries.loc[entries.index == i]).create_mask(self) ).reshape(interpolated_ddem.shape) if np.count_nonzero(feature_mask) == 0: continue try: with warnings.catch_warnings(): warnings.filterwarnings("ignore", "Not enough valid bins") interpolated_ddem = np.asarray( xdem.volume.hypsometric_interpolation( interpolated_ddem, reference_elevation.data, mask=feature_mask)) except ValueError as exception: # Skip the feature if too few glacier values exist. if "x and y arrays must have at least 2 entries" in str( exception): continue raise exception # Set the validity flag of all values within the feature to be valid ddem_mask[feature_mask] = False # All values that were nan in the start and are without the updated validity mask should now be nan # The above interpolates values outside of the dDEM, so this is necessary. interpolated_ddem[ddem_mask] = np.nan diff = abs(np.nanmean(interpolated_ddem - self.data)) assert diff < 0.01, (diff, self.data.mean()) self.filled_data = xdem.volume.linear_interpolation( interpolated_ddem) elif method == "regional_hypsometric": assert reference_elevation is not None assert mask is not None mask_array = xdem.coreg.mask_as_array(self, mask).reshape( self.data.shape) self.filled_data = xdem.volume.hypsometric_interpolation( self.data, reference_elevation.data, mask=mask_array).data else: raise NotImplementedError( f"Interpolation method '{method}' not supported") return self.filled_data
def test_read_paths_vector(test_dataset: str) -> None: warnings.simplefilter("error") assert isinstance(gu.Vector(datasets.get_path(test_dataset)), gu.Vector)
def test_init(self) -> None: vector = gu.Vector(GLACIER_OUTLINES_URL) assert isinstance(vector, gu.Vector)
"""Example script to load a vector file.""" import warnings warnings.simplefilter( "ignore" ) # Temporarily filter warnings since something's up with GeoPandas (2021-05-20). import geoutils as gu filename = gu.datasets.get_path("glacier_outlines") outlines = gu.Vector(filename) # TEXT # Load an example Landsat image filename = gu.datasets.get_path("landsat_B4") image = gu.Raster(filename) # Generate a boolean mask from the glacier outlines. mask = outlines.create_mask(image)
""" Loading and understanding Vectors ================================= This is (right now) a dummy example for showing the functionality of :class:`geoutils.Vector`. """ import matplotlib.pyplot as plt import geoutils as gu # %% # Example raster: glaciers = gu.Vector(gu.datasets.get_path("glacier_outlines")) # %% # Info: print(glaciers) # %% # A plot: for _, glacier in glaciers.ds.iterrows(): plt.plot(*glacier.geometry.exterior.xy) plt.show()