surrealpatch/core/src/sql/geometry.rs
Raphael Darley 4c8c9f6c8a
Info structure refactor (#3886)
Co-authored-by: Micha de Vries <micha@devrie.sh>
2024-04-17 15:27:55 +01:00

612 lines
18 KiB
Rust

#![allow(clippy::derived_hash_with_manual_eq)]
use crate::sql::array::Array;
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_types::{MultiLineString, MultiPoint, MultiPolygon};
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::iter::once;
use std::{fmt, hash};
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Geometry";
#[revisioned(revision = 1)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename = "$surrealdb::private::sql::Geometry")]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum Geometry {
Point(Point<f64>),
Line(LineString<f64>),
Polygon(Polygon<f64>),
MultiPoint(MultiPoint<f64>),
MultiLine(MultiLineString<f64>),
MultiPolygon(MultiPolygon<f64>),
Collection(Vec<Geometry>),
// 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 {
#[rustfmt::skip]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
fn coord(v: &Coord) -> (f64, f64) {
v.x_y()
}
fn point(v: &Point) -> (f64, f64) {
coord(&v.0)
}
fn line(v: &LineString) -> impl Iterator<Item = (f64, f64)> + '_ {
v.into_iter().map(coord)
}
fn polygon(v: &Polygon) -> impl Iterator<Item = (f64, f64)> + '_ {
v.interiors().iter().chain(once(v.exterior())).flat_map(line)
}
fn multipoint(v: &MultiPoint) -> impl Iterator<Item = (f64, f64)> + '_ {
v.iter().map(point)
}
fn multiline(v: &MultiLineString) -> impl Iterator<Item = (f64, f64)> + '_ {
v.iter().flat_map(line)
}
fn multipolygon(v: &MultiPolygon) -> impl Iterator<Item = (f64, f64)> + '_ {
v.iter().flat_map(polygon)
}
match (self, other) {
//
(Self::Point(_), Self::Line(_)) => Some(Ordering::Less),
(Self::Point(_), Self::Polygon(_)) => Some(Ordering::Less),
(Self::Point(_), Self::MultiPoint(_)) => Some(Ordering::Less),
(Self::Point(_), Self::MultiLine(_)) => Some(Ordering::Less),
(Self::Point(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
(Self::Point(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::Line(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::Line(_), Self::Polygon(_)) => Some(Ordering::Less),
(Self::Line(_), Self::MultiPoint(_)) => Some(Ordering::Less),
(Self::Line(_), Self::MultiLine(_)) => Some(Ordering::Less),
(Self::Line(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
(Self::Line(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::Polygon(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::Polygon(_), Self::Line(_)) => Some(Ordering::Greater),
(Self::Polygon(_), Self::MultiPoint(_)) => Some(Ordering::Less),
(Self::Polygon(_), Self::MultiLine(_)) => Some(Ordering::Less),
(Self::Polygon(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
(Self::Polygon(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::MultiPoint(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::MultiPoint(_), Self::Line(_)) => Some(Ordering::Greater),
(Self::MultiPoint(_), Self::Polygon(_)) => Some(Ordering::Greater),
(Self::MultiPoint(_), Self::MultiLine(_)) => Some(Ordering::Less),
(Self::MultiPoint(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
(Self::MultiPoint(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::MultiLine(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::MultiLine(_), Self::Line(_)) => Some(Ordering::Greater),
(Self::MultiLine(_), Self::Polygon(_)) => Some(Ordering::Greater),
(Self::MultiLine(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
(Self::MultiLine(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
(Self::MultiLine(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::MultiPolygon(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::MultiPolygon(_), Self::Line(_)) => Some(Ordering::Greater),
(Self::MultiPolygon(_), Self::Polygon(_)) => Some(Ordering::Greater),
(Self::MultiPolygon(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
(Self::MultiPolygon(_), Self::MultiLine(_)) => Some(Ordering::Greater),
(Self::MultiPolygon(_), Self::Collection(_)) => Some(Ordering::Less),
//
(Self::Collection(_), Self::Point(_)) => Some(Ordering::Greater),
(Self::Collection(_), Self::Line(_)) => Some(Ordering::Greater),
(Self::Collection(_), Self::Polygon(_)) => Some(Ordering::Greater),
(Self::Collection(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
(Self::Collection(_), Self::MultiLine(_)) => Some(Ordering::Greater),
(Self::Collection(_), Self::MultiPolygon(_)) => Some(Ordering::Greater),
//
(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::Polygon(a), Self::Polygon(b)) => polygon(a).partial_cmp(polygon(b)),
(Self::MultiPoint(a), Self::MultiPoint(b)) => multipoint(a).partial_cmp(multipoint(b)),
(Self::MultiLine(a), Self::MultiLine(b)) => multiline(a).partial_cmp(multiline(b)),
(Self::MultiPolygon(a), Self::MultiPolygon(b)) => multipolygon(a).partial_cmp(multipolygon(b)),
(Self::Collection(a), Self::Collection(b)) => a.partial_cmp(b),
}
}
}
impl From<(f64, f64)> for Geometry {
fn from(v: (f64, f64)) -> Self {
Self::Point(v.into())
}
}
impl From<[f64; 2]> for Geometry {
fn from(v: [f64; 2]) -> Self {
Self::Point(v.into())
}
}
impl From<Point<f64>> for Geometry {
fn from(v: Point<f64>) -> Self {
Self::Point(v)
}
}
impl From<LineString<f64>> for Geometry {
fn from(v: LineString<f64>) -> Self {
Self::Line(v)
}
}
impl From<Polygon<f64>> for Geometry {
fn from(v: Polygon<f64>) -> Self {
Self::Polygon(v)
}
}
impl From<MultiPoint<f64>> for Geometry {
fn from(v: MultiPoint<f64>) -> Self {
Self::MultiPoint(v)
}
}
impl From<MultiLineString<f64>> for Geometry {
fn from(v: MultiLineString<f64>) -> Self {
Self::MultiLine(v)
}
}
impl From<MultiPolygon<f64>> for Geometry {
fn from(v: MultiPolygon<f64>) -> Self {
Self::MultiPolygon(v)
}
}
impl From<Vec<Geometry>> for Geometry {
fn from(v: Vec<Geometry>) -> Self {
Self::Collection(v)
}
}
impl From<Vec<Point<f64>>> for Geometry {
fn from(v: Vec<Point<f64>>) -> Self {
Self::MultiPoint(MultiPoint(v))
}
}
impl From<Vec<LineString<f64>>> for Geometry {
fn from(v: Vec<LineString<f64>>) -> Self {
Self::MultiLine(MultiLineString(v))
}
}
impl From<Vec<Polygon<f64>>> for Geometry {
fn from(v: Vec<Polygon<f64>>) -> Self {
Self::MultiPolygon(MultiPolygon(v))
}
}
impl From<Geometry> for geo::Geometry<f64> {
fn from(v: Geometry) -> Self {
match v {
Geometry::Point(v) => v.into(),
Geometry::Line(v) => v.into(),
Geometry::Polygon(v) => v.into(),
Geometry::MultiPoint(v) => v.into(),
Geometry::MultiLine(v) => v.into(),
Geometry::MultiPolygon(v) => v.into(),
Geometry::Collection(v) => v.into_iter().collect::<geo::Geometry<f64>>(),
}
}
}
impl FromIterator<Geometry> for geo::Geometry<f64> {
fn from_iter<I: IntoIterator<Item = Geometry>>(iter: I) -> Self {
let mut c: Vec<geo::Geometry<f64>> = vec![];
for i in iter {
c.push(i.into())
}
geo::Geometry::GeometryCollection(geo::GeometryCollection(c))
}
}
impl Geometry {
// -----------------------------------
// Value operations
// -----------------------------------
pub fn contains(&self, other: &Self) -> bool {
match self {
Self::Point(v) => match other {
Self::Point(w) => v.contains(w),
Self::MultiPoint(w) => w.iter().all(|x| v.contains(x)),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
_ => false,
},
Self::Line(v) => match other {
Self::Point(w) => v.contains(w),
Self::Line(w) => v.contains(w),
Self::MultiLine(w) => w.iter().all(|x| w.contains(x)),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
_ => false,
},
Self::Polygon(v) => match other {
Self::Point(w) => v.contains(w),
Self::Line(w) => v.contains(w),
Self::Polygon(w) => v.contains(w),
Self::MultiPolygon(w) => w.iter().all(|x| w.contains(x)),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
_ => false,
},
Self::MultiPoint(v) => match other {
Self::Point(w) => v.contains(w),
Self::MultiPoint(w) => w.iter().all(|x| w.contains(x)),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
_ => false,
},
Self::MultiLine(v) => match other {
Self::Point(w) => v.contains(w),
Self::Line(w) => v.contains(w),
Self::MultiLine(w) => w.iter().all(|x| w.contains(x)),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
_ => false,
},
Self::MultiPolygon(v) => match other {
Self::Point(w) => v.contains(w),
Self::Line(w) => v.contains(w),
Self::Polygon(w) => v.contains(w),
Self::MultiPoint(w) => v.contains(w),
Self::MultiLine(w) => v.contains(w),
Self::MultiPolygon(w) => v.contains(w),
Self::Collection(w) => w.iter().all(|x| self.contains(x)),
},
Self::Collection(v) => v.iter().all(|x| x.contains(other)),
}
}
pub fn intersects(&self, other: &Self) -> bool {
match self {
Self::Point(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => w.iter().any(|x| v.intersects(x)),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::Line(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => w.iter().any(|x| v.intersects(x)),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::Polygon(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => v.intersects(w),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::MultiPoint(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => w.iter().any(|x| v.intersects(x)),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::MultiLine(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => w.iter().any(|x| v.intersects(x)),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::MultiPolygon(v) => match other {
Self::Point(w) => v.intersects(w),
Self::Line(w) => v.intersects(w),
Self::Polygon(w) => v.intersects(w),
Self::MultiPoint(w) => v.intersects(w),
Self::MultiLine(w) => v.intersects(w),
Self::MultiPolygon(w) => v.intersects(w),
Self::Collection(w) => w.iter().all(|x| self.intersects(x)),
},
Self::Collection(v) => v.iter().all(|x| x.intersects(other)),
}
}
}
impl fmt::Display for Geometry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Point(v) => {
write!(f, "({}, {})", v.x(), v.y())
}
Self::Line(v) => write!(
f,
"{{ type: 'LineString', coordinates: [{}] }}",
Fmt::comma_separated(v.points().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}, {}]",
v.x(),
v.y()
))))
),
Self::Polygon(v) => write!(
f,
"{{ type: 'Polygon', coordinates: [[{}]{}] }}",
Fmt::comma_separated(v.exterior().points().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}, {}]",
v.x(),
v.y()
)))),
Fmt::new(v.interiors(), |interiors, f| {
match interiors.len() {
0 => Ok(()),
_ => write!(
f,
", [{}]",
Fmt::comma_separated(interiors.iter().map(|i| Fmt::new(i, |i, f| {
write!(
f,
"[{}]",
Fmt::comma_separated(i.points().map(|v| Fmt::new(
v,
|v, f| write!(f, "[{}, {}]", v.x(), v.y())
)))
)
})))
),
}
})
),
Self::MultiPoint(v) => {
write!(
f,
"{{ type: 'MultiPoint', coordinates: [{}] }}",
Fmt::comma_separated(v.iter().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}, {}]",
v.x(),
v.y()
))))
)
}
Self::MultiLine(v) => write!(
f,
"{{ type: 'MultiLineString', coordinates: [{}] }}",
Fmt::comma_separated(v.iter().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}]",
Fmt::comma_separated(v.points().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}, {}]",
v.x(),
v.y()
))))
))))
),
Self::MultiPolygon(v) => write!(
f,
"{{ type: 'MultiPolygon', coordinates: [{}] }}",
Fmt::comma_separated(v.iter().map(|v| Fmt::new(v, |v, f| {
write!(
f,
"[[{}]{}]",
Fmt::comma_separated(
v.exterior().points().map(|v| Fmt::new(v, |v, f| write!(
f,
"[{}, {}]",
v.x(),
v.y()
)))
),
Fmt::new(v.interiors(), |interiors, f| {
match interiors.len() {
0 => Ok(()),
_ => write!(
f,
", [{}]",
Fmt::comma_separated(interiors.iter().map(|i| Fmt::new(
i,
|i, f| {
write!(
f,
"[{}]",
Fmt::comma_separated(i.points().map(|v| Fmt::new(
v,
|v, f| write!(f, "[{}, {}]", v.x(), v.y())
)))
)
}
)))
),
}
})
)
}))),
),
Self::Collection(v) => {
write!(
f,
"{{ type: 'GeometryCollection', geometries: [{}] }}",
Fmt::comma_separated(v)
)
}
}
}
}
impl hash::Hash for Geometry {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
match self {
Geometry::Point(p) => {
"Point".hash(state);
p.x().to_bits().hash(state);
p.y().to_bits().hash(state);
}
Geometry::Line(l) => {
"Line".hash(state);
l.points().for_each(|v| {
v.x().to_bits().hash(state);
v.y().to_bits().hash(state);
});
}
Geometry::Polygon(p) => {
"Polygon".hash(state);
p.exterior().points().for_each(|ext| {
ext.x().to_bits().hash(state);
ext.y().to_bits().hash(state);
});
p.interiors().iter().for_each(|int| {
int.points().for_each(|v| {
v.x().to_bits().hash(state);
v.y().to_bits().hash(state);
});
});
}
Geometry::MultiPoint(v) => {
"MultiPoint".hash(state);
v.0.iter().for_each(|v| {
v.x().to_bits().hash(state);
v.y().to_bits().hash(state);
});
}
Geometry::MultiLine(ml) => {
"MultiLine".hash(state);
ml.0.iter().for_each(|ls| {
ls.points().for_each(|p| {
p.x().to_bits().hash(state);
p.y().to_bits().hash(state);
});
});
}
Geometry::MultiPolygon(mp) => {
"MultiPolygon".hash(state);
mp.0.iter().for_each(|p| {
p.exterior().points().for_each(|ext| {
ext.x().to_bits().hash(state);
ext.y().to_bits().hash(state);
});
p.interiors().iter().for_each(|int| {
int.points().for_each(|v| {
v.x().to_bits().hash(state);
v.y().to_bits().hash(state);
});
});
});
}
Geometry::Collection(v) => {
"GeometryCollection".hash(state);
v.iter().for_each(|v| v.hash(state));
}
}
}
}