From af9756cf3080e6ba8188374b33103c1445b7c679 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 4 Nov 2024 15:07:18 +0100 Subject: [PATCH] Add explicit `from_lat_lon` and `from_lon_lat` to `GeoPoints` in Python --- .../all/archetypes/geo_line_string_simple.cpp | 2 +- .../all/archetypes/geo_point_simple.py | 2 +- .../src/rerun/archetypes/geo_points_ext.cpp | 2 +- .../rerun/components/geo_line_string_ext.cpp | 4 +- .../rerun_sdk/rerun/archetypes/geo_points.py | 5 +- .../rerun/archetypes/geo_points_ext.py | 86 +++++++++++++++++++ rerun_py/tests/unit/test_geopoints.py | 12 +++ 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 rerun_py/rerun_sdk/rerun/archetypes/geo_points_ext.py diff --git a/docs/snippets/all/archetypes/geo_line_string_simple.cpp b/docs/snippets/all/archetypes/geo_line_string_simple.cpp index 651d52caa8d0..0bf1a43daf5a 100644 --- a/docs/snippets/all/archetypes/geo_line_string_simple.cpp +++ b/docs/snippets/all/archetypes/geo_line_string_simple.cpp @@ -5,7 +5,7 @@ int main() { const auto rec = rerun::RecordingStream("rerun_example_geo_line_strings"); rec.spawn().exit_on_failure(); - + auto line_string = rerun::components::GeoLineString::from_lat_lon( {{41.0000, -109.0452}, {41.0000, -102.0415}, diff --git a/docs/snippets/all/archetypes/geo_point_simple.py b/docs/snippets/all/archetypes/geo_point_simple.py index f30e7db8944c..70e44625a3cc 100644 --- a/docs/snippets/all/archetypes/geo_point_simple.py +++ b/docs/snippets/all/archetypes/geo_point_simple.py @@ -6,7 +6,7 @@ rr.log( "rerun_hq", - rr.GeoPoints( + rr.GeoPoints.from_lat_lon( [59.319221, 18.075631], radii=rr.Radius.ui_points(10.0), colors=[255, 0, 0], diff --git a/rerun_cpp/src/rerun/archetypes/geo_points_ext.cpp b/rerun_cpp/src/rerun/archetypes/geo_points_ext.cpp index 87c198afe6c5..25c9eeebff52 100644 --- a/rerun_cpp/src/rerun/archetypes/geo_points_ext.cpp +++ b/rerun_cpp/src/rerun/archetypes/geo_points_ext.cpp @@ -18,4 +18,4 @@ namespace rerun { // #endif } // namespace archetypes -} // namespace rerun \ No newline at end of file +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/geo_line_string_ext.cpp b/rerun_cpp/src/rerun/components/geo_line_string_ext.cpp index 0ceebf66ee42..469c7a8e45bb 100644 --- a/rerun_cpp/src/rerun/components/geo_line_string_ext.cpp +++ b/rerun_cpp/src/rerun/components/geo_line_string_ext.cpp @@ -17,5 +17,5 @@ namespace rerun { // #endif - } // namespace archetypes -} // namespace rerun ∆ \ No newline at end of file + } // namespace components +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/geo_points.py b/rerun_py/rerun_sdk/rerun/archetypes/geo_points.py index 08946f322279..8295d0f244f2 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/geo_points.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/geo_points.py @@ -14,12 +14,13 @@ Archetype, ) from ..error_utils import catch_and_log_exceptions +from .geo_points_ext import GeoPointsExt __all__ = ["GeoPoints"] @define(str=False, repr=False, init=False) -class GeoPoints(Archetype): +class GeoPoints(GeoPointsExt, Archetype): """ **Archetype**: Geospatial points with positions expressed in [EPSG:4326](https://epsg.io/4326) altitude and longitude (North/East-positive degrees), and optional colors and radii. @@ -35,7 +36,7 @@ class GeoPoints(Archetype): rr.log( "rerun_hq", - rr.GeoPoints( + rr.GeoPoints.from_lat_lon( [59.319221, 18.075631], radii=rr.Radius.ui_points(10.0), colors=[255, 0, 0], diff --git a/rerun_py/rerun_sdk/rerun/archetypes/geo_points_ext.py b/rerun_py/rerun_sdk/rerun/archetypes/geo_points_ext.py new file mode 100644 index 000000000000..a0e85b1484a0 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/archetypes/geo_points_ext.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Sequence + +import numpy as np + +from .. import datatypes +from .._converters import to_np_float64 + +if TYPE_CHECKING: + from .. import GeoPoints + +NUMPY_VERSION = tuple(map(int, np.version.version.split(".")[:2])) + + +class GeoPointsExt: + """Extension for [GeoPoints][rerun.archetypes.GeoPoints].""" + + @staticmethod + def from_lat_lon( + positions: datatypes.DVec2DArrayLike, + *, + radii: datatypes.Float32ArrayLike | None = None, + colors: datatypes.Rgba32ArrayLike | None = None, + ) -> GeoPoints: + """ + Create a new instance of the GeoPoints archetype using latitudes and longitudes, in that order. + + *Note*: this is how Rerun natively stores geospatial data. + + Parameters + ---------- + positions: + The [EPSG:4326](https://epsg.io/4326) latitudes and longitudes (in that order) coordinates for the points (North/East-positive degrees). + radii: + Optional radii for the points, effectively turning them into circles. + colors: + Optional colors for the points. + + The colors are interpreted as RGB or RGBA in sRGB gamma-space, + As either 0-1 floats or 0-255 integers, with separate alpha. + + """ + + from .. import GeoPoints + + return GeoPoints(positions, radii=radii, colors=colors) + + @staticmethod + def from_lon_lat( + positions: datatypes.DVec2DArrayLike, + *, + radii: datatypes.Float32ArrayLike | None = None, + colors: datatypes.Rgba32ArrayLike | None = None, + ) -> GeoPoints: + """ + Create a new instance of the GeoPoints archetype using longitude and latitudes, in that order. + + *Note*: Rerun stores latitude first, so this method converts the input to a Numpy array and swaps the + coordinates first. + + Parameters + ---------- + positions: + The [EPSG:4326](https://epsg.io/4326) latitudes and longitudes (in that order) coordinates for the points (North/East-positive degrees). + radii: + Optional radii for the points, effectively turning them into circles. + colors: + Optional colors for the points. + + The colors are interpreted as RGB or RGBA in sRGB gamma-space, + As either 0-1 floats or 0-255 integers, with separate alpha. + + """ + + from .. import GeoPoints + from ..datatypes import DVec2D + + if isinstance(positions, Sequence): + flipped_pos = np.array([np.array(p.xy) if isinstance(p, DVec2D) else p for p in positions]) + elif isinstance(positions, DVec2D): + flipped_pos = np.array(positions.xy) + else: + flipped_pos = to_np_float64(positions) + + return GeoPoints(np.fliplr(flipped_pos), radii=radii, colors=colors) diff --git a/rerun_py/tests/unit/test_geopoints.py b/rerun_py/tests/unit/test_geopoints.py index e2b1381e6c63..95c626f290ee 100644 --- a/rerun_py/tests/unit/test_geopoints.py +++ b/rerun_py/tests/unit/test_geopoints.py @@ -55,6 +55,18 @@ def test_geopoints() -> None: assert arch.colors == colors_expected(colors) +def test_geopoints_lat_lon_constructors() -> None: + positions_lat_lon = np.array([[59.319221, 18.075631], [50.319221, 12.075631]]) + positions_lon_lat = np.fliplr(positions_lat_lon) + + arch1 = rr.GeoPoints.from_lat_lon(positions_lat_lon) + arch2 = rr.GeoPoints.from_lon_lat(positions_lon_lat) + arch3 = rr.GeoPoints(positions_lat_lon) + + assert arch1.positions == arch2.positions + assert arch2.positions == arch3.positions + + @pytest.mark.parametrize( "data", [