Skip to content

Commit 37b1e5e

Browse files
tovogtpeanutfun
andauthored
util.coordinates: remove broken dist_to_coast function (#840)
* util.coordinates: remove dist_to_coast function * Update climada/hazard/centroids/centr.py Co-authored-by: Lukas Riedel <[email protected]> * u_coord: dist_to_coast as wrapper for dist_to_coast_nasa * Update climada/hazard/centroids/centr.py Fix trailing whitespace * Centroids.get_dist_coast: fix tests --------- Co-authored-by: Lukas Riedel <[email protected]>
1 parent beb7e75 commit 37b1e5e

File tree

5 files changed

+66
-178
lines changed

5 files changed

+66
-178
lines changed

climada/hazard/centroids/centr.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -659,27 +659,37 @@ def get_elevation(self, topo_path):
659659
"""
660660
return u_coord.read_raster_sample(topo_path, self.lat, self.lon)
661661

662-
def get_dist_coast(self, signed=False, precomputed=False):
662+
def get_dist_coast(self, signed=False, precomputed=True):
663663
"""Get dist_coast attribute for every pixel or point in meters.
664664
665+
The distances are read from a raster file containing precomputed distances (from NASA) at
666+
0.01 degree (approximately 1 km) resolution.
667+
665668
Parameters
666669
----------
667670
signed : bool, optional
668671
If True, use signed distances (positive off shore and negative on land). Default: False
669672
precomputed : bool, optional
670-
If True, use precomputed distances (from NASA). Default: False
673+
Whether distances should be read from a pre-computed raster (True) or computed
674+
on-the-fly (False). Default: True.
675+
676+
.. deprecated:: 5.0
677+
Argument is ignored, because distances are not computed on-the-fly anymore.
671678
672679
Returns
673680
-------
674681
dist : np.array
675682
(Signed) distance to coast in meters.
676683
"""
684+
if not precomputed:
685+
LOGGER.warning(
686+
"The `precomputed` argument is deprecated and will be removed in the future"
687+
" because `get_dist_coast` always uses precomputed distances."
688+
)
677689
ne_geom = self._ne_crs_geom()
678-
if precomputed:
679-
return u_coord.dist_to_coast_nasa(
680-
ne_geom.y.values, ne_geom.x.values, highres=True, signed=signed)
681-
LOGGER.debug('Computing distance to coast for %s centroids.', str(self.size))
682-
return u_coord.dist_to_coast(ne_geom, signed=signed)
690+
return u_coord.dist_to_coast_nasa(
691+
ne_geom.y.values, ne_geom.x.values, highres=True, signed=signed,
692+
)
683693

684694
def get_meta(self, resolution=None):
685695
"""Returns a meta raster based on the centroids bounds.

climada/hazard/centroids/test/test_centr.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def test_init_pass(self):
104104
self.assertTrue(u_coord.equal_crs(self.centr.crs, DEF_CRS))
105105

106106
# Creating Centroids with additional attributes
107-
centroids = Centroids(lat=VEC_LAT, lon=VEC_LON,
107+
centroids = Centroids(lat=VEC_LAT, lon=VEC_LON,
108108
region_id=REGION_ID, on_land=ON_LAND)
109109

110110
# Checking additional attributes
@@ -126,7 +126,7 @@ def test_init_properties(self):
126126
'on_land','region_id','crs',
127127
'shape','size','total_bounds','coord']
128128
centroids = Centroids(lat=[],lon=[])
129-
[self.assertTrue(hasattr(centroids,prop)) for prop in properties]
129+
[self.assertTrue(hasattr(centroids,prop)) for prop in properties]
130130

131131
def test_init_kwargs(self):
132132
""" Test default crs and kwargs forwarding """
@@ -151,7 +151,7 @@ def test_init_kwargs(self):
151151

152152
def test_from_meta_pass(self):
153153
expected_lon = np.array([-30.0, -20.0, -10.0]*3)
154-
expected_lat = np.repeat([30.0, 20.0, 10.0],3)
154+
expected_lat = np.repeat([30.0, 20.0, 10.0],3)
155155
# Check metadata
156156
meta = dict(
157157
crs=DEF_CRS,
@@ -233,7 +233,7 @@ def test_from_pnt_bounds(self):
233233
np.testing.assert_allclose([10.0, 10.0, 9.8], centr.lat[[0, 1, width]], atol=0.1)
234234
# generally we assume that from_meta does not set region_ids and on_land flags
235235
self.assertFalse(centr.region_id)
236-
self.assertFalse(centr.on_land)
236+
self.assertFalse(centr.on_land)
237237

238238
class TestCentroidsTransformation(unittest.TestCase):
239239
""" Test class for coordinate transformations of Centroid objects
@@ -247,7 +247,7 @@ def setUp(self):
247247
self.centr = Centroids(lat=VEC_LAT,lon=VEC_LON,crs=TEST_CRS)
248248

249249
def test_to_default_crs(self):
250-
# Creating Centroids with non-default CRS and
250+
# Creating Centroids with non-default CRS and
251251
# inplace transformation afterwards
252252
centroids = Centroids(lat=VEC_LAT, lon=VEC_LON, crs=ALT_CRS)
253253
self.assertTrue(u_coord.equal_crs(centroids.crs, ALT_CRS))
@@ -329,7 +329,7 @@ def test_set_on_land_implementationerror(self):
329329

330330
with self.assertRaises(NotImplementedError):
331331
centroids.set_on_land(source='satellite',overwrite=True)
332-
332+
333333
def test_set_on_land_raster(self):
334334
"""Test set_on_land"""
335335
centr_ras = Centroids.from_raster_file(HAZ_DEMO_FL, window=Window(0, 0, 50, 60))
@@ -736,7 +736,7 @@ def test_append_pass(self):
736736
def test_append(self):
737737
lat2,lon2 = np.array([6,7,8,9,10]),np.array([6,7,8,9,10])
738738
newcentr = Centroids(lat=lat2,lon=lon2)
739-
newcentr.append(self.centr)
739+
newcentr.append(self.centr)
740740
self.assertTrue(newcentr.size == len(self.centr.lon)+len(lon2))
741741
np.testing.assert_array_equal(newcentr.lon,np.concatenate([lon2,self.centr.lon]))
742742
np.testing.assert_array_equal(newcentr.lat,np.concatenate([lat2,self.centr.lat]))
@@ -764,7 +764,7 @@ def test_remove_duplicate_pass(self):
764764

765765
def test_remove_duplicates_dif_on_land(self):
766766
### We currently expect that only the geometry of the gdf defines duplicates.
767-
### If one geometry is duplicated with differences in other attributes e.g. on_land
767+
### If one geometry is duplicated with differences in other attributes e.g. on_land
768768
### they get removed nevertheless. Only the first occurrence will be part of the new object
769769
### this test is only here to guarantee this behaviour
770770
lat, lon = np.array([0,0,1,2,3,4,5]),np.array([0,0,1,2,3,4,5])
@@ -926,19 +926,18 @@ def test_get_closest_point(self):
926926
self.assertEqual(idx, 1)
927927

928928
def test_dist_coast_pass(self):
929-
"""Test set_dist_coast"""
929+
"""Test get_dist_coast"""
930930
dist_coast = self.centr.get_dist_coast()
931931
# Just checking that the output doesnt change over time.
932932
REF_VALUES = np.array([
933-
1605.243, 603.261, 26112.239, 2228.629, 7207.817,
934-
156271.372, 661.114, 158184.4,
933+
860.0, 200.0, 25610.0, 1000.0, 4685.0,
934+
507500.0, 500.0, 150500.0,
935935
])
936-
#
937936
self.assertIsInstance(dist_coast,np.ndarray)
938-
np.testing.assert_array_almost_equal(dist_coast, REF_VALUES, decimal=3)
937+
np.testing.assert_allclose(dist_coast, REF_VALUES, atol=1.0)
939938

940939
def test_dist_coast_pass_raster(self):
941-
"""Test set_region_id"""
940+
"""Test get_dist_coast for centroids derived from a raster file"""
942941
centr_ras = Centroids.from_raster_file(HAZ_DEMO_FL, window=Window(0, 0, 50, 60))
943942
dist_coast = centr_ras.get_dist_coast()
944943
self.assertLess(abs(dist_coast[0] - 117000), 1000)
@@ -978,7 +977,7 @@ def test_area_pass_raster(self):
978977
981037.92674582, 981065.50487659, 981065.50487385,
979978
])
980979
np.testing.assert_allclose(area_pixel, test_area)
981-
980+
982981
def test_equal_pass(self):
983982
"""Test equal"""
984983
centr_list = [

climada/hazard/test/test_tc_tracks.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,8 @@ def test_dist_since_lf_pass(self):
788788

789789
self.assertGreater(
790790
track.dist_since_lf.values[-1],
791-
u_coord.dist_to_coast(track.lat.values[-1], track.lon.values[-1]) / 1000)
791+
u_coord.dist_to_coast_nasa(track.lat.values[-1], track.lon.values[-1]) / 1000,
792+
)
792793
self.assertEqual(1020.5431562223974, track['dist_since_lf'].values[-1])
793794

794795
# check distances on land always increase, in second landfall

climada/util/coordinates.py

Lines changed: 33 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -457,36 +457,6 @@ def grid_is_regular(coord):
457457
regular = True
458458
return regular, count_lat[0], count_lon[0]
459459

460-
def get_coastlines(bounds=None, resolution=110):
461-
"""Get Polygones of coast intersecting given bounds
462-
463-
Parameters
464-
----------
465-
bounds : tuple
466-
min_lon, min_lat, max_lon, max_lat in EPSG:4326
467-
resolution : float, optional
468-
10, 50 or 110. Resolution in m. Default: 110m, i.e. 1:110.000.000
469-
470-
Returns
471-
-------
472-
coastlines : GeoDataFrame
473-
Polygons of coast intersecting given bounds.
474-
"""
475-
resolution = nat_earth_resolution(resolution)
476-
shp_file = shapereader.natural_earth(resolution=resolution,
477-
category='physical',
478-
name='coastline')
479-
coast_df = gpd.read_file(shp_file)
480-
coast_df.crs = NE_CRS
481-
if bounds is None:
482-
return coast_df[['geometry']]
483-
tot_coast = np.zeros(1)
484-
while not np.any(tot_coast):
485-
tot_coast = coast_df.envelope.intersects(box(*bounds))
486-
bounds = (bounds[0] - 20, bounds[1] - 20,
487-
bounds[2] + 20, bounds[3] + 20)
488-
return coast_df[tot_coast][['geometry']]
489-
490460
def convert_wgs_to_utm(lon, lat):
491461
"""Get EPSG code of UTM projection for input point in EPSG 4326
492462
@@ -505,49 +475,30 @@ def convert_wgs_to_utm(lon, lat):
505475
epsg_utm_base = 32601 + (0 if lat >= 0 else 100)
506476
return epsg_utm_base + (math.floor((lon + 180) / 6) % 60)
507477

508-
def utm_zones(wgs_bounds):
509-
"""Get EPSG code and bounds of UTM zones covering specified region
510-
511-
Parameters
512-
----------
513-
wgs_bounds : tuple
514-
lon_min, lat_min, lon_max, lat_max
478+
def dist_to_coast(coord_lat, lon=None, highres=False, signed=False):
479+
"""Read interpolated (signed) distance to coast (in m) from NASA data
515480
516-
Returns
517-
-------
518-
zones : list of pairs (zone_epsg, zone_wgs_bounds)
519-
EPSG code and bounding box in WGS coordinates.
520-
"""
521-
lon_min, lat_min, lon_max, lat_max = wgs_bounds
522-
lon_min, lon_max = max(-179.99, lon_min), min(179.99, lon_max)
523-
utm_min, utm_max = [math.floor((l + 180) / 6) for l in [lon_min, lon_max]]
524-
zones = []
525-
for utm in range(utm_min, utm_max + 1):
526-
epsg = 32601 + utm
527-
bounds = (-180 + 6 * utm, 0, -180 + 6 * (utm + 1), 90)
528-
if lat_max >= 0:
529-
zones.append((epsg, bounds))
530-
if lat_min < 0:
531-
bounds = (bounds[0], -90, bounds[2], 0)
532-
zones.append((epsg + 100, bounds))
533-
return zones
534-
535-
def dist_to_coast(coord_lat, lon=None, signed=False):
536-
"""Compute (signed) distance to coast from input points in meters.
481+
Note: The NASA raster file is 300 MB and will be downloaded on first run!
537482
538483
Parameters
539484
----------
540-
coord_lat : GeoDataFrame or np.array or float
541-
One of the following:
542-
* GeoDataFrame with geometry column in epsg:4326
543-
* np.array with two columns, first for latitude of each point
544-
and second with longitude in epsg:4326
545-
* np.array with one dimension containing latitudes in epsg:4326
546-
* float with a latitude value in epsg:4326
547-
lon : np.array or float, optional
485+
coord_lat : GeoDataFrame or np.ndarray or float
548486
One of the following:
549-
* np.array with one dimension containing longitudes in epsg:4326
550-
* float with a longitude value in epsg:4326
487+
488+
* GeoDataFrame with geometry column in epsg:4326
489+
* np.array with two columns, first for latitude of each point
490+
and second with longitude in epsg:4326
491+
* np.array with one dimension containing latitudes in epsg:4326
492+
* float with a latitude value in epsg:4326
493+
494+
lon : np.ndarray or float, optional
495+
If given, one of the following:
496+
497+
* np.array with one dimension containing longitudes in epsg:4326
498+
* float with a longitude value in epsg:4326
499+
500+
highres : bool, optional
501+
Use full resolution of NASA data (much slower). Default: False.
551502
signed : bool
552503
If True, distance is signed with positive values off shore and negative values on land.
553504
Default: False
@@ -557,53 +508,21 @@ def dist_to_coast(coord_lat, lon=None, signed=False):
557508
dist : np.array
558509
(Signed) distance to coast in meters.
559510
"""
560-
if isinstance(coord_lat, (gpd.GeoDataFrame, gpd.GeoSeries)):
561-
if not equal_crs(coord_lat.crs, NE_CRS):
562-
raise ValueError('Input CRS is not %s' % str(NE_CRS))
563-
geom = coord_lat
564-
else:
565-
if lon is None:
566-
if isinstance(coord_lat, np.ndarray) and coord_lat.shape[1] == 2:
567-
lat, lon = coord_lat[:, 0], coord_lat[:, 1]
568-
else:
569-
raise ValueError('Missing longitude values.')
511+
if lon is None:
512+
if isinstance(coord_lat, (gpd.GeoDataFrame, gpd.GeoSeries)):
513+
if not equal_crs(coord_lat.crs, DEF_CRS):
514+
raise ValueError('Input CRS is not %s' % str(DEF_CRS))
515+
geom = coord_lat if isinstance(coord_lat, gpd.GeoSeries) else coord_lat["geometry"]
516+
lon, lat = geom.x.values, geom.y.values
517+
elif isinstance(coord_lat, np.ndarray) and coord_lat.shape[1] == 2:
518+
lat, lon = coord_lat[:, 0], coord_lat[:, 1]
570519
else:
571-
lat, lon = [np.asarray(v).reshape(-1) for v in [coord_lat, lon]]
572-
if lat.size != lon.size:
573-
raise ValueError('Mismatching input coordinates size: %s != %s'
574-
% (lat.size, lon.size))
575-
geom = gpd.GeoDataFrame(geometry=gpd.points_from_xy(lon, lat), crs=NE_CRS)
576-
577-
pad = 20
578-
bounds = (geom.total_bounds[0] - pad, geom.total_bounds[1] - pad,
579-
geom.total_bounds[2] + pad, geom.total_bounds[3] + pad)
580-
coast = get_coastlines(bounds, 10).geometry
581-
coast = gpd.GeoDataFrame(geometry=coast, crs=NE_CRS)
582-
dist = np.empty(geom.shape[0])
583-
zones = utm_zones(geom.geometry.total_bounds)
584-
for izone, (epsg, bounds) in enumerate(zones):
585-
to_crs = f"epsg:{epsg}"
586-
zone_mask = (
587-
(bounds[1] <= geom.geometry.y)
588-
& (geom.geometry.y <= bounds[3])
589-
& (bounds[0] <= geom.geometry.x)
590-
& (geom.geometry.x <= bounds[2])
591-
)
592-
if np.count_nonzero(zone_mask) == 0:
593-
continue
594-
LOGGER.info("dist_to_coast: UTM %d (%d/%d)",
595-
epsg, izone + 1, len(zones))
596-
bounds = geom[zone_mask].total_bounds
597-
bounds = (bounds[0] - pad, bounds[1] - pad,
598-
bounds[2] + pad, bounds[3] + pad)
599-
coast_mask = coast.envelope.intersects(box(*bounds))
600-
utm_coast = coast[coast_mask].geometry.unary_union
601-
utm_coast = gpd.GeoDataFrame(geometry=[utm_coast], crs=NE_CRS)
602-
utm_coast = utm_coast.to_crs(to_crs).geometry[0]
603-
dist[zone_mask] = geom[zone_mask].to_crs(to_crs).distance(utm_coast)
604-
if signed:
605-
dist[coord_on_land(geom.geometry.y, geom.geometry.x)] *= -1
606-
return dist
520+
raise ValueError('Missing longitude values.')
521+
else:
522+
lat, lon = [np.asarray(v).reshape(-1) for v in [coord_lat, lon]]
523+
if lat.size != lon.size:
524+
raise ValueError(f'Mismatching input coordinates size: {lat.size} != {lon.size}')
525+
return dist_to_coast_nasa(lat, lon, highres=highres, signed=signed)
607526

608527
def _get_dist_to_coast_nasa_tif():
609528
"""Get the path to the NASA raster file for distance to coast.

climada/util/test/test_coordinates.py

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -969,27 +969,6 @@ def test_nat_earth_resolution_fail(self):
969969
with self.assertRaises(ValueError):
970970
u_coord.nat_earth_resolution(111)
971971

972-
def test_get_coastlines_all_pass(self):
973-
"""Check get_coastlines function over whole earth"""
974-
coast = u_coord.get_coastlines(resolution=110)
975-
tot_bounds = coast.total_bounds
976-
self.assertEqual((134, 1), coast.shape)
977-
self.assertAlmostEqual(tot_bounds[0], -180)
978-
self.assertAlmostEqual(tot_bounds[1], -85.60903777)
979-
self.assertAlmostEqual(tot_bounds[2], 180.00000044)
980-
self.assertAlmostEqual(tot_bounds[3], 83.64513)
981-
982-
def test_get_coastlines_pass(self):
983-
"""Check get_coastlines function in defined extent"""
984-
bounds = (-100, -55, -20, 35)
985-
coast = u_coord.get_coastlines(bounds, resolution=110)
986-
ex_box = box(bounds[0], bounds[1], bounds[2], bounds[3])
987-
self.assertEqual(coast.shape[0], 14)
988-
self.assertTrue(coast.total_bounds[2] < 0)
989-
for _row, line in coast.iterrows():
990-
if not ex_box.intersects(line.geometry):
991-
self.assertEqual(1, 0)
992-
993972
def test_get_land_geometry_country_pass(self):
994973
"""get_land_geometry with selected countries."""
995974
iso_countries = ['DEU', 'VNM']
@@ -1041,26 +1020,6 @@ def test_on_land_pass(self):
10411020
self.assertEqual(res.size, lat.size)
10421021
np.testing.assert_array_equal(res[:3], [True, False, True])
10431022

1044-
def test_dist_to_coast(self):
1045-
"""Test point in coast and point not in coast"""
1046-
points = np.array([
1047-
# Caribbean Sea:
1048-
[13.208333333333329, -59.625000000000014],
1049-
# South America:
1050-
[-12.497529, -58.849505],
1051-
# Very close to coast of Somalia:
1052-
[1.96768, 45.23219],
1053-
])
1054-
dists = [2594.2071059573445, 1382985.2459744606, 0.088222234]
1055-
for d, p in zip(dists, points):
1056-
res = u_coord.dist_to_coast(*p)
1057-
self.assertAlmostEqual(d, res[0])
1058-
1059-
# All at once requires more than one UTM
1060-
res = u_coord.dist_to_coast(points)
1061-
for d, r in zip(dists, res):
1062-
self.assertAlmostEqual(d, r)
1063-
10641023
def test_dist_to_coast_nasa(self):
10651024
"""Test point in coast and point not in coast"""
10661025
points = np.array([

0 commit comments

Comments
 (0)