The Project
This Polars plugin provides functionality which can be loosely described as tranformation of coordinates and extraction of features from them.
It contains functions which were needed in personal and work projects, therefore its set of features might appear a bit random. Nevertheless one can find it useful in projects related to robotics, geospatial science, spatial analytics etc.
The functions are divided among three namespaces: transform
, s2
, distance
:
-
transform
namespace contains functions for converting coordinates from\to map, ecef, lla, utm reference frames.
-
s2
namespace contains functions which allow to work with S2 Cells
-
distance
namespace allows to calculate distances between coordinates.
This plugin presupposes that coordianates represent points in space and that they are expressed with struct
datatype in Polars.
Getting Started
Installation
pip install polars-coord-transforms
Usage
import polars_coord_transforms
In order to use plugin, coordinates should be represented as struct
with fields x
, y
, z
(or, in case of LLA-points: lon
, lat
, alt
)!
For instance, if coordinates are in separate columns, one can make a valid struct
with pl.struct
native Polars function:
import polars as pl
df = pl.DataFrame(
dict(
lon=[31.409197919000064,],
lat=[58.860667429000046,],
alt=[57.309668855211015,],
)
)
df.with_columns(
point=pl.struct("lon", "lat", "alt")
)
Examples
Suppose we have the following DataFrame with some coordinates (column "pose"), rotation quaternion (column "rotation") and offset vector (column "offset"):
import polars as pl
df = pl.DataFrame(
[
pl.Series("pose", [{'x': 4190.66735544079, 'y': 14338.862844330957, 'z': 10.96391354687512}], dtype=pl.Struct({'x': pl.Float64, 'y': pl.Float64, 'z': pl.Float64})),
pl.Series("rotation", [{'x': 0.13007119, 'y': 0.26472049, 'z': 0.85758219, 'w': 0.42137553}], dtype=pl.Struct({'x': pl.Float64, 'y': pl.Float64, 'z': pl.Float64, 'w': pl.Float64})),
pl.Series("offset", [{'x': 2852423.40536658, 'y': 2201848.41975346, 'z': 5245234.74365368}], dtype=pl.Struct({'x': pl.Float64, 'y': pl.Float64, 'z': pl.Float64})),
]
)
print(df)
shape: (1, 3)
βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β pose β rotation β offset β
β --- β --- β --- β
β struct[3] β struct[4] β struct[3] β
βββββββββββββββββββββββββββββββͺββββββββββββββββββββββββββββͺββββββββββββββββββββββββββββββββββββ‘
β {4190.667,14338.863,10.964} β {0.130,0.265,0.858,0.421} β {2852423.405,2201848.420,5245234β¦ β
βββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββ
transform
Transform coordinates from map reference frame to ECEF (Earth-Ceneterd, Earth-Fixed) coordinate system using a rotation quaternion and an offset vector.
df.with_columns(
ecef=pl.col("pose").transform.map_to_ecef(
pl.col("rotation"), pl.col("offset")
)
)
shape: (1, 4)
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β pose β rotation β offset β ecef β
β --- β --- β --- β --- β
β struct[3] β struct[4] β struct[3] β struct[3] β
ββββββββββββββββββββββββββͺβββββββββββββββββββββββββͺβββββββββββββββββββββββββͺββββββββββββββββββββββββ‘
β {4190.667,14338.863,10 β {0.130,0.265,0.858,0.4 β {2852423.405,2201848.4 β {2840491.941,2197932. β
β .964} β 21} β 20,5245234β¦ β 225,5253325β¦ β
ββββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββββββββββββββββ
Inverse transformation from ECEF to map
df.with_columns(
pose_new=pl.col("ecef").transform.ecef_to_map("rotation", "offset")
).select(
"pose",
"pose_new"
)
shape: (1, 5)
βββββββββββββββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββββ¬ββββββββββββββββββββ¬βββββββββββββββββββ
β pose β rotation β offset β ecef β pose_new β
β --- β --- β --- β --- β --- β
β struct[3] β struct[4] β struct[3] β struct[3] β struct[3] β
βββββββββββββββββββββͺββββββββββββββββββββͺββββββββββββββββββββͺββββββββββββββββββββͺβββββββββββββββββββ‘
β {4190.667,14338.8 β {0.130,0.265,0.85 β {2852423.405,2201 β {2840491.941,2197 β {4190.667,14338. β
β 63,10.964} β 8,0.421} β 848.420,5245234β¦ β 932.225,5253325β¦ β 863,10.964} β
βββββββββββββββββββββ΄ββββββββββββββββββββ΄ββββββββββββββββββββ΄ββββββββββββββββββββ΄βββββββββββββββββββ
Transform coordinates from ECEF to LLA (Longitude, Latitude, Altitude)
df.with_columns(
lla=pl.col("ecef").transform.ecef_to_lla()
)
shape: (1, 3)
βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ
β pose β ecef β lla β
β --- β --- β --- β
β struct[3] β struct[3] β struct[3] β
βββββββββββββββββββββββββββββββͺββββββββββββββββββββββββββββββββββββͺββββββββββββββββββββββββββ‘
β {4190.667,14338.863,10.964} β {2840491.941,2197932.225,5253325β¦ β {37.732,55.820,163.916} β
βββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ
Inverse transform from LLA to ECEF
df.with_columns(
ecef_new=pl.col("lla").transform.lla_to_ecef()
)
shape: (1, 4)
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬βββββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β pose β ecef β lla β ecef_new β
β --- β --- β --- β --- β
β struct[3] β struct[3] β struct[3] β struct[3] β
ββββββββββββββββββββββββββͺβββββββββββββββββββββββββͺβββββββββββββββββββββββββͺββββββββββββββββββββββββ‘
β {4190.667,14338.863,10 β {2840491.941,2197932.2 β {37.732,55.820,163.916 β {2840491.941,2197932. β
β .964} β 25,5253325β¦ β } β 225,5253325β¦ β
ββββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄βββββββββββββββββββββββββ΄ββββββββββββββββββββββββ
Transform coordinates from LLA to UTM coordinates (UTM zone is derived from coordinates themselves)
df.with_columns(
utm=pl.col("lla").transform.lla_to_utm()
)
shape: (1, 3)
βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β pose β lla β utm β
β --- β --- β --- β
β struct[3] β struct[3] β struct[3] β
βββββββββββββββββββββββββββββββͺββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββββββββ‘
β {4190.667,14338.863,10.964} β {37.732,55.820,163.916} β {420564.380,6186739.936,163.916} β
βββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ
Find UTM zone number from a LLA point
df.with_columns(
utm_zone_number=pl.col("lla").transform.lla_to_utm_zone_number()
)
shape: (1, 3)
βββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββ
β lla β utm β utm_zone_number β
β --- β --- β --- β
β struct[3] β struct[3] β u8 β
βββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββββββββͺββββββββββββββββββ‘
β {37.732,55.820,163.916} β {420564.380,6186739.936,163.916} β 37 β
βββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββ΄ββββββββββββββββββ
Transform quaternion to Euler angles (roll, pitch, yaw)
the function returns a struct with 3 fields:"roll", "pitch", "yaw"
df.select(
euler_angles=pl.col("rotation").transform.quat_to_euler_angles()
)
ββββββββββββββββββββββββββββββββ
β euler_angles β
β --- β
β struct[3] β
ββββββββββββββββββββββββββββββββ‘
β {0.598806,0.000000,2.228181} β
ββββββββββββββββββββββββββββββββ
s2
Find S2 CellID of a point with longitude and latitude (with a given cell level)
df.select(
cellid_30=pl.col("lla").s2.lonlat_to_cellid(level=30),
cellid_28=pl.col("lla").s2.lonlat_to_cellid(level=28),
cellid_5=pl.col("lla").s2.lonlat_to_cellid(level=5),
)
shape: (1, 3)
βββββββββββββββββββββββ¬ββββββββββββββββββββββ¬ββββββββββββββββββββββ
β cellid_30 β cellid_28 β cellid_5 β
β --- β --- β --- β
β u64 β u64 β u64 β
βββββββββββββββββββββββͺββββββββββββββββββββββͺββββββββββββββββββββββ‘
β 5095036114269810839 β 5095036114269810832 β 5094697078462873600 β
βββββββββββββββββββββββ΄ββββββββββββββββββββββ΄ββββββββββββββββββββββ
Find longitude and latitude from a S2 CellID
df.select(
lla_cell=pl.lit(5095036114269810839, dtype=pl.UInt64()).s2.cellid_to_lonlat()
)
shape: (1, 1)
βββββββββββββββββββ
β lla_cell β
β --- β
β struct[2] β
βββββββββββββββββββ‘
β {37.732,55.820} β
βββββββββββββββββββ
Find whether a given LLA point is in a S2 Cell identified by a specific ID
df.select(
lla",
cellid=pl.lit(5095036114269810832, dtype=pl.UInt64()),
is_in_cell=pl.lit(5095036114269810832, dtype=pl.UInt64()).s2.cell_contains_point(pl.col("lla"))
)
shape: (1, 3)
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββ¬βββββββββββββ
β lla β cellid β is_in_cell β
β --- β --- β --- β
β struct[3] β u64 β bool β
βββββββββββββββββββββββββββͺββββββββββββββββββββββͺβββββββββββββ‘
β {37.732,55.820,163.916} β 5095036114269810832 β true β
βββββββββββββββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββββββ
Find vertices of a S2 Cell from a CellID
df.with_columns(
cellid=pl.col("lla").s2.lonlat_to_cellid(level=5),
).with_columns(
vertices=pl.col("cellid").s2.cellid_to_vertices()
)
shape: (1, 4)
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ¬ββββββββββββββββββββββ¬βββββββββββββββββββββββββ
β pose β lla β cellid β vertices β
β --- β --- β --- β --- β
β struct[3] β struct[3] β u64 β struct[8] β
βββββββββββββββββββββββββββͺββββββββββββββββββββββββββͺββββββββββββββββββββββͺβββββββββββββββββββββββββ‘
β {4190.667,14338.863,10. β {37.732,55.820,163.916} β 5094697078462873600 β {37.304,55.491,40.932, β
β 964} β β β 57.545,36.β¦ β
βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββ΄ββββββββββββββββββββββ΄βββββββββββββββββββββββββ
df.select("vertices").unnest("vertices")
shape: (1, 8)
ββββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ¬βββββββββ
β v0_lon β v0_lat β v1_lon β v1_lat β v2_lon β v2_lat β v3_lon β v3_lat β
β --- β --- β --- β --- β --- β --- β --- β --- β
β f64 β f64 β f64 β f64 β f64 β f64 β f64 β f64 β
ββββββββββͺβββββββββͺβββββββββͺβββββββββͺβββββββββͺβββββββββͺβββββββββͺβββββββββ‘
β 37.304 β 55.491 β 40.932 β 57.545 β 36.495 β 59.135 β 33.024 β 56.886 β
ββββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββ΄βββββββββ
distance
df = pl.DataFrame(
[
pl.Series("point_1", [{'x': -8893.663914126577, 'y': 19116.178523519542, 'z': 14.98697863612324}], dtype=pl.Struct({'x': pl.Float64, 'y': pl.Float64, 'z': pl.Float64})),
pl.Series("point_2", [{'x': 1553.3742543335538, 'y': 2916.118342842441, 'z': 15.580027717165649}], dtype=pl.Struct({'x': pl.Float64, 'y': pl.Float64, 'z': pl.Float64})),
]
)
Find Euclidean distance between two points using all 3 components of a point-vector
df.with_columns(
distance=pl.col("point_1").distance.euclidean_3d(pl.col("point_2"))
)
shape: (1, 3)
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ¬ββββββββββββ
β point_1 β point_2 β distance β
β --- β --- β --- β
β struct[3] β struct[3] β f64 β
ββββββββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββͺββββββββββββ‘
β {-8893.664,19116.179,14.987} β {1553.374,2916.118,15.580} β 19276.477 β
ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ΄ββββββββββββ
Find cosine similarity between between two points using all 3 components of a point-vector
df.with_columns(
cosine_sim=pl.col("point_1").distance.cosine_similarity_3d(pl.col("point_2"))
)
shape: (1, 3)
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ¬βββββββββββββ
β point_1 β point_2 β cosine_sim β
β --- β --- β --- β
β struct[3] β struct[3] β f64 β
ββββββββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββͺβββββββββββββ‘
β {-8893.664,19116.179,14.987} β {1553.374,2916.118,15.580} β 0.602 β
ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ΄βββββββββββββ
Find Euclidean distance between two points using 2 components of a point-vector (X and Y)
df.with_columns(
distance=pl.col("point_1").distance.euclidean_2d(pl.col("point_2"))
)
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ¬ββββββββββββ
β point_1 β point_2 β distance β
β --- β --- β --- β
β struct[3] β struct[3] β f64 β
ββββββββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββͺββββββββββββ‘
β {-8893.664,19116.179,14.987} β {1553.374,2916.118,15.580} β 19276.477 β
ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ΄ββββββββββββ
Find cosine similarity between between two points using 2 components of a point-vector (X and Y)
shape: (1, 3)
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ¬βββββββββββββ
β point_1 β point_2 β cosine_sim β
β --- β --- β --- β
β struct[3] β struct[3] β f64 β
ββββββββββββββββββββββββββββββββͺβββββββββββββββββββββββββββββͺβββββββββββββ‘
β {-8893.664,19116.179,14.987} β {1553.374,2916.118,15.580} β 0.602 β
ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββ΄βββββββββββββ