Skip to content

Instantly share code, notes, and snippets.

@malteneuss
Created April 20, 2025 15:06
Show Gist options
  • Save malteneuss/fed6625bba30d43422bee1b531a06309 to your computer and use it in GitHub Desktop.
Save malteneuss/fed6625bba30d43422bee1b531a06309 to your computer and use it in GitHub Desktop.
SqlxPostgresGeolocation
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