use geo_types::Geometry;
use crate::error::{Error, Result};
use crate::geom::{self, Geom};
use crate::gpb;
pub fn st_force_2d(bytes: &[u8]) -> Result<Vec<u8>> {
if let Some(flat) = crate::functions::surface::force_2d(bytes)? {
return Ok(flat);
}
let mut geom = geom::decode_auto(bytes)?;
geom.has_zm = false; geom::encode_canonical_gpb(&geom, "ST_Force2D")
}
pub fn st_force_3d(bytes: &[u8], z: f64) -> Result<Vec<u8>> {
const FUNC: &str = "ST_Force3D";
if geom::has_z_encoded(bytes)? {
return crate::coords::map_coords(bytes, &mut |_| {});
}
if let Some(kind) = geom::surface_kind(bytes) {
return Err(Error::Unsupported {
func: FUNC,
reason: format!(
"cannot add a Z to a 2D {}: kenro writes surface collections \
through byte-level paths only, and this one would have to \
rebuild the patch encoding",
kind.name()
),
});
}
let g = geom::decode_auto(bytes)?;
if geom::is_empty(&g.geometry) {
return geom::encode_canonical_gpb(&g, FUNC);
}
let wkb = crate::coords::write_wkb_z(&g.geometry, &crate::coords::ZIndex::constant(z), FUNC)?;
Ok(crate::gpb::write_gpb(&wkb, g.srid, None, false))
}
pub fn st_as_ewkt(bytes: &[u8]) -> Result<String> {
let geom = geom::decode_auto(bytes)?;
let wkt = geom::encode_wkt(&geom, "ST_AsEWKT")?;
Ok(if geom.srid > 0 {
format!("SRID={};{}", geom.srid, wkt)
} else {
wkt
})
}
pub fn st_geom_from_ewkt(text: &str) -> Result<Vec<u8>> {
let (srid, wkt) = split_ewkt(text)?;
let geom = geom::decode_wkt(wkt, srid)?;
geom::encode_canonical_gpb(&geom, "ST_GeomFromEWKT")
}
fn split_ewkt(text: &str) -> Result<(i32, &str)> {
let trimmed = text.trim_start();
let Some(rest) = trimmed
.strip_prefix("SRID=")
.or_else(|| trimmed.strip_prefix("srid="))
else {
return Ok((0, text));
};
let Some((digits, wkt)) = rest.split_once(';') else {
return Err(Error::InvalidWkt(
"EWKT SRID prefix is missing its ';'".into(),
));
};
let srid = digits
.trim()
.parse::<i32>()
.map_err(|_| Error::InvalidWkt(format!("EWKT SRID prefix {digits:?} is not an integer")))?;
Ok((srid, wkt))
}
pub fn st_as_ewkb(bytes: &[u8]) -> Result<Vec<u8>> {
let geom = geom::decode_auto(bytes)?;
let wkb = geom::encode_wkb(&geom, "ST_AsEWKB")?;
if geom.srid <= 0 {
return Ok(wkb);
}
if wkb.len() < 5 || wkb[0] != 0x01 {
return Err(Error::Unsupported {
func: "ST_AsEWKB",
reason: "expected little-endian WKB from the encoder".into(),
});
}
let mut out = Vec::with_capacity(wkb.len() + 4);
out.push(wkb[0]);
let mut type_word = u32::from_le_bytes([wkb[1], wkb[2], wkb[3], wkb[4]]);
type_word |= 0x2000_0000; out.extend_from_slice(&type_word.to_le_bytes());
out.extend_from_slice(&geom.srid.to_le_bytes());
out.extend_from_slice(&wkb[5..]);
Ok(out)
}
pub fn st_as_hex_ewkb(bytes: &[u8]) -> Result<String> {
let ewkb = st_as_ewkb(bytes)?;
let mut out = String::with_capacity(ewkb.len() * 2);
for byte in ewkb {
use std::fmt::Write;
let _ = write!(out, "{byte:02X}");
}
Ok(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Expect {
Point,
LineString,
Polygon,
MultiPoint,
MultiLineString,
MultiPolygon,
}
impl Expect {
fn matches(self, g: &Geometry<f64>) -> bool {
matches!(
(self, g),
(Expect::Point, Geometry::Point(_))
| (Expect::LineString, Geometry::LineString(_))
| (Expect::Polygon, Geometry::Polygon(_))
| (Expect::MultiPoint, Geometry::MultiPoint(_))
| (Expect::MultiLineString, Geometry::MultiLineString(_))
| (Expect::MultiPolygon, Geometry::MultiPolygon(_))
)
}
}
pub fn from_text_typed(wkt: &str, srid: Option<i32>, expect: Expect) -> Result<Option<Vec<u8>>> {
let geom = geom::decode_wkt(wkt, srid.unwrap_or(0))?;
typed(geom, expect, "ST_GeomFromText")
}
pub fn from_wkb_typed(bytes: &[u8], srid: Option<i32>, expect: Expect) -> Result<Option<Vec<u8>>> {
let mut geom = if gpb::is_gpb(bytes) {
geom::decode_gpb(bytes)?.1
} else {
geom::decode_wkb(bytes, srid)?
};
if let Some(srid) = srid {
geom.srid = srid;
}
typed(geom, expect, "ST_GeomFromWKB")
}
fn typed(geom: Geom, expect: Expect, func: &'static str) -> Result<Option<Vec<u8>>> {
if !expect.matches(&geom.geometry) {
return Ok(None);
}
geom::encode_canonical_gpb(&geom, func).map(Some)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::functions::io::{st_as_text, st_geom_from_text};
#[test]
fn ewkt_roundtrip_carries_srid_and_omits_zero() {
let with = st_geom_from_ewkt("SRID=4326;POINT(1 2)").unwrap();
assert_eq!(st_as_ewkt(&with).unwrap(), "SRID=4326;POINT(1 2)");
let without = st_geom_from_ewkt("POINT(1 2)").unwrap();
assert_eq!(st_as_ewkt(&without).unwrap(), "POINT(1 2)");
}
#[test]
fn ewkt_rejects_a_malformed_prefix() {
assert!(st_geom_from_ewkt("SRID=abc;POINT(1 2)").is_err());
assert!(st_geom_from_ewkt("SRID=4326 POINT(1 2)").is_err());
}
#[test]
fn hex_ewkb_matches_postgis_byte_for_byte() {
let g = st_geom_from_text("POINT(1 2)", Some(4326)).unwrap();
assert_eq!(
st_as_hex_ewkb(&g).unwrap(),
"0101000020E6100000000000000000F03F0000000000000040"
);
let plain = st_geom_from_text("POINT(1 2)", None).unwrap();
assert_eq!(
st_as_hex_ewkb(&plain).unwrap(),
"0101000000000000000000F03F0000000000000040"
);
}
#[test]
fn typed_constructors_return_null_on_a_type_mismatch() {
assert!(
from_text_typed("POINT(1 2)", None, Expect::Point)
.unwrap()
.is_some()
);
assert!(
from_text_typed("LINESTRING(0 0,1 1)", None, Expect::Point)
.unwrap()
.is_none()
);
}
#[test]
fn typed_constructors_keep_the_srid_argument() {
let blob = from_text_typed("POINT(1 2)", Some(3857), Expect::Point)
.unwrap()
.unwrap();
assert_eq!(crate::functions::io::st_srid(&blob).unwrap(), 3857);
}
#[test]
fn force_2d_lets_a_3d_payload_through_the_encoders() {
let mut wkb = vec![0x01];
wkb.extend_from_slice(&1001u32.to_le_bytes());
for v in [1.0f64, 2.0, 3.0] {
wkb.extend_from_slice(&v.to_le_bytes());
}
assert!(st_as_text(&wkb).is_err());
let flat = st_force_2d(&wkb).unwrap();
assert_eq!(st_as_text(&flat).unwrap(), "POINT(1 2)");
}
}