use crate::error::PostgisError;
use crate::geometry::{Geometry, LineString, Point, Polygon};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpatialIndexType {
Gist,
SpGist,
Brin,
}
impl SpatialIndexType {
pub fn as_sql_method(&self) -> &'static str {
match self {
SpatialIndexType::Gist => "GIST",
SpatialIndexType::SpGist => "SPGIST",
SpatialIndexType::Brin => "BRIN",
}
}
}
#[derive(Debug, Clone)]
pub struct SpatialIndexDef {
pub index_name: String,
pub table: String,
pub column: String,
pub index_type: SpatialIndexType,
pub unique: bool,
pub concurrently: bool,
}
impl SpatialIndexDef {
pub fn new_gist(table: &str, column: &str) -> Self {
Self {
index_name: format!("idx_{}_{}_gist", table, column),
table: table.to_string(),
column: column.to_string(),
index_type: SpatialIndexType::Gist,
unique: false,
concurrently: false,
}
}
pub fn with_concurrently(mut self) -> Self {
self.concurrently = true;
self
}
pub fn to_create_sql(&self) -> String {
let unique_str = if self.unique { "UNIQUE " } else { "" };
let concurrently_str = if self.concurrently {
"CONCURRENTLY "
} else {
""
};
format!(
"CREATE {}INDEX {} {}ON {} USING {}(\"{}\")",
unique_str,
self.index_name,
concurrently_str,
self.table,
self.index_type.as_sql_method(),
self.column
)
}
pub fn to_drop_sql(&self) -> String {
let concurrently_str = if self.concurrently {
"CONCURRENTLY "
} else {
""
};
format!("DROP INDEX {}{}", concurrently_str, self.index_name)
}
pub fn to_reindex_sql(&self) -> String {
format!("REINDEX INDEX {}", self.index_name)
}
}
#[derive(Debug, Clone, Default)]
pub struct SpatialIndexRegistry {
indexes: HashMap<String, SpatialIndexDef>,
}
impl SpatialIndexRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, def: SpatialIndexDef) -> Result<(), PostgisError> {
if self.indexes.contains_key(&def.index_name) {
return Err(PostgisError::Query(format!(
"spatial index already exists: {}",
def.index_name
)));
}
self.indexes.insert(def.index_name.clone(), def);
Ok(())
}
pub fn unregister(&mut self, index_name: &str) -> Result<SpatialIndexDef, PostgisError> {
self.indexes
.remove(index_name)
.ok_or_else(|| PostgisError::Query(format!("spatial index not found: {}", index_name)))
}
pub fn exists(&self, index_name: &str) -> bool {
self.indexes.contains_key(index_name)
}
pub fn list_for_table(&self, table: &str) -> Vec<&SpatialIndexDef> {
self.indexes
.values()
.filter(|def| def.table == table)
.collect()
}
pub fn list_all(&self) -> Vec<&SpatialIndexDef> {
self.indexes.values().collect()
}
pub fn reindex_all_sql(&self) -> Vec<String> {
self.indexes
.values()
.map(|def| def.to_reindex_sql())
.collect()
}
}
pub trait SpatialRelationsExt {
fn st_crosses(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_touches(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_overlaps(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_disjoint(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_equals(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_covers(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
fn st_covered_by(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError>;
}
pub struct MemorySpatialRelations;
impl MemorySpatialRelations {
pub fn new() -> Self {
Self
}
}
impl Default for MemorySpatialRelations {
fn default() -> Self {
Self::new()
}
}
impl SpatialRelationsExt for MemorySpatialRelations {
fn st_crosses(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match (g1, g2) {
(Geometry::LineString(ls), Geometry::Polygon(poly)) => {
check_srid(ls.srid, poly.srid)?;
Ok(line_crosses_polygon(ls, poly))
}
(Geometry::Polygon(poly), Geometry::LineString(ls)) => {
check_srid(ls.srid, poly.srid)?;
Ok(line_crosses_polygon(ls, poly))
}
_ => Err(PostgisError::Unsupported(format!(
"st_crosses not supported for {}-{} in memory impl",
g1.type_name(),
g2.type_name()
))),
}
}
fn st_touches(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match (g1, g2) {
(Geometry::Polygon(poly), Geometry::Point(p)) => {
check_srid(poly.srid, p.srid)?;
Ok(point_on_polygon_boundary(p, poly))
}
(Geometry::Point(p), Geometry::Polygon(poly)) => {
check_srid(poly.srid, p.srid)?;
Ok(point_on_polygon_boundary(p, poly))
}
(Geometry::Point(p1), Geometry::Point(p2)) => {
check_srid(p1.srid, p2.srid)?;
Ok(p1.euclidean_distance(p2) == 0.0)
}
_ => Err(PostgisError::Unsupported(format!(
"st_touches not supported for {}-{} in memory impl",
g1.type_name(),
g2.type_name()
))),
}
}
fn st_overlaps(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match (g1, g2) {
(Geometry::Polygon(p1), Geometry::Polygon(p2)) => {
check_srid(p1.srid, p2.srid)?;
let intersects = polygons_intersect(p1, p2);
let p1_contains_p2 = polygon_contains_polygon(p1, p2);
let p2_contains_p1 = polygon_contains_polygon(p2, p1);
Ok(intersects && !p1_contains_p2 && !p2_contains_p1)
}
_ => Err(PostgisError::Unsupported(format!(
"st_overlaps not supported for {}-{} in memory impl",
g1.type_name(),
g2.type_name()
))),
}
}
fn st_disjoint(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match (g1, g2) {
(Geometry::Point(p1), Geometry::Point(p2)) => {
check_srid(p1.srid, p2.srid)?;
Ok(p1.x != p2.x || p1.y != p2.y)
}
(Geometry::Polygon(poly), Geometry::Point(p)) => {
check_srid(poly.srid, p.srid)?;
Ok(!poly.contains_point(p))
}
(Geometry::Point(p), Geometry::Polygon(poly)) => {
check_srid(poly.srid, p.srid)?;
Ok(!poly.contains_point(p))
}
_ => Err(PostgisError::Unsupported(format!(
"st_disjoint not supported for {}-{} in memory impl",
g1.type_name(),
g2.type_name()
))),
}
}
fn st_equals(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
check_srid(g1.srid(), g2.srid())?;
Ok(g1 == g2)
}
fn st_covers(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
match (g1, g2) {
(Geometry::Polygon(poly), Geometry::Point(p)) => {
check_srid(poly.srid, p.srid)?;
Ok(poly.contains_point(p) || point_on_polygon_boundary(p, poly))
}
_ => Err(PostgisError::Unsupported(format!(
"st_covers not supported for {}-{} in memory impl",
g1.type_name(),
g2.type_name()
))),
}
}
fn st_covered_by(&self, g1: &Geometry, g2: &Geometry) -> Result<bool, PostgisError> {
self.st_covers(g2, g1)
}
}
pub trait SpatialAggregateExt {
fn st_collect(&self, geometries: &[Geometry]) -> Result<Geometry, PostgisError>;
fn st_envelope(&self, geom: &Geometry) -> Result<Geometry, PostgisError>;
fn st_convex_hull(&self, geom: &Geometry) -> Result<Geometry, PostgisError>;
fn st_centroid(&self, geom: &Geometry) -> Result<Point, PostgisError>;
}
pub struct MemorySpatialAggregate;
impl MemorySpatialAggregate {
pub fn new() -> Self {
Self
}
}
impl Default for MemorySpatialAggregate {
fn default() -> Self {
Self::new()
}
}
impl SpatialAggregateExt for MemorySpatialAggregate {
fn st_collect(&self, geometries: &[Geometry]) -> Result<Geometry, PostgisError> {
if geometries.is_empty() {
return Err(PostgisError::InvalidGeometry(
"st_collect requires at least one geometry".to_string(),
));
}
if geometries.len() == 1 {
return Ok(geometries[0].clone());
}
let srid = geometries[0].srid();
for g in geometries.iter().skip(1) {
check_srid(srid, g.srid())?;
}
match &geometries[0] {
Geometry::Point(_) => {
let mut points = Vec::new();
for g in geometries {
match g {
Geometry::Point(p) => points.push(*p),
_ => {
return Err(PostgisError::TypeMismatch {
expected: "Point",
actual: g.type_name(),
})
}
}
}
Ok(Geometry::MultiPoint(points))
}
Geometry::LineString(_) => {
let mut lines = Vec::new();
for g in geometries {
match g {
Geometry::LineString(ls) => lines.push(ls.clone()),
_ => {
return Err(PostgisError::TypeMismatch {
expected: "LineString",
actual: g.type_name(),
})
}
}
}
Ok(Geometry::MultiLineString(lines))
}
Geometry::Polygon(_) => {
let mut polys = Vec::new();
for g in geometries {
match g {
Geometry::Polygon(p) => polys.push(p.clone()),
_ => {
return Err(PostgisError::TypeMismatch {
expected: "Polygon",
actual: g.type_name(),
})
}
}
}
Ok(Geometry::MultiPolygon(polys))
}
_ => Err(PostgisError::Unsupported(format!(
"st_collect not supported for {} as first geometry",
geometries[0].type_name()
))),
}
}
fn st_envelope(&self, geom: &Geometry) -> Result<Geometry, PostgisError> {
let (min_x, min_y, max_x, max_y) = compute_bounding_box(geom)?;
let srid = geom.srid();
let ring = vec![
Point::with_srid(min_x, min_y, srid),
Point::with_srid(max_x, min_y, srid),
Point::with_srid(max_x, max_y, srid),
Point::with_srid(min_x, max_y, srid),
Point::with_srid(min_x, min_y, srid), ];
Ok(Geometry::Polygon(Polygon::new(ring)))
}
fn st_convex_hull(&self, geom: &Geometry) -> Result<Geometry, PostgisError> {
let points = collect_all_points(geom)?;
if points.len() < 3 {
return Err(PostgisError::InvalidGeometry(format!(
"convex hull requires at least 3 points, got {}",
points.len()
)));
}
let hull = convex_hull(&points);
if hull.len() < 3 {
return Err(PostgisError::InvalidGeometry(
"convex hull degenerate (all points collinear)".to_string(),
));
}
let srid = geom.srid();
let hull_points: Vec<Point> = hull
.into_iter()
.map(|(x, y)| Point::with_srid(x, y, srid))
.collect();
Ok(Geometry::Polygon(Polygon::new(hull_points)))
}
fn st_centroid(&self, geom: &Geometry) -> Result<Point, PostgisError> {
let points = collect_all_points(geom)?;
if points.is_empty() {
return Err(PostgisError::InvalidGeometry(
"centroid requires at least one point".to_string(),
));
}
let srid = geom.srid();
let sum_x: f64 = points.iter().map(|(x, _)| x).sum();
let sum_y: f64 = points.iter().map(|(_, y)| y).sum();
let count = points.len() as f64;
Ok(Point::with_srid(sum_x / count, sum_y / count, srid))
}
}
pub mod srid {
pub const WGS84: i32 = 4326;
pub const WEB_MERCATOR: i32 = 3857;
pub const UTM_ZONE_50N: i32 = 32650;
pub const CGCS2000: i32 = 4490;
}
pub trait CoordinateTransformExt {
fn st_transform(&self, geom: &Geometry, target_srid: i32) -> Result<Geometry, PostgisError>;
fn srid_name(&self, srid: i32) -> &'static str;
fn is_srid_supported(&self, srid: i32) -> bool;
}
pub struct MemoryCoordTransform;
impl MemoryCoordTransform {
pub fn new() -> Self {
Self
}
fn wgs84_to_mercator(lon: f64, lat: f64) -> (f64, f64) {
const R: f64 = 6_378_137.0; let x = R * lon.to_radians();
let y = R
* (std::f64::consts::FRAC_PI_4 + lat.to_radians() / 2.0)
.tan()
.ln();
(x, y)
}
fn mercator_to_wgs84(x: f64, y: f64) -> (f64, f64) {
const R: f64 = 6_378_137.0;
let lon = (x / R).to_degrees();
let lat = (2.0 * (y / R).exp().atan() - std::f64::consts::FRAC_PI_2).to_degrees();
(lon, lat)
}
fn transform_point(p: &Point, target_srid: i32) -> Result<Point, PostgisError> {
match (p.srid, target_srid) {
(srid::WGS84, srid::WEB_MERCATOR) => {
let (x, y) = Self::wgs84_to_mercator(p.x, p.y);
Ok(Point::with_srid(x, y, target_srid))
}
(srid::WEB_MERCATOR, srid::WGS84) => {
let (lon, lat) = Self::mercator_to_wgs84(p.x, p.y);
Ok(Point::with_srid(lon, lat, target_srid))
}
(s, t) if s == t => Ok(*p),
_ => Err(PostgisError::Unsupported(format!(
"st_transform not supported for SRID {} -> {} in memory impl",
p.srid, target_srid
))),
}
}
}
impl Default for MemoryCoordTransform {
fn default() -> Self {
Self::new()
}
}
impl CoordinateTransformExt for MemoryCoordTransform {
fn st_transform(&self, geom: &Geometry, target_srid: i32) -> Result<Geometry, PostgisError> {
match geom {
Geometry::Point(p) => {
let transformed = Self::transform_point(p, target_srid)?;
Ok(Geometry::Point(transformed))
}
Geometry::LineString(ls) => {
let mut new_points = Vec::with_capacity(ls.points.len());
for p in &ls.points {
new_points.push(Self::transform_point(p, target_srid)?);
}
Ok(Geometry::LineString(LineString {
points: new_points,
srid: target_srid,
}))
}
Geometry::Polygon(poly) => {
let mut new_rings = Vec::with_capacity(poly.rings.len());
for ring in &poly.rings {
let mut new_ring = Vec::with_capacity(ring.len());
for p in ring {
new_ring.push(Self::transform_point(p, target_srid)?);
}
new_rings.push(new_ring);
}
Ok(Geometry::Polygon(Polygon {
rings: new_rings,
srid: target_srid,
}))
}
Geometry::MultiPoint(pts) => {
let mut new_pts = Vec::with_capacity(pts.len());
for p in pts {
new_pts.push(Self::transform_point(p, target_srid)?);
}
Ok(Geometry::MultiPoint(new_pts))
}
_ => Err(PostgisError::Unsupported(format!(
"st_transform not supported for {} in memory impl",
geom.type_name()
))),
}
}
fn srid_name(&self, srid: i32) -> &'static str {
match srid {
srid::WGS84 => "WGS84",
srid::WEB_MERCATOR => "Web Mercator",
srid::UTM_ZONE_50N => "UTM Zone 50N",
srid::CGCS2000 => "CGCS2000",
_ => "Unknown",
}
}
fn is_srid_supported(&self, srid: i32) -> bool {
matches!(srid, srid::WGS84 | srid::WEB_MERCATOR)
}
}
fn check_srid(srid1: i32, srid2: i32) -> Result<(), PostgisError> {
if srid1 != srid2 {
Err(PostgisError::SridMismatch {
expected: srid1,
actual: srid2,
})
} else {
Ok(())
}
}
fn line_crosses_polygon(ls: &LineString, poly: &Polygon) -> bool {
let outer = &poly.rings[0];
for w in ls.points.windows(2) {
for j in 0..outer.len() {
let k = (j + 1) % outer.len();
if segments_intersect(&w[0], &w[1], &outer[j], &outer[k]) {
return true;
}
}
}
false
}
fn point_on_polygon_boundary(p: &Point, poly: &Polygon) -> bool {
for ring in &poly.rings {
for j in 0..ring.len() {
let k = (j + 1) % ring.len();
if point_on_segment(p, &ring[j], &ring[k]) {
return true;
}
}
}
false
}
fn point_on_segment(p: &Point, a: &Point, b: &Point) -> bool {
let cross = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
if cross.abs() > 1e-10 {
return false;
}
let min_x = a.x.min(b.x);
let max_x = a.x.max(b.x);
let min_y = a.y.min(b.y);
let max_y = a.y.max(b.y);
p.x >= min_x - 1e-10 && p.x <= max_x + 1e-10 && p.y >= min_y - 1e-10 && p.y <= max_y + 1e-10
}
fn segments_intersect(p1: &Point, p2: &Point, p3: &Point, p4: &Point) -> bool {
let d1 = cross_product(p3, p4, p1);
let d2 = cross_product(p3, p4, p2);
let d3 = cross_product(p1, p2, p3);
let d4 = cross_product(p1, p2, p4);
if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
&& ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
{
return true;
}
if d1.abs() < 1e-10 && point_on_segment(p1, p3, p4) {
return true;
}
if d2.abs() < 1e-10 && point_on_segment(p2, p3, p4) {
return true;
}
if d3.abs() < 1e-10 && point_on_segment(p3, p1, p2) {
return true;
}
if d4.abs() < 1e-10 && point_on_segment(p4, p1, p2) {
return true;
}
false
}
fn cross_product(p3: &Point, p4: &Point, p1: &Point) -> f64 {
(p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)
}
fn polygons_intersect(p1: &Polygon, p2: &Polygon) -> bool {
if p2.rings.is_empty() || p1.rings.is_empty() {
return false;
}
for p in &p2.rings[0] {
if p1.contains_point(p) {
return true;
}
}
for p in &p1.rings[0] {
if p2.contains_point(p) {
return true;
}
}
false
}
fn polygon_contains_polygon(p1: &Polygon, p2: &Polygon) -> bool {
if p2.rings.is_empty() {
return false;
}
p2.rings[0].iter().all(|p| p1.contains_point(p))
}
fn compute_bounding_box(geom: &Geometry) -> Result<(f64, f64, f64, f64), PostgisError> {
let points = collect_all_points(geom)?;
if points.is_empty() {
return Err(PostgisError::InvalidGeometry(
"cannot compute bounding box of empty geometry".to_string(),
));
}
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for (x, y) in &points {
min_x = min_x.min(*x);
min_y = min_y.min(*y);
max_x = max_x.max(*x);
max_y = max_y.max(*y);
}
Ok((min_x, min_y, max_x, max_y))
}
fn collect_all_points(geom: &Geometry) -> Result<Vec<(f64, f64)>, PostgisError> {
let mut points = Vec::new();
match geom {
Geometry::Point(p) => points.push((p.x, p.y)),
Geometry::LineString(ls) => {
for p in &ls.points {
points.push((p.x, p.y));
}
}
Geometry::Polygon(poly) => {
for ring in &poly.rings {
for p in ring {
points.push((p.x, p.y));
}
}
}
Geometry::MultiPoint(pts) => {
for p in pts {
points.push((p.x, p.y));
}
}
Geometry::MultiLineString(lss) => {
for ls in lss {
for p in &ls.points {
points.push((p.x, p.y));
}
}
}
Geometry::MultiPolygon(polys) => {
for poly in polys {
for ring in &poly.rings {
for p in ring {
points.push((p.x, p.y));
}
}
}
}
}
Ok(points)
}
fn convex_hull(points: &[(f64, f64)]) -> Vec<(f64, f64)> {
let mut pts: Vec<(f64, f64)> = points.to_vec();
pts.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.total_cmp(&b.1)));
pts.dedup();
let n = pts.len();
if n < 3 {
return pts;
}
let mut hull = vec![Default::default(); 2 * n];
let mut k = 0;
#[allow(clippy::needless_range_loop)]
for i in 0..n {
while k >= 2 && cross(&hull[k - 2], &hull[k - 1], &pts[i]) <= 0.0 {
k -= 1;
}
hull[k] = pts[i];
k += 1;
}
let lower = k + 1;
for i in (0..n - 1).rev() {
while k >= lower && cross(&hull[k - 2], &hull[k - 1], &pts[i]) <= 0.0 {
k -= 1;
}
hull[k] = pts[i];
k += 1;
}
hull.truncate(k - 1);
hull
}
fn cross(o: &(f64, f64), a: &(f64, f64), b: &(f64, f64)) -> f64 {
(a.0 - o.0) * (b.1 - o.1) - (a.1 - o.1) * (b.0 - o.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spatial_index_type_as_sql() {
assert_eq!(SpatialIndexType::Gist.as_sql_method(), "GIST");
assert_eq!(SpatialIndexType::SpGist.as_sql_method(), "SPGIST");
assert_eq!(SpatialIndexType::Brin.as_sql_method(), "BRIN");
}
#[test]
fn test_spatial_index_def_create_sql() {
let def = SpatialIndexDef::new_gist("cities", "geom");
let sql = def.to_create_sql();
assert!(sql.contains("CREATE INDEX"));
assert!(sql.contains("idx_cities_geom_gist"));
assert!(sql.contains("USING GIST"));
assert!(sql.contains("\"geom\""));
}
#[test]
fn test_spatial_index_def_concurrently() {
let def = SpatialIndexDef::new_gist("cities", "geom").with_concurrently();
let sql = def.to_create_sql();
assert!(sql.contains("CONCURRENTLY"));
}
#[test]
fn test_spatial_index_def_drop_sql() {
let def = SpatialIndexDef::new_gist("cities", "geom");
let sql = def.to_drop_sql();
assert!(sql.contains("DROP INDEX"));
assert!(sql.contains("idx_cities_geom_gist"));
}
#[test]
fn test_spatial_index_def_reindex_sql() {
let def = SpatialIndexDef::new_gist("cities", "geom");
let sql = def.to_reindex_sql();
assert!(sql.contains("REINDEX INDEX"));
}
#[test]
fn test_spatial_index_registry_register_and_exists() {
let mut reg = SpatialIndexRegistry::new();
let def = SpatialIndexDef::new_gist("cities", "geom");
reg.register(def).unwrap();
assert!(reg.exists("idx_cities_geom_gist"));
}
#[test]
fn test_spatial_index_registry_duplicate_fails() {
let mut reg = SpatialIndexRegistry::new();
let def = SpatialIndexDef::new_gist("cities", "geom");
reg.register(def).unwrap();
let def2 = SpatialIndexDef::new_gist("cities", "geom");
let result = reg.register(def2);
assert!(result.is_err());
}
#[test]
fn test_spatial_index_registry_unregister() {
let mut reg = SpatialIndexRegistry::new();
let def = SpatialIndexDef::new_gist("cities", "geom");
reg.register(def).unwrap();
let removed = reg.unregister("idx_cities_geom_gist").unwrap();
assert_eq!(removed.table, "cities");
assert!(!reg.exists("idx_cities_geom_gist"));
}
#[test]
fn test_spatial_index_registry_list_for_table() {
let mut reg = SpatialIndexRegistry::new();
reg.register(SpatialIndexDef::new_gist("cities", "geom"))
.unwrap();
reg.register(SpatialIndexDef::new_gist("cities", "bbox"))
.unwrap();
reg.register(SpatialIndexDef::new_gist("roads", "geom"))
.unwrap();
let city_indexes = reg.list_for_table("cities");
assert_eq!(city_indexes.len(), 2);
let road_indexes = reg.list_for_table("roads");
assert_eq!(road_indexes.len(), 1);
}
#[test]
fn test_spatial_index_registry_reindex_all() {
let mut reg = SpatialIndexRegistry::new();
reg.register(SpatialIndexDef::new_gist("t1", "g1")).unwrap();
reg.register(SpatialIndexDef::new_gist("t2", "g2")).unwrap();
let sqls = reg.reindex_all_sql();
assert_eq!(sqls.len(), 2);
assert!(sqls.iter().all(|s| s.contains("REINDEX")));
}
#[test]
fn test_st_disjoint_points() {
let rel = MemorySpatialRelations::new();
let p1 = Geometry::Point(Point::new(0.0, 0.0));
let p2 = Geometry::Point(Point::new(1.0, 1.0));
assert!(rel.st_disjoint(&p1, &p2).unwrap());
}
#[test]
fn test_st_disjoint_same_point() {
let rel = MemorySpatialRelations::new();
let p1 = Geometry::Point(Point::new(0.0, 0.0));
let p2 = Geometry::Point(Point::new(0.0, 0.0));
assert!(!rel.st_disjoint(&p1, &p2).unwrap());
}
#[test]
fn test_st_disjoint_point_polygon() {
let rel = MemorySpatialRelations::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Point::new(10.0, 10.0),
Point::new(0.0, 10.0),
]));
let inside = Geometry::Point(Point::new(5.0, 5.0));
let outside = Geometry::Point(Point::new(20.0, 20.0));
assert!(!rel.st_disjoint(&inside, &poly).unwrap());
assert!(rel.st_disjoint(&outside, &poly).unwrap());
}
#[test]
fn test_st_equals_same() {
let rel = MemorySpatialRelations::new();
let p1 = Geometry::Point(Point::new(1.0, 2.0));
let p2 = Geometry::Point(Point::new(1.0, 2.0));
assert!(rel.st_equals(&p1, &p2).unwrap());
}
#[test]
fn test_st_equals_different() {
let rel = MemorySpatialRelations::new();
let p1 = Geometry::Point(Point::new(1.0, 2.0));
let p2 = Geometry::Point(Point::new(3.0, 4.0));
assert!(!rel.st_equals(&p1, &p2).unwrap());
}
#[test]
fn test_st_equals_srid_mismatch() {
let rel = MemorySpatialRelations::new();
let p1 = Geometry::Point(Point::with_srid(1.0, 2.0, 4326));
let p2 = Geometry::Point(Point::with_srid(1.0, 2.0, 3857));
let result = rel.st_equals(&p1, &p2);
assert!(matches!(result, Err(PostgisError::SridMismatch { .. })));
}
#[test]
fn test_st_covers_point_inside() {
let rel = MemorySpatialRelations::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Point::new(10.0, 10.0),
Point::new(0.0, 10.0),
]));
let p = Geometry::Point(Point::new(5.0, 5.0));
assert!(rel.st_covers(&poly, &p).unwrap());
}
#[test]
fn test_st_covers_point_outside() {
let rel = MemorySpatialRelations::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Point::new(10.0, 10.0),
Point::new(0.0, 10.0),
]));
let p = Geometry::Point(Point::new(20.0, 20.0));
assert!(!rel.st_covers(&poly, &p).unwrap());
}
#[test]
fn test_st_covered_by() {
let rel = MemorySpatialRelations::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(10.0, 0.0),
Point::new(10.0, 10.0),
Point::new(0.0, 10.0),
]));
let p = Geometry::Point(Point::new(5.0, 5.0));
assert!(rel.st_covered_by(&p, &poly).unwrap());
}
#[test]
fn test_st_collect_points() {
let agg = MemorySpatialAggregate::new();
let pts = vec![
Geometry::Point(Point::new(0.0, 0.0)),
Geometry::Point(Point::new(1.0, 1.0)),
];
let result = agg.st_collect(&pts).unwrap();
match result {
Geometry::MultiPoint(mp) => assert_eq!(mp.len(), 2),
_ => panic!("expected MultiPoint"),
}
}
#[test]
fn test_st_collect_single() {
let agg = MemorySpatialAggregate::new();
let pts = vec![Geometry::Point(Point::new(0.0, 0.0))];
let result = agg.st_collect(&pts).unwrap();
assert!(matches!(result, Geometry::Point(_)));
}
#[test]
fn test_st_collect_empty_fails() {
let agg = MemorySpatialAggregate::new();
let result = agg.st_collect(&[]);
assert!(result.is_err());
}
#[test]
fn test_st_envelope_polygon() {
let agg = MemorySpatialAggregate::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(1.0, 2.0),
Point::new(5.0, 1.0),
Point::new(4.0, 8.0),
Point::new(0.0, 6.0),
]));
let envelope = agg.st_envelope(&poly).unwrap();
let (min_x, min_y, max_x, max_y) = compute_bounding_box(&envelope).unwrap();
assert!((min_x - 0.0).abs() < 1e-10);
assert!((min_y - 1.0).abs() < 1e-10);
assert!((max_x - 5.0).abs() < 1e-10);
assert!((max_y - 8.0).abs() < 1e-10);
assert!(matches!(envelope, Geometry::Polygon(_)));
}
#[test]
fn test_st_envelope_point() {
let agg = MemorySpatialAggregate::new();
let p = Geometry::Point(Point::new(3.0, 7.0));
let envelope = agg.st_envelope(&p).unwrap();
match envelope {
Geometry::Polygon(poly) => {
assert_eq!(poly.rings[0].len(), 5); }
_ => panic!("expected Polygon"),
}
}
#[test]
fn test_st_centroid_point() {
let agg = MemorySpatialAggregate::new();
let p = Geometry::Point(Point::new(3.0, 7.0));
let centroid = agg.st_centroid(&p).unwrap();
assert!((centroid.x - 3.0).abs() < 1e-10);
assert!((centroid.y - 7.0).abs() < 1e-10);
}
#[test]
fn test_st_centroid_polygon() {
let agg = MemorySpatialAggregate::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(4.0, 0.0),
Point::new(4.0, 4.0),
Point::new(0.0, 4.0),
]));
let centroid = agg.st_centroid(&poly).unwrap();
assert!((centroid.x - 2.0).abs() < 1e-10);
assert!((centroid.y - 2.0).abs() < 1e-10);
}
#[test]
fn test_st_convex_hull() {
let agg = MemorySpatialAggregate::new();
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(4.0, 0.0),
Point::new(4.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 4.0),
Point::new(0.0, 4.0),
]));
let hull = agg.st_convex_hull(&poly).unwrap();
match hull {
Geometry::Polygon(p) => {
assert!(p.rings[0].len() >= 3);
}
_ => panic!("expected Polygon"),
}
}
#[test]
fn test_srid_names() {
let ct = MemoryCoordTransform::new();
assert_eq!(ct.srid_name(srid::WGS84), "WGS84");
assert_eq!(ct.srid_name(srid::WEB_MERCATOR), "Web Mercator");
assert_eq!(ct.srid_name(9999), "Unknown");
}
#[test]
fn test_is_srid_supported() {
let ct = MemoryCoordTransform::new();
assert!(ct.is_srid_supported(srid::WGS84));
assert!(ct.is_srid_supported(srid::WEB_MERCATOR));
assert!(!ct.is_srid_supported(9999));
}
#[test]
fn test_st_transform_wgs84_to_mercator() {
let ct = MemoryCoordTransform::new();
let p = Geometry::Point(Point::with_srid(116.397, 39.908, srid::WGS84));
let transformed = ct.st_transform(&p, srid::WEB_MERCATOR).unwrap();
match transformed {
Geometry::Point(mp) => {
assert_eq!(mp.srid, srid::WEB_MERCATOR);
assert!(mp.x > 12_900_000.0 && mp.x < 13_000_000.0);
assert!(mp.y > 4_800_000.0 && mp.y < 4_900_000.0);
}
_ => panic!("expected Point"),
}
}
#[test]
fn test_st_transform_mercator_to_wgs84() {
let ct = MemoryCoordTransform::new();
let original = Geometry::Point(Point::with_srid(116.397, 39.908, srid::WGS84));
let mercator = ct.st_transform(&original, srid::WEB_MERCATOR).unwrap();
let back = ct.st_transform(&mercator, srid::WGS84).unwrap();
match back {
Geometry::Point(p) => {
assert_eq!(p.srid, srid::WGS84);
assert!((p.x - 116.397).abs() < 1e-6);
assert!((p.y - 39.908).abs() < 1e-6);
}
_ => panic!("expected Point"),
}
}
#[test]
fn test_st_transform_same_srid() {
let ct = MemoryCoordTransform::new();
let p = Geometry::Point(Point::with_srid(116.0, 39.0, srid::WGS84));
let transformed = ct.st_transform(&p, srid::WGS84).unwrap();
match transformed {
Geometry::Point(tp) => {
assert!((tp.x - 116.0).abs() < 1e-10);
assert!((tp.y - 39.0).abs() < 1e-10);
}
_ => panic!("expected Point"),
}
}
#[test]
fn test_st_transform_unsupported_srid() {
let ct = MemoryCoordTransform::new();
let p = Geometry::Point(Point::with_srid(116.0, 39.0, srid::WGS84));
let result = ct.st_transform(&p, 9999);
assert!(matches!(result, Err(PostgisError::Unsupported(_))));
}
#[test]
fn test_st_transform_linestring() {
let ct = MemoryCoordTransform::new();
let ls = Geometry::LineString(LineString::new(vec![
Point::with_srid(116.0, 39.0, srid::WGS84),
Point::with_srid(117.0, 40.0, srid::WGS84),
]));
let transformed = ct.st_transform(&ls, srid::WEB_MERCATOR).unwrap();
match transformed {
Geometry::LineString(tls) => {
assert_eq!(tls.srid, srid::WEB_MERCATOR);
assert_eq!(tls.points.len(), 2);
}
_ => panic!("expected LineString"),
}
}
#[test]
fn test_point_on_segment() {
let a = Point::new(0.0, 0.0);
let b = Point::new(10.0, 0.0);
let on = Point::new(5.0, 0.0);
let off = Point::new(5.0, 1.0);
assert!(point_on_segment(&on, &a, &b));
assert!(!point_on_segment(&off, &a, &b));
}
#[test]
fn test_segments_intersect_crossing() {
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(10.0, 10.0);
let p3 = Point::new(0.0, 10.0);
let p4 = Point::new(10.0, 0.0);
assert!(segments_intersect(&p1, &p2, &p3, &p4));
}
#[test]
fn test_segments_intersect_parallel() {
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(10.0, 0.0);
let p3 = Point::new(0.0, 5.0);
let p4 = Point::new(10.0, 5.0);
assert!(!segments_intersect(&p1, &p2, &p3, &p4));
}
#[test]
fn test_convex_hull_square() {
let points = vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(5.0, 5.0), ];
let hull = convex_hull(&points);
assert_eq!(hull.len(), 4); }
#[test]
fn test_compute_bounding_box() {
let poly = Geometry::Polygon(Polygon::new(vec![
Point::new(3.0, 7.0),
Point::new(1.0, 5.0),
Point::new(8.0, 2.0),
]));
let (min_x, min_y, max_x, max_y) = compute_bounding_box(&poly).unwrap();
assert!((min_x - 1.0).abs() < 1e-10);
assert!((min_y - 2.0).abs() < 1e-10);
assert!((max_x - 8.0).abs() < 1e-10);
assert!((max_y - 7.0).abs() < 1e-10);
}
}