Created
April 20, 2025 15:06
-
-
Save malteneuss/fed6625bba30d43422bee1b531a06309 to your computer and use it in GitHub Desktop.
SqlxPostgresGeolocation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use byteorder::ReadBytesExt; | |
use byteorder::{BigEndian, LittleEndian, WriteBytesExt}; | |
use sqlx::encode::{Encode, IsNull}; | |
use sqlx::postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef}; | |
use sqlx::{Postgres, Type}; | |
use std::error::Error; | |
// PostGIS enables PostgreSQL to handle spatial data and perform complex | |
// geographic operations, making it a | |
// powerful tool for applications that require geospatial capabilities. | |
// GEOGRAPHY: This is a PostGIS data type used to store geographic data, which takes into account the Earth's curvature. | |
// Point: This specifies that the data type is a point, representing a single location with latitude and longitude. | |
// 4326: This is the Spatial Reference System Identifier (SRID) for WGS 84, a standard coordinate system used globally for GPS and mapping. | |
// POSTGIS encodes the geometric data in Well-Known Binary (WKB) format, which is a common binary representation of a geometry object. | |
// https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry | |
// For geography data, POSTGIS uses Extended Well-Known Binary (EWKB) format is used, which also | |
// includes the SRID integer to specify GPS "geography" data type. | |
#[derive(Debug, Clone, PartialEq)] | |
pub struct Geolocation { | |
pub latitude: f64, | |
pub longitude: f64, | |
} | |
const SRID_WGS84: i32 = 4326; | |
// println!("size_hint: {}", self.to_postgis_extended_well_known_bytes_little_endian().len()); | |
const EWKB_GEOGRAPHY_POINT_2D_BYTE_COUNT: usize = 25; | |
impl Geolocation { | |
pub fn new(latitude: f64, longitude: f64) -> Self { | |
Geolocation { | |
latitude, | |
longitude, | |
} | |
} | |
pub fn to_postgis_extended_well_known_bytes_little_endian(&self) -> Vec<u8> { | |
let mut ewkb = Vec::with_capacity(EWKB_GEOGRAPHY_POINT_2D_BYTE_COUNT); | |
ewkb.write_u8(1).unwrap(); // Little endian flag | |
// wkb.write_u32::<LittleEndian>(0x00000001).unwrap(); // WKB type for Point without SRID flag; don't use, because it messes up floats later if used with SRID!! | |
ewkb.write_u32::<LittleEndian>(0x20000001).unwrap(); // EWKB type for Point with SRID flag!! | |
ewkb.write_u32::<LittleEndian>(SRID_WGS84 as u32).unwrap(); | |
ewkb.write_f64::<LittleEndian>(self.longitude).unwrap(); | |
ewkb.write_f64::<LittleEndian>(self.latitude).unwrap(); | |
// println!("ewkb hex: {}", ewkb.iter() | |
// .map(|b| format!("{:02x}", b)) | |
// .collect::<Vec<String>>() | |
// .join(" ")); | |
ewkb | |
} | |
} | |
impl Encode<'_, Postgres> for Geolocation { | |
fn encode_by_ref( | |
&self, | |
buf: &mut PgArgumentBuffer, | |
) -> Result<IsNull, Box<dyn Error + Send + Sync>> { | |
let ewkb = self.to_postgis_extended_well_known_bytes_little_endian(); | |
buf.extend_from_slice(&ewkb); | |
Ok(IsNull::No) | |
} | |
fn size_hint(&self) -> usize { | |
EWKB_GEOGRAPHY_POINT_2D_BYTE_COUNT | |
} | |
} | |
impl<'r> Type<Postgres> for Geolocation { | |
fn type_info() -> PgTypeInfo { | |
PgTypeInfo::with_name("geography") | |
} | |
} | |
impl<'r> sqlx::Decode<'r, Postgres> for Geolocation { | |
fn decode(value: PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> { | |
let mut buf = value.as_bytes()?; | |
// println!("buf hex: {}", buf.iter() | |
// .map(|b| format!("{:02x}", b)) | |
// .collect::<Vec<String>>() | |
// .join(" ")); | |
let endian_flag = buf.read_u8()?; | |
let (longitude, latitude) = match endian_flag { | |
1 => { | |
// Little endian | |
let _ewkb_type = buf.read_u32::<LittleEndian>()?; | |
let _srid = buf.read_u32::<LittleEndian>()?; | |
let longitude = buf.read_f64::<LittleEndian>()?; | |
let latitude = buf.read_f64::<LittleEndian>()?; | |
(longitude, latitude) | |
} | |
0 => { | |
// Big endian | |
let _ewkb_type = buf.read_u32::<BigEndian>()?; | |
let _srid = buf.read_u32::<BigEndian>()?; | |
let longitude = buf.read_f64::<BigEndian>()?; | |
let latitude = buf.read_f64::<BigEndian>()?; | |
(longitude, latitude) | |
} | |
err_endian => return Err(format!("Invalid endian flag: {}", err_endian).into()), | |
}; | |
Ok(Geolocation { | |
latitude, | |
longitude, | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment