1use crate::error::{QueryError, Result};
74use crate::executor::filter::Value;
75
76use geo::algorithm::{
77 area::Area, bool_ops::BooleanOps, bounding_rect::BoundingRect, buffer::Buffer,
78 centroid::Centroid, contains::Contains,
79};
80use geo::algorithm::{intersects::Intersects, relate::Relate, within::Within};
81use geo::line_measures::{Euclidean, Length};
82use geo_types::{Coord, Geometry, LineString, Polygon};
83use std::str::FromStr;
84use wkt::Wkt;
85
86const EQUALS_EPS: f64 = f64::EPSILON;
88
89pub fn evaluate_spatial_function(
99 name: &str,
100 args: &[Value],
101 _coordinate_dim: usize,
102) -> Result<Value> {
103 match name.to_ascii_uppercase().as_str() {
104 "ST_INTERSECTS" => binary_predicate(name, args, |a, b| a.intersects(b)),
106 "ST_CONTAINS" => binary_predicate(name, args, |a, b| a.contains(b)),
107 "ST_WITHIN" => binary_predicate(name, args, |a, b| a.is_within(b)),
108 "ST_DISJOINT" => binary_predicate(name, args, |a, b| !a.intersects(b)),
109 "ST_EQUALS" => binary_predicate(name, args, geom_equals_within_eps),
110 "ST_TOUCHES" => binary_predicate(name, args, |a, b| a.relate(b).is_touches()),
111 "ST_OVERLAPS" => binary_predicate(name, args, |a, b| a.relate(b).is_overlaps()),
112 "ST_CROSSES" => binary_predicate(name, args, |a, b| a.relate(b).is_crosses()),
113 "ST_COVERS" => binary_predicate(name, args, |a, b| a.relate(b).is_covers()),
114 "ST_COVEREDBY" => binary_predicate(name, args, |a, b| a.relate(b).is_coveredby()),
115
116 "ST_DWITHIN" => st_dwithin(args),
118
119 "ST_DISTANCE" => st_distance(args),
121 "ST_AREA" => st_area(args),
122 "ST_LENGTH" => st_length(args),
123
124 "ST_CENTROID" => st_centroid(args),
126 "ST_ENVELOPE" => st_envelope(args),
127 "ST_BUFFER" => st_buffer(args),
128
129 "ST_INTERSECTION" => st_bool_op(args, BoolOp::Intersection),
131 "ST_UNION" => st_bool_op(args, BoolOp::Union),
132 "ST_DIFFERENCE" => st_bool_op(args, BoolOp::Difference),
133
134 other => Err(QueryError::FunctionNotFound(other.to_string())),
135 }
136}
137
138fn check_arity(name: &str, args: &[Value], expected: usize) -> Result<()> {
142 if args.len() == expected {
143 Ok(())
144 } else {
145 Err(QueryError::InvalidArgument(format!(
146 "{} expects {} arg(s), got {}",
147 name,
148 expected,
149 args.len()
150 )))
151 }
152}
153
154fn parse_geometry_arg(v: &Value) -> Result<Geometry<f64>> {
156 match v {
157 Value::Geometry(g) => Ok(g.clone()),
158 Value::String(s) => parse_wkt(s),
159 other => Err(QueryError::TypeMismatch {
160 expected: "geometry or WKT string".to_string(),
161 actual: format!("{:?}", other),
162 }),
163 }
164}
165
166fn parse_wkt(s: &str) -> Result<Geometry<f64>> {
168 let parsed = Wkt::<f64>::from_str(s)
169 .map_err(|e| QueryError::InvalidArgument(format!("wkt parse error: {}", e)))?;
170 Geometry::<f64>::try_from(parsed)
171 .map_err(|e| QueryError::InvalidArgument(format!("wkt -> geo: {}", e)))
172}
173
174fn parse_numeric_arg(v: &Value) -> Result<f64> {
176 match v {
177 Value::Float64(f) => Ok(*f),
178 Value::Float32(f) => Ok(*f as f64),
179 Value::Int32(i) => Ok(*i as f64),
180 Value::Int64(i) => Ok(*i as f64),
181 other => Err(QueryError::TypeMismatch {
182 expected: "numeric".to_string(),
183 actual: format!("{:?}", other),
184 }),
185 }
186}
187
188fn binary_predicate<F>(name: &str, args: &[Value], pred: F) -> Result<Value>
190where
191 F: Fn(&Geometry<f64>, &Geometry<f64>) -> bool,
192{
193 check_arity(name, args, 2)?;
194 let a = parse_geometry_arg(&args[0])?;
195 let b = parse_geometry_arg(&args[1])?;
196 Ok(Value::Boolean(pred(&a, &b)))
197}
198
199fn geom_equals_within_eps(a: &Geometry<f64>, b: &Geometry<f64>) -> bool {
205 use geo_types::Geometry as G;
206 match (a, b) {
207 (G::Point(p1), G::Point(p2)) => coord_eq(p1.0, p2.0),
208 (G::Line(l1), G::Line(l2)) => coord_eq(l1.start, l2.start) && coord_eq(l1.end, l2.end),
209 (G::LineString(ls1), G::LineString(ls2)) => coords_eq(&ls1.0, &ls2.0),
210 (G::Polygon(p1), G::Polygon(p2)) => polygon_eq(p1, p2),
211 (G::MultiPoint(m1), G::MultiPoint(m2)) => {
212 m1.0.len() == m2.0.len() && m1.0.iter().zip(&m2.0).all(|(a, b)| coord_eq(a.0, b.0))
213 }
214 (G::MultiLineString(m1), G::MultiLineString(m2)) => {
215 m1.0.len() == m2.0.len() && m1.0.iter().zip(&m2.0).all(|(a, b)| coords_eq(&a.0, &b.0))
216 }
217 (G::MultiPolygon(m1), G::MultiPolygon(m2)) => {
218 m1.0.len() == m2.0.len() && m1.0.iter().zip(&m2.0).all(|(a, b)| polygon_eq(a, b))
219 }
220 (G::Rect(r1), G::Rect(r2)) => coord_eq(r1.min(), r2.min()) && coord_eq(r1.max(), r2.max()),
221 (G::Triangle(t1), G::Triangle(t2)) => {
222 coord_eq(t1.v1(), t2.v1()) && coord_eq(t1.v2(), t2.v2()) && coord_eq(t1.v3(), t2.v3())
223 }
224 _ => false,
225 }
226}
227
228fn coord_eq(a: Coord<f64>, b: Coord<f64>) -> bool {
229 (a.x - b.x).abs() <= EQUALS_EPS && (a.y - b.y).abs() <= EQUALS_EPS
230}
231
232fn coords_eq(a: &[Coord<f64>], b: &[Coord<f64>]) -> bool {
233 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| coord_eq(*x, *y))
234}
235
236fn polygon_eq(a: &Polygon<f64>, b: &Polygon<f64>) -> bool {
237 coords_eq(&a.exterior().0, &b.exterior().0)
238 && a.interiors().len() == b.interiors().len()
239 && a.interiors()
240 .iter()
241 .zip(b.interiors())
242 .all(|(x, y)| coords_eq(&x.0, &y.0))
243}
244
245fn st_dwithin(args: &[Value]) -> Result<Value> {
248 check_arity("ST_DWithin", args, 3)?;
249 let a = parse_geometry_arg(&args[0])?;
250 let b = parse_geometry_arg(&args[1])?;
251 let threshold = parse_numeric_arg(&args[2])?;
252 if threshold.is_nan() || threshold < 0.0 {
253 return Err(QueryError::InvalidArgument(format!(
254 "ST_DWithin threshold must be a non-negative finite number, got {}",
255 threshold
256 )));
257 }
258 let dist = euclidean_geometry_distance(&a, &b);
259 Ok(Value::Boolean(dist <= threshold))
260}
261
262fn st_distance(args: &[Value]) -> Result<Value> {
263 check_arity("ST_Distance", args, 2)?;
264 let a = parse_geometry_arg(&args[0])?;
265 let b = parse_geometry_arg(&args[1])?;
266 Ok(Value::Float64(euclidean_geometry_distance(&a, &b)))
267}
268
269fn st_area(args: &[Value]) -> Result<Value> {
270 check_arity("ST_Area", args, 1)?;
271 let g = parse_geometry_arg(&args[0])?;
272 Ok(Value::Float64(g.unsigned_area()))
273}
274
275fn st_length(args: &[Value]) -> Result<Value> {
276 check_arity("ST_Length", args, 1)?;
277 let g = parse_geometry_arg(&args[0])?;
278 Ok(Value::Float64(geometry_length(&g)))
279}
280
281fn geometry_length(g: &Geometry<f64>) -> f64 {
283 use geo_types::Geometry as G;
284 match g {
285 G::Line(line) => Euclidean.length(line),
286 G::LineString(ls) => Euclidean.length(ls),
287 G::MultiLineString(mls) => Euclidean.length(mls),
288 G::Triangle(t) => {
289 let a = t.v1();
290 let b = t.v2();
291 let c = t.v3();
292 edge_len(a, b) + edge_len(b, c) + edge_len(c, a)
293 }
294 G::Rect(r) => {
295 let p = r.max() - r.min();
296 2.0 * (p.x.abs() + p.y.abs())
297 }
298 G::Polygon(p) => polygon_perimeter(p),
299 G::MultiPolygon(mp) => mp.0.iter().map(polygon_perimeter).sum(),
300 G::Point(_) | G::MultiPoint(_) => 0.0,
301 G::GeometryCollection(c) => c.0.iter().map(geometry_length).sum(),
302 }
303}
304
305fn edge_len(a: Coord<f64>, b: Coord<f64>) -> f64 {
306 let dx = a.x - b.x;
307 let dy = a.y - b.y;
308 dx.hypot(dy)
309}
310
311fn polygon_perimeter(p: &Polygon<f64>) -> f64 {
312 let ext = Euclidean.length(p.exterior());
313 let interior: f64 = p.interiors().iter().map(|r| Euclidean.length(r)).sum();
314 ext + interior
315}
316
317fn euclidean_geometry_distance(a: &Geometry<f64>, b: &Geometry<f64>) -> f64 {
322 use geo::line_measures::Distance;
323 Euclidean.distance(a, b)
324}
325
326fn st_centroid(args: &[Value]) -> Result<Value> {
329 check_arity("ST_Centroid", args, 1)?;
330 let g = parse_geometry_arg(&args[0])?;
331 let pt = g.centroid().ok_or_else(|| {
332 QueryError::execution("ST_Centroid: geometry has no centroid (empty geometry)")
333 })?;
334 Ok(Value::Geometry(Geometry::Point(pt)))
335}
336
337fn st_envelope(args: &[Value]) -> Result<Value> {
338 check_arity("ST_Envelope", args, 1)?;
339 let g = parse_geometry_arg(&args[0])?;
340 let rect = g.bounding_rect().ok_or_else(|| {
341 QueryError::execution("ST_Envelope: geometry has no bounding rectangle (empty geometry)")
342 })?;
343 let mn = rect.min();
345 let mx = rect.max();
346 let ls = LineString::from(vec![
347 Coord { x: mn.x, y: mn.y },
348 Coord { x: mx.x, y: mn.y },
349 Coord { x: mx.x, y: mx.y },
350 Coord { x: mn.x, y: mx.y },
351 Coord { x: mn.x, y: mn.y },
352 ]);
353 Ok(Value::Geometry(Geometry::Polygon(Polygon::new(ls, vec![]))))
354}
355
356fn st_buffer(args: &[Value]) -> Result<Value> {
357 check_arity("ST_Buffer", args, 2)?;
358 let g = parse_geometry_arg(&args[0])?;
359 let dist = parse_numeric_arg(&args[1])?;
360 if !dist.is_finite() {
361 return Err(QueryError::InvalidArgument(format!(
362 "ST_Buffer distance must be finite, got {}",
363 dist
364 )));
365 }
366 let mp = g.buffer(dist);
370 Ok(Value::Geometry(Geometry::MultiPolygon(mp)))
371}
372
373enum BoolOp {
376 Intersection,
377 Union,
378 Difference,
379}
380
381fn st_bool_op(args: &[Value], op: BoolOp) -> Result<Value> {
382 let name = match op {
383 BoolOp::Intersection => "ST_Intersection",
384 BoolOp::Union => "ST_Union",
385 BoolOp::Difference => "ST_Difference",
386 };
387 check_arity(name, args, 2)?;
388 let a = parse_geometry_arg(&args[0])?;
389 let b = parse_geometry_arg(&args[1])?;
390 let multi_a = to_multi_polygon(&a, name)?;
393 let multi_b = to_multi_polygon(&b, name)?;
394 let result = match op {
395 BoolOp::Intersection => multi_a.intersection(&multi_b),
396 BoolOp::Union => multi_a.union(&multi_b),
397 BoolOp::Difference => multi_a.difference(&multi_b),
398 };
399 Ok(Value::Geometry(Geometry::MultiPolygon(result)))
400}
401
402fn to_multi_polygon(g: &Geometry<f64>, fn_name: &str) -> Result<geo_types::MultiPolygon<f64>> {
403 use geo_types::{Geometry as G, MultiPolygon};
404 match g {
405 G::Polygon(p) => Ok(MultiPolygon::new(vec![p.clone()])),
406 G::MultiPolygon(mp) => Ok(mp.clone()),
407 other => Err(QueryError::Unsupported(format!(
408 "{} requires Polygon/MultiPolygon operands, got {}",
409 fn_name,
410 geometry_kind(other)
411 ))),
412 }
413}
414
415fn geometry_kind(g: &Geometry<f64>) -> &'static str {
416 use geo_types::Geometry as G;
417 match g {
418 G::Point(_) => "Point",
419 G::Line(_) => "Line",
420 G::LineString(_) => "LineString",
421 G::Polygon(_) => "Polygon",
422 G::MultiPoint(_) => "MultiPoint",
423 G::MultiLineString(_) => "MultiLineString",
424 G::MultiPolygon(_) => "MultiPolygon",
425 G::Rect(_) => "Rect",
426 G::Triangle(_) => "Triangle",
427 G::GeometryCollection(_) => "GeometryCollection",
428 }
429}
430
431#[cfg(test)]
434#[allow(clippy::panic)]
435#[allow(clippy::unwrap_used)]
436mod tests {
437 use super::*;
438
439 fn wkt(s: &str) -> Value {
440 Value::String(s.to_string())
441 }
442
443 #[test]
444 fn test_check_arity_ok_and_err() {
445 assert!(check_arity("F", &[Value::Null], 1).is_ok());
446 let err = check_arity("F", &[], 2).unwrap_err();
447 assert!(matches!(err, QueryError::InvalidArgument(_)));
448 }
449
450 #[test]
451 fn test_parse_geometry_from_wkt() {
452 let g = parse_geometry_arg(&wkt("POINT(1 2)")).expect("parse");
453 assert!(matches!(g, Geometry::Point(_)));
454 }
455
456 #[test]
457 fn test_parse_geometry_type_mismatch() {
458 let err = parse_geometry_arg(&Value::Int64(1)).unwrap_err();
459 assert!(matches!(err, QueryError::TypeMismatch { .. }));
460 }
461
462 #[test]
463 fn test_parse_numeric_int_and_float() {
464 assert_eq!(parse_numeric_arg(&Value::Int64(7)).unwrap(), 7.0);
465 assert_eq!(parse_numeric_arg(&Value::Float64(2.5)).unwrap(), 2.5);
466 }
467}