@@ -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-
490460def 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
608527def _get_dist_to_coast_nasa_tif ():
609528 """Get the path to the NASA raster file for distance to coast.
0 commit comments