Add geo::is::valid() function (#4711)

Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
David Bottiau 2024-09-16 00:45:24 +02:00 committed by GitHub
parent f7a8b5dff9
commit 218fa83c10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 143 additions and 42 deletions

View file

@ -1,7 +1,7 @@
use crate::err::Error; use crate::err::Error;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::{ use crate::sql::{
Array, Bytes, Closure, Datetime, Duration, Kind, Number, Object, Regex, Strand, Thing, Array, Bytes, Closure, Datetime, Duration, Geometry, Kind, Number, Object, Regex, Strand, Thing,
}; };
use std::vec::IntoIter; use std::vec::IntoIter;
@ -58,6 +58,12 @@ impl FromArg for Duration {
} }
} }
impl FromArg for Geometry {
fn from_arg(arg: Value) -> Result<Self, Error> {
arg.coerce_to_geometry()
}
}
impl FromArg for Thing { impl FromArg for Thing {
fn from_arg(arg: Value) -> Result<Self, Error> { fn from_arg(arg: Value) -> Result<Self, Error> {
arg.coerce_to_record() arg.coerce_to_record()

View file

@ -6,55 +6,45 @@ use geo::algorithm::centroid::Centroid;
use geo::algorithm::chamberlain_duquette_area::ChamberlainDuquetteArea; use geo::algorithm::chamberlain_duquette_area::ChamberlainDuquetteArea;
use geo::algorithm::haversine_distance::HaversineDistance; use geo::algorithm::haversine_distance::HaversineDistance;
pub fn area((arg,): (Value,)) -> Result<Value, Error> { pub fn area((arg,): (Geometry,)) -> Result<Value, Error> {
match arg { match arg {
Value::Geometry(v) => match v { Geometry::Point(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::Point(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::Line(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::Line(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::Polygon(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::Polygon(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::MultiPoint(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::MultiPoint(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::MultiLine(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::MultiLine(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::MultiPolygon(v) => Ok(v.chamberlain_duquette_unsigned_area().into()),
Geometry::MultiPolygon(v) => Ok(v.chamberlain_duquette_unsigned_area().into()), Geometry::Collection(v) => Ok(v
Geometry::Collection(v) => Ok(v .into_iter()
.into_iter() .collect::<geo::Geometry<f64>>()
.collect::<geo::Geometry<f64>>() .chamberlain_duquette_unsigned_area()
.chamberlain_duquette_unsigned_area() .into()),
.into()),
},
_ => Ok(Value::None),
} }
} }
pub fn bearing(points: (Value, Value)) -> Result<Value, Error> { pub fn bearing((v, w): (Geometry, Geometry)) -> Result<Value, Error> {
Ok(match points { Ok(match (v, w) {
(Value::Geometry(Geometry::Point(v)), Value::Geometry(Geometry::Point(w))) => { (Geometry::Point(v), Geometry::Point(w)) => v.haversine_bearing(w).into(),
v.haversine_bearing(w).into()
}
_ => Value::None, _ => Value::None,
}) })
} }
pub fn centroid((arg,): (Value,)) -> Result<Value, Error> { pub fn centroid((arg,): (Geometry,)) -> Result<Value, Error> {
let centroid = match arg { let centroid = match arg {
Value::Geometry(v) => match v { Geometry::Point(v) => Some(v.centroid()),
Geometry::Point(v) => Some(v.centroid()), Geometry::Line(v) => v.centroid(),
Geometry::Line(v) => v.centroid(), Geometry::Polygon(v) => v.centroid(),
Geometry::Polygon(v) => v.centroid(), Geometry::MultiPoint(v) => v.centroid(),
Geometry::MultiPoint(v) => v.centroid(), Geometry::MultiLine(v) => v.centroid(),
Geometry::MultiLine(v) => v.centroid(), Geometry::MultiPolygon(v) => v.centroid(),
Geometry::MultiPolygon(v) => v.centroid(), Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>().centroid(),
Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>().centroid(),
},
_ => None,
}; };
Ok(centroid.map(Into::into).unwrap_or(Value::None)) Ok(centroid.map(Into::into).unwrap_or(Value::None))
} }
pub fn distance(points: (Value, Value)) -> Result<Value, Error> { pub fn distance((v, w): (Geometry, Geometry)) -> Result<Value, Error> {
Ok(match points { Ok(match (v, w) {
(Value::Geometry(Geometry::Point(v)), Value::Geometry(Geometry::Point(w))) => { (Geometry::Point(v), Geometry::Point(w)) => v.haversine_distance(&w).into(),
v.haversine_distance(&w).into()
}
_ => Value::None, _ => Value::None,
}) })
} }
@ -66,7 +56,7 @@ pub mod hash {
use crate::sql::geometry::Geometry; use crate::sql::geometry::Geometry;
use crate::sql::value::Value; use crate::sql::value::Value;
pub fn encode((point, len): (Value, Option<usize>)) -> Result<Value, Error> { pub fn encode((arg, len): (Geometry, Option<usize>)) -> Result<Value, Error> {
let len = match len { let len = match len {
Some(len) if (1..=12).contains(&len) => len, Some(len) if (1..=12).contains(&len) => len,
None => 12, None => 12,
@ -76,8 +66,8 @@ pub mod hash {
}) })
}; };
Ok(match point { Ok(match arg {
Value::Geometry(Geometry::Point(v)) => geo::encode(v, len).into(), Geometry::Point(v) => geo::encode(v, len).into(),
_ => Value::None, _ => Value::None,
}) })
} }
@ -89,3 +79,14 @@ pub mod hash {
} }
} }
} }
pub mod is {
use crate::err::Error;
use crate::sql::geometry::Geometry;
use crate::sql::value::Value;
pub fn valid((arg,): (Geometry,)) -> Result<Value, Error> {
Ok(arg.is_valid().into())
}
}

View file

@ -194,6 +194,7 @@ pub fn synchronous(
"geo::distance" => geo::distance, "geo::distance" => geo::distance,
"geo::hash::decode" => geo::hash::decode, "geo::hash::decode" => geo::hash::decode,
"geo::hash::encode" => geo::hash::encode, "geo::hash::encode" => geo::hash::encode,
"geo::is::valid" => geo::is::valid,
// //
"math::abs" => math::abs, "math::abs" => math::abs,
"math::acos" => math::acos, "math::acos" => math::acos,
@ -639,6 +640,7 @@ pub async fn idiom(
"distance" => geo::distance, "distance" => geo::distance,
"hash_decode" => geo::hash::decode, "hash_decode" => geo::hash::decode,
"hash_encode" => geo::hash::encode, "hash_encode" => geo::hash::encode,
"is_valid" => geo::is::valid,
) )
} }
Value::Thing(_) => { Value::Thing(_) => {

View file

@ -2,6 +2,7 @@ use super::run;
use crate::fnc::script::modules::impl_module_def; use crate::fnc::script::modules::impl_module_def;
mod hash; mod hash;
mod is;
#[non_exhaustive] #[non_exhaustive]
pub struct Package; pub struct Package;
@ -13,5 +14,6 @@ impl_module_def!(
"bearing" => run, "bearing" => run,
"centroid" => run, "centroid" => run,
"distance" => run, "distance" => run,
"hash" => (hash::Package) "hash" => (hash::Package),
"is" => (is::Package)
); );

View file

@ -0,0 +1,11 @@
use super::super::run;
use crate::fnc::script::modules::impl_module_def;
#[non_exhaustive]
pub struct Package;
impl_module_def!(
Package,
"geo::is",
"valid" => run
);

View file

@ -5,7 +5,7 @@ use crate::sql::fmt::Fmt;
use crate::sql::value::Value; use crate::sql::value::Value;
use geo::algorithm::contains::Contains; use geo::algorithm::contains::Contains;
use geo::algorithm::intersects::Intersects; use geo::algorithm::intersects::Intersects;
use geo::{Coord, LineString, Point, Polygon}; use geo::{Coord, LineString, LinesIter, Point, Polygon};
use geo_types::{MultiLineString, MultiPoint, MultiPolygon}; use geo_types::{MultiLineString, MultiPoint, MultiPolygon};
use revision::revisioned; use revision::revisioned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -67,6 +67,48 @@ impl Geometry {
pub fn is_collection(&self) -> bool { pub fn is_collection(&self) -> bool {
matches!(self, Self::Collection(_)) matches!(self, Self::Collection(_))
} }
/// Check if this has valid latitude and longitude points:
/// * -90 <= lat <= 90
/// * -180 <= lng <= 180
pub fn is_valid(&self) -> bool {
match self {
Geometry::Point(p) => {
(-90.0..=90.0).contains(&p.0.y) && (-180.0..=180.0).contains(&p.0.x)
}
Geometry::MultiPoint(v) => v
.iter()
.all(|p| (-90.0..=90.0).contains(&p.0.y) && (-180.0..=180.0).contains(&p.0.x)),
Geometry::Line(v) => v.lines_iter().all(|l| {
(-90.0..=90.0).contains(&l.start.y)
&& (-180.0..=180.0).contains(&l.start.x)
&& (-90.0..=90.0).contains(&l.end.y)
&& (-180.0..=180.0).contains(&l.end.x)
}),
Geometry::Polygon(v) => v.lines_iter().all(|l| {
(-90.0..=90.0).contains(&l.start.y)
&& (-180.0..=180.0).contains(&l.start.x)
&& (-90.0..=90.0).contains(&l.end.y)
&& (-180.0..=180.0).contains(&l.end.x)
}),
Geometry::MultiLine(v) => v.iter().all(|l| {
l.lines_iter().all(|l| {
(-90.0..=90.0).contains(&l.start.y)
&& (-180.0..=180.0).contains(&l.start.x)
&& (-90.0..=90.0).contains(&l.end.y)
&& (-180.0..=180.0).contains(&l.end.x)
})
}),
Geometry::MultiPolygon(v) => v.iter().all(|p| {
p.lines_iter().all(|l| {
(-90.0..=90.0).contains(&l.start.y)
&& (-180.0..=180.0).contains(&l.start.x)
&& (-90.0..=90.0).contains(&l.end.y)
&& (-180.0..=180.0).contains(&l.end.x)
})
}),
Geometry::Collection(v) => v.iter().all(Geometry::is_valid),
}
}
/// Get the type of this Geometry as text /// Get the type of this Geometry as text
pub fn as_type(&self) -> &'static str { pub fn as_type(&self) -> &'static str {
match self { match self {

View file

@ -182,6 +182,7 @@ pub(crate) static PATHS: phf::Map<UniCase<&'static str>, PathKind> = phf_map! {
UniCase::ascii("geo::distance") => PathKind::Function, UniCase::ascii("geo::distance") => PathKind::Function,
UniCase::ascii("geo::hash::decode") => PathKind::Function, UniCase::ascii("geo::hash::decode") => PathKind::Function,
UniCase::ascii("geo::hash::encode") => PathKind::Function, UniCase::ascii("geo::hash::encode") => PathKind::Function,
UniCase::ascii("geo::is::valid") => PathKind::Function,
// //
UniCase::ascii("http::head") => PathKind::Function, UniCase::ascii("http::head") => PathKind::Function,
UniCase::ascii("http::get") => PathKind::Function, UniCase::ascii("http::get") => PathKind::Function,

View file

@ -231,6 +231,7 @@
"hash" "hash"
"geo::hash::decode(" "geo::hash::decode("
"geo::hash::encode(" "geo::hash::encode("
"geo::is::valid("
"http" "http"
"http::" "http::"
"http::head(" "http::head("

View file

@ -230,6 +230,7 @@
"hash" "hash"
"geo::hash::decode(" "geo::hash::decode("
"geo::hash::encode(" "geo::hash::encode("
"geo::is::valid("
"http" "http"
"http::" "http::"
"http::head(" "http::head("

View file

@ -1902,6 +1902,40 @@ async fn function_parse_geo_hash_decode() -> Result<(), Error> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn function_geo_is_valid() -> Result<(), Error> {
let sql = r#"
RETURN geo::is::valid((-0.118092, 51.509865));
RETURN geo::is::valid((-181.0, 51.509865));
RETURN geo::is::valid((181.0, 51.509865));
RETURN geo::is::valid((-0.118092, -91.0));
RETURN geo::is::valid((-0.118092, 91.0));
"#;
let mut test = Test::new(sql).await?;
//
let tmp = test.next()?.result?;
let val = Value::from(true);
assert_eq!(tmp, val);
//
let tmp = test.next()?.result?;
let val = Value::from(false);
assert_eq!(tmp, val);
//
let tmp = test.next()?.result?;
let val = Value::from(false);
assert_eq!(tmp, val);
//
let tmp = test.next()?.result?;
let val = Value::from(false);
assert_eq!(tmp, val);
//
let tmp = test.next()?.result?;
let val = Value::from(false);
assert_eq!(tmp, val);
//
Ok(())
}
// -------------------------------------------------- // --------------------------------------------------
// math // math
// -------------------------------------------------- // --------------------------------------------------