Skip to content

Commit

Permalink
ENH: Add two functions for converting degrees to and from meters: `ra…
Browse files Browse the repository at this point in the history
…sters.from_deg_to_meters` and `rasters.from_meters_to_deg`
  • Loading branch information
remi-braun committed Feb 20, 2025
1 parent d26fbbf commit 7b1689f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Release History

## 1.45.3 (2025-mm-dd)
## 1.46.0 (2025-mm-dd)

- **ENH: Add two functions for converting degrees to and from meters: `rasters.from_deg_to_meters` and `rasters.from_meters_to_deg`**
- FIX: Don't take nodata value into account in `ci.assert_raster_almost_equal_magnitude`

## 1.45.2 (2025-02-17)
Expand Down
47 changes: 47 additions & 0 deletions ci/test_rasters.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from ci.script_utils import KAPUT_KWARGS, dask_env, get_output, rasters_path, s3_env
from sertit import ci, geometry, path, rasters, unistra, vectors
from sertit.rasters import (
DEG_LAT_TO_M,
FLOAT_NODATA,
INT8_NODATA,
UINT8_NODATA,
Expand Down Expand Up @@ -991,3 +992,49 @@ def test_get_notata_from_xr(raster_path):

raster_path = rasters_path().joinpath("dem.tif")
ci.assert_val(get_nodata_value_from_xr(rasters.read(raster_path)), -9999, "nodata")


def test_deg_meters_conversion():
"""
Test conversions between deg and meters
https://wiki.openstreetmap.org/wiki/Precision_of_coordinates#Precision_of_latitudes
"""
# from_deg_to_meters
ci.assert_val(
rasters.from_deg_to_meters(1), DEG_LAT_TO_M, "1 degree of latitude in meters"
)
ci.assert_val(
rasters.from_deg_to_meters(1, lat=45),
78573.71,
"1 degree of longitude at 45 degrees of latitude in meters",
)
ci.assert_val(
rasters.from_deg_to_meters(1, lat=45, decimals=0),
78574,
"1 degree of longitude at 45 degrees of latitude in meters with 0 decimal",
)
ci.assert_val(
rasters.from_deg_to_meters(1, lat=45, decimals=0, average_lat_lon=True),
round((DEG_LAT_TO_M + 78574) / 2),
"1 degree of longitude at 45 degrees of latitude in meters (averaged) with 0 decimal",
)

# from_deg_to_meters
ci.assert_val(
rasters.from_meters_to_deg(0.5), 4.5e-06, "0.5 meter in degrees of latitude"
)
ci.assert_val(
rasters.from_meters_to_deg(0.5, lat=45),
6.4e-06,
"0.5 meter in degree of longitude at 45 degrees of latitude",
)
ci.assert_val(
rasters.from_meters_to_deg(0.5, lat=45, decimals=9),
6.363e-06,
"0.5 meter in degree of longitude at 45 degrees of latitude with 9 decimal",
)
ci.assert_val(
rasters.from_meters_to_deg(0.5, lat=45, decimals=9, average_lat_lon=True),
5.432e-06,
"0.5 meter in degree of longitude at 45 degrees of latitude (averaged) with 9 decimal",
)
2 changes: 1 addition & 1 deletion sertit/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
Sertit Utils library
"""

__version__ = "1.45.2"
__version__ = "1.46.0.dev0"
65 changes: 63 additions & 2 deletions sertit/rasters.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
import xarray as xr
from shapely.geometry import Polygon

from sertit.rasters_rio import DEG_2_RAD

try:
import rasterio
import rioxarray
Expand Down Expand Up @@ -63,6 +61,11 @@
FLOAT_NODATA = rasters_rio.FLOAT_NODATA
""" :code:`float` nodata """

DEG_LAT_TO_M = 111_120.0
DEG_2_RAD = rasters_rio.DEG_2_RAD

# https://wiki.openstreetmap.org/wiki/Precision_of_coordinates#Precision_of_latitudes


def get_nodata_value_from_xr(xds: AnyXrDataStructure) -> float:
"""
Expand Down Expand Up @@ -2033,3 +2036,61 @@ def aspect(xds: AnyRasterType, **kwargs) -> AnyXrDataStructure:


# TODO: add other DEM-related functions like 'curvature', etc if needed. Create a dedicated module?


def from_deg_to_meters(
deg: float, lat: float = None, decimals: float = 2, average_lat_lon: bool = False
) -> float:
"""
Approximately convert a distance in degrees to a distance in meters.
Only true at the Equator if lat is not given.
Very false for longitude distance elsewhere.
If lat is given
See https://wiki.openstreetmap.org/wiki/Precision_of_coordinates
Args:
deg (float): Distance in degrees
lat (float): Latitude (useful if the distance is longitudinal)
decimals (float): To round the distance, as the precision can be poor here
average_lat_lon (bool): Average between lat distance and lon distance
Returns:
float: Distance in meters
"""
dist = float(deg) * DEG_LAT_TO_M

if lat is not None:
dist_lon = dist * np.cos(lat * DEG_2_RAD)
dist = (dist + dist_lon) / 2 if average_lat_lon else dist_lon

return np.round(dist, decimals)


def from_meters_to_deg(
meters: float, lat: float = None, decimals: float = 7, average_lat_lon: bool = False
) -> float:
"""
Approximately convert a distance in meters to a distance in degrees. Only true at the Equator.
Very false for longitude distance elsewhere.
See https://wiki.openstreetmap.org/wiki/Precision_of_coordinates
Args:
meters (float): Distance in meters
lat (float): Latitude (useful if the distance is longitudinal)
decimals (float): To round the distance, as the precision can be poor here. 7 decimals is equivalent to a centimetric precision
average_lat_lon (bool): Average between lat distance and lon distance
Returns:
float: Distance in degrees
"""
dist = float(meters) / DEG_LAT_TO_M

if lat is not None:
dist_lon = dist / np.cos(lat * DEG_2_RAD)
dist = (dist + dist_lon) / 2 if average_lat_lon else dist_lon

return np.round(dist, decimals)

0 comments on commit 7b1689f

Please sign in to comment.