Add geo::is::valid()
function (#4711)
Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
parent
f7a8b5dff9
commit
218fa83c10
10 changed files with 143 additions and 42 deletions
|
@ -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()
|
||||
|
|
|
@ -6,55 +6,45 @@ 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()),
|
||||
Geometry::MultiPoint(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::Collection(v) => Ok(v
|
||||
.into_iter()
|
||||
.collect::<geo::Geometry<f64>>()
|
||||
.chamberlain_duquette_unsigned_area()
|
||||
.into()),
|
||||
},
|
||||
_ => Ok(Value::None),
|
||||
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()),
|
||||
Geometry::MultiPoint(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::Collection(v) => Ok(v
|
||||
.into_iter()
|
||||
.collect::<geo::Geometry<f64>>()
|
||||
.chamberlain_duquette_unsigned_area()
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
Geometry::MultiPoint(v) => v.centroid(),
|
||||
Geometry::MultiLine(v) => v.centroid(),
|
||||
Geometry::MultiPolygon(v) => v.centroid(),
|
||||
Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>().centroid(),
|
||||
},
|
||||
_ => None,
|
||||
Geometry::Point(v) => Some(v.centroid()),
|
||||
Geometry::Line(v) => v.centroid(),
|
||||
Geometry::Polygon(v) => v.centroid(),
|
||||
Geometry::MultiPoint(v) => v.centroid(),
|
||||
Geometry::MultiLine(v) => v.centroid(),
|
||||
Geometry::MultiPolygon(v) => v.centroid(),
|
||||
Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>().centroid(),
|
||||
};
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(_) => {
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
11
core/src/fnc/script/modules/surrealdb/functions/geo/is.rs
Normal file
11
core/src/fnc/script/modules/surrealdb/functions/geo/is.rs
Normal 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
|
||||
);
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -231,6 +231,7 @@
|
|||
"hash"
|
||||
"geo::hash::decode("
|
||||
"geo::hash::encode("
|
||||
"geo::is::valid("
|
||||
"http"
|
||||
"http::"
|
||||
"http::head("
|
||||
|
|
|
@ -230,6 +230,7 @@
|
|||
"hash"
|
||||
"geo::hash::decode("
|
||||
"geo::hash::encode("
|
||||
"geo::is::valid("
|
||||
"http"
|
||||
"http::"
|
||||
"http::head("
|
||||
|
|
|
@ -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
|
||||
// --------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue