Enable inner field access on sql::Geometry types (#2203)

This commit is contained in:
Tobie Morgan Hitchcock 2023-07-01 22:04:24 +01:00 committed by GitHub
parent 55918b7c0e
commit 31ccb0c904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 367 additions and 33 deletions

View file

@ -42,6 +42,12 @@ impl From<Vec<i32>> for Array {
} }
} }
impl From<Vec<f64>> for Array {
fn from(v: Vec<f64>) -> Self {
Self(v.into_iter().map(Value::from).collect())
}
}
impl From<Vec<&str>> for Array { impl From<Vec<&str>> for Array {
fn from(v: Vec<&str>) -> Self { fn from(v: Vec<&str>) -> Self {
Self(v.into_iter().map(Value::from).collect()) Self(v.into_iter().map(Value::from).collect())

View file

@ -1,9 +1,13 @@
#![allow(clippy::derived_hash_with_manual_eq)] #![allow(clippy::derived_hash_with_manual_eq)]
use crate::sql::array::Array;
use crate::sql::comment::mightbespace; use crate::sql::comment::mightbespace;
use crate::sql::common::{closebracket, closeparentheses, commas, openbracket, openparentheses}; use crate::sql::common::{
closebraces, closebracket, closeparentheses, commas, openbraces, openbracket, openparentheses,
};
use crate::sql::error::IResult; use crate::sql::error::IResult;
use crate::sql::fmt::Fmt; use crate::sql::fmt::Fmt;
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, Point, Polygon};
@ -40,35 +44,122 @@ pub enum Geometry {
// Add new variants here // Add new variants here
} }
impl Geometry {
/// Check if this is a Point
pub fn is_point(&self) -> bool {
matches!(self, Self::Point(_))
}
/// Check if this is a Line
pub fn is_line(&self) -> bool {
matches!(self, Self::Line(_))
}
/// Check if this is a Polygon
pub fn is_polygon(&self) -> bool {
matches!(self, Self::Polygon(_))
}
/// Check if this is a MultiPoint
pub fn is_multipoint(&self) -> bool {
matches!(self, Self::MultiPoint(_))
}
/// Check if this is a MultiLine
pub fn is_multiline(&self) -> bool {
matches!(self, Self::MultiLine(_))
}
/// Check if this is a MultiPolygon
pub fn is_multipolygon(&self) -> bool {
matches!(self, Self::MultiPolygon(_))
}
/// Check if this is not a Collection
pub fn is_geometry(&self) -> bool {
!matches!(self, Self::Collection(_))
}
/// Check if this is a Collection
pub fn is_collection(&self) -> bool {
matches!(self, Self::Collection(_))
}
/// Get the type of this Geometry as text
pub fn as_type(&self) -> &'static str {
match self {
Self::Point(_) => "Point",
Self::Line(_) => "LineString",
Self::Polygon(_) => "Polygon",
Self::MultiPoint(_) => "MultiPoint",
Self::MultiLine(_) => "MultiLineString",
Self::MultiPolygon(_) => "MultiPolygon",
Self::Collection(_) => "GeometryCollection",
}
}
/// Get the raw coordinates of this Geometry as an Array
pub fn as_coordinates(&self) -> Value {
fn point(v: &Point) -> Value {
Array::from(vec![v.x(), v.y()]).into()
}
fn line(v: &LineString) -> Value {
v.points().map(|v| point(&v)).collect::<Vec<Value>>().into()
}
fn polygon(v: &Polygon) -> Value {
once(v.exterior()).chain(v.interiors()).map(line).collect::<Vec<Value>>().into()
}
fn multipoint(v: &MultiPoint) -> Value {
v.iter().map(point).collect::<Vec<Value>>().into()
}
fn multiline(v: &MultiLineString) -> Value {
v.iter().map(line).collect::<Vec<Value>>().into()
}
fn multipolygon(v: &MultiPolygon) -> Value {
v.iter().map(polygon).collect::<Vec<Value>>().into()
}
fn collection(v: &[Geometry]) -> Value {
v.iter().map(Geometry::as_coordinates).collect::<Vec<Value>>().into()
}
match self {
Self::Point(v) => point(v),
Self::Line(v) => line(v),
Self::Polygon(v) => polygon(v),
Self::MultiPoint(v) => multipoint(v),
Self::MultiLine(v) => multiline(v),
Self::MultiPolygon(v) => multipolygon(v),
Self::Collection(v) => collection(v),
}
}
}
impl PartialOrd for Geometry { impl PartialOrd for Geometry {
#[rustfmt::skip] #[rustfmt::skip]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
fn coord(coord: &Coord) -> (f64, f64) { fn coord(v: &Coord) -> (f64, f64) {
coord.x_y() v.x_y()
} }
fn point(point: &Point) -> (f64, f64) { fn point(v: &Point) -> (f64, f64) {
coord(&point.0) coord(&v.0)
} }
fn line(line: &LineString) -> impl Iterator<Item = (f64, f64)> + '_ { fn line(v: &LineString) -> impl Iterator<Item = (f64, f64)> + '_ {
line.into_iter().map(coord) v.into_iter().map(coord)
} }
fn polygon(polygon: &Polygon) -> impl Iterator<Item = (f64, f64)> + '_ { fn polygon(v: &Polygon) -> impl Iterator<Item = (f64, f64)> + '_ {
polygon.interiors().iter().chain(once(polygon.exterior())).flat_map(line) v.interiors().iter().chain(once(v.exterior())).flat_map(line)
} }
fn multi_point(multi_point: &MultiPoint) -> impl Iterator<Item = (f64, f64)> + '_ { fn multipoint(v: &MultiPoint) -> impl Iterator<Item = (f64, f64)> + '_ {
multi_point.iter().map(point) v.iter().map(point)
} }
fn multi_line(multi_line: &MultiLineString) -> impl Iterator<Item = (f64, f64)> + '_ { fn multiline(v: &MultiLineString) -> impl Iterator<Item = (f64, f64)> + '_ {
multi_line.iter().flat_map(line) v.iter().flat_map(line)
} }
fn multi_polygon(multi_polygon: &MultiPolygon) -> impl Iterator<Item = (f64, f64)> + '_ { fn multipolygon(v: &MultiPolygon) -> impl Iterator<Item = (f64, f64)> + '_ {
multi_polygon.iter().flat_map(polygon) v.iter().flat_map(polygon)
} }
match (self, other) { match (self, other) {
@ -125,9 +216,9 @@ impl PartialOrd for Geometry {
(Self::Point(a), Self::Point(b)) => point(a).partial_cmp(&point(b)), (Self::Point(a), Self::Point(b)) => point(a).partial_cmp(&point(b)),
(Self::Line(a), Self::Line(b)) => line(a).partial_cmp(line(b)), (Self::Line(a), Self::Line(b)) => line(a).partial_cmp(line(b)),
(Self::Polygon(a), Self::Polygon(b)) => polygon(a).partial_cmp(polygon(b)), (Self::Polygon(a), Self::Polygon(b)) => polygon(a).partial_cmp(polygon(b)),
(Self::MultiPoint(a), Self::MultiPoint(b)) => multi_point(a).partial_cmp(multi_point(b)), (Self::MultiPoint(a), Self::MultiPoint(b)) => multipoint(a).partial_cmp(multipoint(b)),
(Self::MultiLine(a), Self::MultiLine(b)) => multi_line(a).partial_cmp(multi_line(b)), (Self::MultiLine(a), Self::MultiLine(b)) => multiline(a).partial_cmp(multiline(b)),
(Self::MultiPolygon(a), Self::MultiPolygon(b)) => multi_polygon(a).partial_cmp(multi_polygon(b)), (Self::MultiPolygon(a), Self::MultiPolygon(b)) => multipolygon(a).partial_cmp(multipolygon(b)),
(Self::Collection(a), Self::Collection(b)) => a.partial_cmp(b), (Self::Collection(a), Self::Collection(b)) => a.partial_cmp(b),
} }
} }
@ -547,13 +638,11 @@ fn simple(i: &str) -> IResult<&str, Geometry> {
} }
fn normal(i: &str) -> IResult<&str, Geometry> { fn normal(i: &str) -> IResult<&str, Geometry> {
let (i, _) = char('{')(i)?; let (i, _) = openbraces(i)?;
let (i, _) = mightbespace(i)?;
let (i, v) = alt((point, line, polygon, multipoint, multiline, multipolygon, collection))(i)?; let (i, v) = alt((point, line, polygon, multipoint, multiline, multipolygon, collection))(i)?;
let (i, _) = mightbespace(i)?; let (i, _) = mightbespace(i)?;
let (i, _) = opt(char(','))(i)?; let (i, _) = opt(char(','))(i)?;
let (i, _) = mightbespace(i)?; let (i, _) = closebraces(i)?;
let (i, _) = char('}')(i)?;
Ok((i, v)) Ok((i, v))
} }
@ -706,15 +795,11 @@ fn polygon_vals(i: &str) -> IResult<&str, Polygon<f64>> {
let (i, e) = line_vals(i)?; let (i, e) = line_vals(i)?;
let (i, _) = mightbespace(i)?; let (i, _) = mightbespace(i)?;
let (i, _) = opt(char(','))(i)?; let (i, _) = opt(char(','))(i)?;
let (i, _) = mightbespace(i)?;
let (i, v) = separated_list0(commas, line_vals)(i)?;
let (i, _) = mightbespace(i)?;
let (i, _) = opt(char(','))(i)?;
let (i, _) = closebracket(i)?; let (i, _) = closebracket(i)?;
let (i, v) = separated_list0(commas, |i| {
let (i, _) = openbracket(i)?;
let (i, v) = line_vals(i)?;
let (i, _) = mightbespace(i)?;
let (i, _) = opt(char(','))(i)?;
let (i, _) = closebracket(i)?;
Ok((i, v))
})(i)?;
Ok((i, Polygon::new(e, v))) Ok((i, Polygon::new(e, v)))
} }
@ -876,10 +961,63 @@ mod tests {
#[test] #[test]
fn simple() { fn simple() {
let sql = "(51.509865, -0.118092)"; let sql = "(-0.118092, 51.509865)";
let res = geometry(sql); let res = geometry(sql);
assert!(res.is_ok()); assert!(res.is_ok());
let out = res.unwrap().1; let out = res.unwrap().1;
assert_eq!("(51.509865, -0.118092)", format!("{}", out)); assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
}
#[test]
fn point() {
let sql = r#"{
type: 'Point',
coordinates: [-0.118092, 51.509865]
}"#;
let res = geometry(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("(-0.118092, 51.509865)", format!("{}", out));
}
#[test]
fn polygon_exterior() {
let sql = r#"{
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
]
]
}"#;
let res = geometry(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]] }", format!("{}", out));
}
#[test]
fn polygon_interior() {
let sql = r#"{
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
],
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386]
]
]
}"#;
let res = geometry(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("{ type: 'Polygon', coordinates: [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]], [[[-0.38314819, 51.37692386], [0.1785278, 51.37692386], [0.1785278, 51.6146057], [-0.38314819, 51.6146057], [-0.38314819, 51.37692386]]]] }", format!("{}", out));
} }
} }

View file

@ -51,10 +51,22 @@ impl Ident {
pub fn to_raw(&self) -> String { pub fn to_raw(&self) -> String {
self.0.to_string() self.0.to_string()
} }
/// Returns a yield if an alias is specified /// Checks if this field is the `id` field
pub(crate) fn is_id(&self) -> bool { pub(crate) fn is_id(&self) -> bool {
self.0.as_str() == "id" self.0.as_str() == "id"
} }
/// Checks if this field is the `type` field
pub(crate) fn is_type(&self) -> bool {
self.0.as_str() == "type"
}
/// Checks if this field is the `coordinates` field
pub(crate) fn is_coordinates(&self) -> bool {
self.0.as_str() == "coordinates"
}
/// Checks if this field is the `geometries` field
pub(crate) fn is_geometries(&self) -> bool {
self.0.as_str() == "geometries"
}
} }
impl Display for Ident { impl Display for Ident {

View file

@ -26,6 +26,24 @@ impl Value {
match path.first() { match path.first() {
// Get the current path part // Get the current path part
Some(p) => match self { Some(p) => match self {
// Current path part is a geometry
Value::Geometry(v) => match p {
// If this is the 'type' field then continue
Part::Field(f) if f.is_type() => {
Value::from(v.as_type()).get(ctx, opt, path.next()).await
}
// If this is the 'coordinates' field then continue
Part::Field(f) if f.is_coordinates() && v.is_geometry() => {
v.as_coordinates().get(ctx, opt, path.next()).await
}
// If this is the 'geometries' field then continue
Part::Field(f) if f.is_geometries() && v.is_collection() => {
v.as_coordinates().get(ctx, opt, path.next()).await
}
// otherwise return none
_ => Ok(Value::None),
},
// Current path part is a future // Current path part is a future
Value::Future(v) => { Value::Future(v) => {
// Check how many path parts are remaining // Check how many path parts are remaining

View file

@ -309,3 +309,163 @@ async fn geometry_multipolygon() -> Result<(), Error> {
// //
Ok(()) Ok(())
} }
#[tokio::test]
async fn geometry_inner_access() -> Result<(), Error> {
let sql = "
SELECT type, coordinates[0] as lng, coordinates[1] AS lat FROM type::point([-0.118092, 51.509865]);
SELECT type, coordinates[0] as lng, coordinates[1] AS lat FROM (-0.118092, 51.509865);
SELECT coordinates FROM {
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386],
]
],
};
SELECT coordinates FROM {
type: 'Polygon',
coordinates: [
[
[-0.38314819, 51.37692386], [0.1785278, 51.37692386],
[0.1785278, 51.61460570], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386],
],
[
[-0.38314819, 51.37692386], [-0.38314819, 51.61460570],
[-0.38314819, 51.37692386],
],
[
[110.38314819, 110.37692386], [110.38314819, 110.61460570],
[110.38314819, 110.37692386],
]
],
};
";
let dbs = Datastore::new("memory").await?;
let ses = Session::for_kv().with_ns("test").with_db("test");
let res = &mut dbs.execute(sql, &ses, None, false).await?;
assert_eq!(res.len(), 4);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
r#"[
{
lat: 51.509865,
lng: -0.118092,
type: 'Point'
}
]"#,
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
r#"[
{
lat: 51.509865,
lng: -0.118092,
type: 'Point'
}
]"#,
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
r#"[
{
coordinates: [
[
[
-0.38314819,
51.37692386
],
[
0.1785278,
51.37692386
],
[
0.1785278,
51.6146057
],
[
-0.38314819,
51.6146057
],
[
-0.38314819,
51.37692386
]
]
]
}
]"#,
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
r#"[
{
coordinates: [
[
[
-0.38314819,
51.37692386
],
[
0.1785278,
51.37692386
],
[
0.1785278,
51.6146057
],
[
-0.38314819,
51.6146057
],
[
-0.38314819,
51.37692386
]
],
[
[
-0.38314819,
51.37692386
],
[
-0.38314819,
51.6146057
],
[
-0.38314819,
51.37692386
]
],
[
[
110.38314819,
110.37692386
],
[
110.38314819,
110.6146057
],
[
110.38314819,
110.37692386
]
]
]
}
]"#,
);
assert_eq!(tmp, val);
//
Ok(())
}