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::sql::value::Value;
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;
@ -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 {
fn from_arg(arg: Value) -> Result<Self, Error> {
arg.coerce_to_record()

View file

@ -6,9 +6,8 @@ use geo::algorithm::centroid::Centroid;
use geo::algorithm::chamberlain_duquette_area::ChamberlainDuquetteArea;
use geo::algorithm::haversine_distance::HaversineDistance;
pub fn area((arg,): (Value,)) -> Result<Value, Error> {
pub fn area((arg,): (Geometry,)) -> Result<Value, Error> {
match arg {
Value::Geometry(v) => match v {
Geometry::Point(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()),
@ -20,23 +19,18 @@ pub fn area((arg,): (Value,)) -> Result<Value, Error> {
.collect::<geo::Geometry<f64>>()
.chamberlain_duquette_unsigned_area()
.into()),
},
_ => Ok(Value::None),
}
}
pub fn bearing(points: (Value, Value)) -> Result<Value, Error> {
Ok(match points {
(Value::Geometry(Geometry::Point(v)), Value::Geometry(Geometry::Point(w))) => {
v.haversine_bearing(w).into()
}
pub fn bearing((v, w): (Geometry, Geometry)) -> Result<Value, Error> {
Ok(match (v, w) {
(Geometry::Point(v), Geometry::Point(w)) => v.haversine_bearing(w).into(),
_ => Value::None,
})
}
pub fn centroid((arg,): (Value,)) -> Result<Value, Error> {
pub fn centroid((arg,): (Geometry,)) -> Result<Value, Error> {
let centroid = match arg {
Value::Geometry(v) => match v {
Geometry::Point(v) => Some(v.centroid()),
Geometry::Line(v) => v.centroid(),
Geometry::Polygon(v) => v.centroid(),
@ -44,17 +38,13 @@ pub fn centroid((arg,): (Value,)) -> Result<Value, Error> {
Geometry::MultiLine(v) => v.centroid(),
Geometry::MultiPolygon(v) => v.centroid(),
Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>().centroid(),
},
_ => None,
};
Ok(centroid.map(Into::into).unwrap_or(Value::None))
}
pub fn distance(points: (Value, Value)) -> Result<Value, Error> {
Ok(match points {
(Value::Geometry(Geometry::Point(v)), Value::Geometry(Geometry::Point(w))) => {
v.haversine_distance(&w).into()
}
pub fn distance((v, w): (Geometry, Geometry)) -> Result<Value, Error> {
Ok(match (v, w) {
(Geometry::Point(v), Geometry::Point(w)) => v.haversine_distance(&w).into(),
_ => Value::None,
})
}
@ -66,7 +56,7 @@ pub mod hash {
use crate::sql::geometry::Geometry;
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 {
Some(len) if (1..=12).contains(&len) => len,
None => 12,
@ -76,8 +66,8 @@ pub mod hash {
})
};
Ok(match point {
Value::Geometry(Geometry::Point(v)) => geo::encode(v, len).into(),
Ok(match arg {
Geometry::Point(v) => geo::encode(v, len).into(),
_ => 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::hash::decode" => geo::hash::decode,
"geo::hash::encode" => geo::hash::encode,
"geo::is::valid" => geo::is::valid,
//
"math::abs" => math::abs,
"math::acos" => math::acos,
@ -639,6 +640,7 @@ pub async fn idiom(
"distance" => geo::distance,
"hash_decode" => geo::hash::decode,
"hash_encode" => geo::hash::encode,
"is_valid" => geo::is::valid,
)
}
Value::Thing(_) => {

View file

@ -2,6 +2,7 @@ use super::run;
use crate::fnc::script::modules::impl_module_def;
mod hash;
mod is;
#[non_exhaustive]
pub struct Package;
@ -13,5 +14,6 @@ impl_module_def!(
"bearing" => run,
"centroid" => 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 geo::algorithm::contains::Contains;
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 revision::revisioned;
use serde::{Deserialize, Serialize};
@ -67,6 +67,48 @@ impl Geometry {
pub fn is_collection(&self) -> bool {
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
pub fn as_type(&self) -> &'static str {
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::hash::decode") => 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::get") => PathKind::Function,

View file

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

View file

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

View file

@ -1902,6 +1902,40 @@ async fn function_parse_geo_hash_decode() -> Result<(), Error> {
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
// --------------------------------------------------