Skip to main content

surrealdb_types/value/
geometry.rs

1use std::cmp::Ordering;
2use std::fmt::{self, Display, Formatter};
3use std::hash;
4use std::iter::once;
5
6use geo::{
7	Coord, LineString, LinesIter, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::sql::{SqlFormat, ToSql};
12use crate::{GeometryKind, Object, SurrealValue, Value, array, object};
13
14/// Represents geometric shapes in SurrealDB
15///
16/// Geometry types support various geometric shapes including points, lines, polygons,
17/// and their multi-variants. This is useful for spatial data and geographic applications.
18///
19/// The types used internally originate from the `geo` crate.
20
21#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
22#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
23pub enum Geometry {
24	/// A single point in 2D space
25	Point(Point<f64>),
26	/// A line consisting of multiple connected points
27	Line(LineString<f64>),
28	/// A polygon with an exterior boundary and optional interior holes
29	Polygon(Polygon<f64>),
30	/// Multiple points
31	MultiPoint(MultiPoint<f64>),
32	/// Multiple lines
33	MultiLine(MultiLineString<f64>),
34	/// Multiple polygons
35	MultiPolygon(MultiPolygon<f64>),
36	/// A collection of different geometry types
37	Collection(Vec<Geometry>),
38}
39
40macro_rules! impl_geometry {
41	($($variant:ident($type:ty) => ($is:ident, $into:ident, $from:ident),)+) => {
42		impl Geometry {
43			/// Get the kind of geometry
44			pub fn kind(&self) -> GeometryKind {
45				match self {
46					$(
47						Self::$variant(_) => GeometryKind::$variant,
48					)+
49				}
50			}
51
52			$(
53				/// Check if this is a of the given type
54				pub fn $is(&self) -> bool {
55					matches!(self, Self::$variant(_))
56				}
57
58				/// Convert this geometry into the given type
59				pub fn $into(self) -> anyhow::Result<$type> {
60					if let Self::$variant(v) = self {
61						Ok(v)
62					} else {
63						Err(anyhow::anyhow!("Expected a geometry<{}> but got a geometry<{}>", GeometryKind::$variant, self.kind()))
64					}
65				}
66
67				/// Create a new geometry from the given type
68				pub fn $from(v: $type) -> Self {
69					Self::$variant(v)
70				}
71			)+
72		}
73	}
74}
75
76impl Display for Geometry {
77	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
78		use crate::value::format::F;
79
80		match self {
81			Self::Point(v) => {
82				write!(f, "({}, {})", F(v.x()), F(v.y()))
83			}
84			Self::Line(v) => {
85				write!(f, "{{ type: 'LineString', coordinates: [")?;
86				for (i, point) in v.points().enumerate() {
87					if i > 0 {
88						write!(f, ", ")?;
89					}
90					write!(f, "[{}, {}]", F(point.x()), F(point.y()))?;
91				}
92				write!(f, "] }}")
93			}
94			Self::Polygon(v) => {
95				write!(f, "{{ type: 'Polygon', coordinates: [")?;
96				for (ring_idx, ring) in once(v.exterior()).chain(v.interiors()).enumerate() {
97					if ring_idx > 0 {
98						write!(f, ", ")?;
99					}
100					write!(f, "[")?;
101					for (i, point) in ring.points().enumerate() {
102						if i > 0 {
103							write!(f, ", ")?;
104						}
105						write!(f, "[{}, {}]", F(point.x()), F(point.y()))?;
106					}
107					write!(f, "]")?;
108				}
109				write!(f, "] }}")
110			}
111			Self::MultiPoint(v) => {
112				write!(f, "{{ type: 'MultiPoint', coordinates: [")?;
113				for (i, point) in v.iter().enumerate() {
114					if i > 0 {
115						write!(f, ", ")?;
116					}
117					write!(f, "[{}, {}]", F(point.x()), F(point.y()))?;
118				}
119				write!(f, "] }}")
120			}
121			Self::MultiLine(v) => {
122				write!(f, "{{ type: 'MultiLineString', coordinates: [")?;
123				for (line_idx, line) in v.iter().enumerate() {
124					if line_idx > 0 {
125						write!(f, ", ")?;
126					}
127					write!(f, "[")?;
128					for (i, point) in line.points().enumerate() {
129						if i > 0 {
130							write!(f, ", ")?;
131						}
132						write!(f, "[{}, {}]", F(point.x()), F(point.y()))?;
133					}
134					write!(f, "]")?;
135				}
136				write!(f, "] }}")
137			}
138			Self::MultiPolygon(v) => {
139				write!(f, "{{ type: 'MultiPolygon', coordinates: [")?;
140				for (poly_idx, polygon) in v.iter().enumerate() {
141					if poly_idx > 0 {
142						write!(f, ", ")?;
143					}
144					write!(f, "[")?;
145					for (ring_idx, ring) in
146						once(polygon.exterior()).chain(polygon.interiors()).enumerate()
147					{
148						if ring_idx > 0 {
149							write!(f, ", ")?;
150						}
151						write!(f, "[")?;
152						for (i, point) in ring.points().enumerate() {
153							if i > 0 {
154								write!(f, ", ")?;
155							}
156							write!(f, "[{}, {}]", F(point.x()), F(point.y()))?;
157						}
158						write!(f, "]")?;
159					}
160					write!(f, "]")?;
161				}
162				write!(f, "] }}")
163			}
164			Self::Collection(v) => {
165				write!(f, "{{ type: 'GeometryCollection', geometries: [")?;
166				for (i, geom) in v.iter().enumerate() {
167					if i > 0 {
168						write!(f, ", ")?;
169					}
170					write!(f, "{}", geom)?;
171				}
172				write!(f, "] }}")
173			}
174		}
175	}
176}
177
178impl ToSql for Geometry {
179	fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
180		f.push_str(&self.to_string());
181	}
182}
183
184impl_geometry! (
185	Point(Point<f64>) => (is_point, into_point, from_point),
186	Line(LineString<f64>) => (is_line, into_line, from_line),
187	Polygon(Polygon<f64>) => (is_polygon, into_polygon, from_polygon),
188	MultiPoint(MultiPoint<f64>) => (is_multipoint, into_multipoint, from_multipoint),
189	MultiLine(MultiLineString<f64>) => (is_multiline, into_multiline, from_multiline),
190	MultiPolygon(MultiPolygon<f64>) => (is_multipolygon, into_multipolygon, from_multipolygon),
191	Collection(Vec<Geometry>) => (is_collection, into_collection, from_collection),
192);
193
194impl Geometry {
195	/// Check if this has valid latitude and longitude points:
196	/// * -90 <= lat <= 90
197	/// * -180 <= lng <= 180
198	pub fn is_valid(&self) -> bool {
199		match self {
200			Geometry::Point(p) => {
201				(-90.0..=90.0).contains(&p.0.y) && (-180.0..=180.0).contains(&p.0.x)
202			}
203			Geometry::MultiPoint(v) => v
204				.iter()
205				.all(|p| (-90.0..=90.0).contains(&p.0.y) && (-180.0..=180.0).contains(&p.0.x)),
206			Geometry::Line(v) => v.lines_iter().all(|l| {
207				(-90.0..=90.0).contains(&l.start.y)
208					&& (-180.0..=180.0).contains(&l.start.x)
209					&& (-90.0..=90.0).contains(&l.end.y)
210					&& (-180.0..=180.0).contains(&l.end.x)
211			}),
212			Geometry::Polygon(v) => v.lines_iter().all(|l| {
213				(-90.0..=90.0).contains(&l.start.y)
214					&& (-180.0..=180.0).contains(&l.start.x)
215					&& (-90.0..=90.0).contains(&l.end.y)
216					&& (-180.0..=180.0).contains(&l.end.x)
217			}),
218			Geometry::MultiLine(v) => v.iter().all(|l| {
219				l.lines_iter().all(|l| {
220					(-90.0..=90.0).contains(&l.start.y)
221						&& (-180.0..=180.0).contains(&l.start.x)
222						&& (-90.0..=90.0).contains(&l.end.y)
223						&& (-180.0..=180.0).contains(&l.end.x)
224				})
225			}),
226			Geometry::MultiPolygon(v) => v.iter().all(|p| {
227				p.lines_iter().all(|l| {
228					(-90.0..=90.0).contains(&l.start.y)
229						&& (-180.0..=180.0).contains(&l.start.x)
230						&& (-90.0..=90.0).contains(&l.end.y)
231						&& (-180.0..=180.0).contains(&l.end.x)
232				})
233			}),
234			Geometry::Collection(v) => v.iter().all(Geometry::is_valid),
235		}
236	}
237
238	/// Get the type of this Geometry as text
239	pub fn as_type(&self) -> &'static str {
240		match self {
241			Self::Point(_) => "Point",
242			Self::Line(_) => "LineString",
243			Self::Polygon(_) => "Polygon",
244			Self::MultiPoint(_) => "MultiPoint",
245			Self::MultiLine(_) => "MultiLineString",
246			Self::MultiPolygon(_) => "MultiPolygon",
247			Self::Collection(_) => "GeometryCollection",
248		}
249	}
250
251	/// Get the raw coordinates of this Geometry as an Array
252	pub fn as_coordinates(&self) -> Value {
253		fn point(v: &Point) -> Value {
254			array![v.x(), v.y()].into_value()
255		}
256
257		fn line(v: &LineString) -> Value {
258			v.points().map(|v| point(&v)).collect::<Vec<Value>>().into_value()
259		}
260
261		fn polygon(v: &Polygon) -> Value {
262			once(v.exterior()).chain(v.interiors()).map(line).collect::<Vec<Value>>().into_value()
263		}
264
265		fn multipoint(v: &MultiPoint) -> Value {
266			v.iter().map(point).collect::<Vec<Value>>().into_value()
267		}
268
269		fn multiline(v: &MultiLineString) -> Value {
270			v.iter().map(line).collect::<Vec<Value>>().into_value()
271		}
272
273		fn multipolygon(v: &MultiPolygon) -> Value {
274			v.iter().map(polygon).collect::<Vec<Value>>().into_value()
275		}
276
277		fn collection(v: &[Geometry]) -> Value {
278			v.iter().map(Geometry::as_coordinates).collect::<Vec<Value>>().into_value()
279		}
280
281		match self {
282			Self::Point(v) => point(v),
283			Self::Line(v) => line(v),
284			Self::Polygon(v) => polygon(v),
285			Self::MultiPoint(v) => multipoint(v),
286			Self::MultiLine(v) => multiline(v),
287			Self::MultiPolygon(v) => multipolygon(v),
288			Self::Collection(v) => collection(v),
289		}
290	}
291
292	/// Convert this geometry into an object
293	pub fn as_object(&self) -> Object {
294		object! {
295			type: Value::String(self.as_type().to_string()),
296			coordinates: self.as_coordinates(),
297		}
298	}
299
300	/// Try to convert an object into a geometry
301	pub fn try_from_object(object: &Object) -> Option<Geometry> {
302		if object.len() != 2 {
303			return None;
304		}
305
306		let Some(Value::String(key)) = object.get("type") else {
307			return None;
308		};
309
310		match key.as_str() {
311			"Point" => {
312				object.get("coordinates").and_then(Geometry::array_to_point).map(Geometry::Point)
313			}
314			"LineString" => {
315				object.get("coordinates").and_then(Geometry::array_to_line).map(Geometry::Line)
316			}
317			"Polygon" => object
318				.get("coordinates")
319				.and_then(Geometry::array_to_polygon)
320				.map(Geometry::Polygon),
321			"MultiPoint" => object
322				.get("coordinates")
323				.and_then(Geometry::array_to_multipoint)
324				.map(Geometry::MultiPoint),
325			"MultiLineString" => object
326				.get("coordinates")
327				.and_then(Geometry::array_to_multiline)
328				.map(Geometry::MultiLine),
329			"MultiPolygon" => object
330				.get("coordinates")
331				.and_then(Geometry::array_to_multipolygon)
332				.map(Geometry::MultiPolygon),
333			"GeometryCollection" => {
334				let Some(Value::Array(x)) = object.get("geometries") else {
335					return None;
336				};
337
338				let mut res = Vec::with_capacity(x.len());
339
340				for x in x.iter() {
341					let Value::Geometry(x) = x else {
342						return None;
343					};
344					res.push(x.clone());
345				}
346
347				Some(Geometry::Collection(res))
348			}
349
350			_ => None,
351		}
352	}
353
354	/// Converts a surreal value to a MultiPolygon if the array matches to a MultiPolygon.
355	pub(crate) fn array_to_multipolygon(v: &Value) -> Option<MultiPolygon<f64>> {
356		let mut res = Vec::new();
357		let Value::Array(v) = v else {
358			return None;
359		};
360		for x in v.iter() {
361			res.push(Self::array_to_polygon(x)?);
362		}
363		Some(MultiPolygon::new(res))
364	}
365
366	/// Converts a surreal value to a MultiLine if the array matches to a MultiLine.
367	pub(crate) fn array_to_multiline(v: &Value) -> Option<MultiLineString<f64>> {
368		let mut res = Vec::new();
369		let Value::Array(v) = v else {
370			return None;
371		};
372		for x in v.iter() {
373			res.push(Self::array_to_line(x)?);
374		}
375		Some(MultiLineString::new(res))
376	}
377
378	/// Converts a surreal value to a MultiPoint if the array matches to a MultiPoint.
379	pub(crate) fn array_to_multipoint(v: &Value) -> Option<MultiPoint<f64>> {
380		let mut res = Vec::new();
381		let Value::Array(v) = v else {
382			return None;
383		};
384		for x in v.iter() {
385			res.push(Self::array_to_point(x)?);
386		}
387		Some(MultiPoint::new(res))
388	}
389
390	/// Converts a surreal value to a Polygon if the array matches to a Polygon.
391	pub(crate) fn array_to_polygon(v: &Value) -> Option<Polygon<f64>> {
392		let mut res = Vec::new();
393		let Value::Array(v) = v else {
394			return None;
395		};
396		if v.is_empty() {
397			return None;
398		}
399		let first = Self::array_to_line(&v[0])?;
400		for x in &v[1..] {
401			res.push(Self::array_to_line(x)?);
402		}
403		Some(Polygon::new(first, res))
404	}
405
406	/// Converts a surreal value to a LineString if the array matches to a LineString.
407	pub(crate) fn array_to_line(v: &Value) -> Option<LineString<f64>> {
408		let mut res = Vec::new();
409		let Value::Array(v) = v else {
410			return None;
411		};
412		for x in v.iter() {
413			res.push(Self::array_to_point(x)?);
414		}
415		Some(LineString::from(res))
416	}
417
418	/// Converts a surreal value to a Point if the array matches to a point.
419	pub(crate) fn array_to_point(v: &Value) -> Option<Point<f64>> {
420		let Value::Array(v) = v else {
421			return None;
422		};
423		if v.len() != 2 {
424			return None;
425		}
426		let a = v.0[0].clone().into_float().ok()?;
427		let b = v.0[1].clone().into_float().ok()?;
428		Some(Point::from((a, b)))
429	}
430}
431
432impl PartialOrd for Geometry {
433	#[rustfmt::skip]
434	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
435		#[inline]
436		fn coord(v: &Coord) -> (f64, f64) {
437			v.x_y()
438		}
439
440		#[inline]
441		fn point(v: &Point) -> (f64, f64) {
442			coord(&v.0)
443		}
444
445		#[inline]
446		fn line(v: &LineString) -> impl Iterator<Item = (f64, f64)> + '_ {
447			v.into_iter().map(coord)
448		}
449
450		#[inline]
451		fn polygon(v: &Polygon) -> impl Iterator<Item = (f64, f64)> + '_ {
452			v.interiors().iter().chain(once(v.exterior())).flat_map(line)
453		}
454
455		#[inline]
456		fn multipoint(v: &MultiPoint) -> impl Iterator<Item = (f64, f64)> + '_ {
457			v.iter().map(point)
458		}
459
460		#[inline]
461		fn multiline(v: &MultiLineString) -> impl Iterator<Item = (f64, f64)> + '_ {
462			v.iter().flat_map(line)
463		}
464
465		#[inline]
466		fn multipolygon(v: &MultiPolygon) -> impl Iterator<Item = (f64, f64)> + '_ {
467			v.iter().flat_map(polygon)
468		}
469
470		match (self, other) {
471			//
472			(Self::Point(_), Self::Line(_)) => Some(Ordering::Less),
473			(Self::Point(_), Self::Polygon(_)) => Some(Ordering::Less),
474			(Self::Point(_), Self::MultiPoint(_)) => Some(Ordering::Less),
475			(Self::Point(_), Self::MultiLine(_)) => Some(Ordering::Less),
476			(Self::Point(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
477			(Self::Point(_), Self::Collection(_)) => Some(Ordering::Less),
478			//
479			(Self::Line(_), Self::Point(_)) => Some(Ordering::Greater),
480			(Self::Line(_), Self::Polygon(_)) => Some(Ordering::Less),
481			(Self::Line(_), Self::MultiPoint(_)) => Some(Ordering::Less),
482			(Self::Line(_), Self::MultiLine(_)) => Some(Ordering::Less),
483			(Self::Line(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
484			(Self::Line(_), Self::Collection(_)) => Some(Ordering::Less),
485			//
486			(Self::Polygon(_), Self::Point(_)) => Some(Ordering::Greater),
487			(Self::Polygon(_), Self::Line(_)) => Some(Ordering::Greater),
488			(Self::Polygon(_), Self::MultiPoint(_)) => Some(Ordering::Less),
489			(Self::Polygon(_), Self::MultiLine(_)) => Some(Ordering::Less),
490			(Self::Polygon(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
491			(Self::Polygon(_), Self::Collection(_)) => Some(Ordering::Less),
492			//
493			(Self::MultiPoint(_), Self::Point(_)) => Some(Ordering::Greater),
494			(Self::MultiPoint(_), Self::Line(_)) => Some(Ordering::Greater),
495			(Self::MultiPoint(_), Self::Polygon(_)) => Some(Ordering::Greater),
496			(Self::MultiPoint(_), Self::MultiLine(_)) => Some(Ordering::Less),
497			(Self::MultiPoint(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
498			(Self::MultiPoint(_), Self::Collection(_)) => Some(Ordering::Less),
499			//
500			(Self::MultiLine(_), Self::Point(_)) => Some(Ordering::Greater),
501			(Self::MultiLine(_), Self::Line(_)) => Some(Ordering::Greater),
502			(Self::MultiLine(_), Self::Polygon(_)) => Some(Ordering::Greater),
503			(Self::MultiLine(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
504			(Self::MultiLine(_), Self::MultiPolygon(_)) => Some(Ordering::Less),
505			(Self::MultiLine(_), Self::Collection(_)) => Some(Ordering::Less),
506			//
507			(Self::MultiPolygon(_), Self::Point(_)) => Some(Ordering::Greater),
508			(Self::MultiPolygon(_), Self::Line(_)) => Some(Ordering::Greater),
509			(Self::MultiPolygon(_), Self::Polygon(_)) => Some(Ordering::Greater),
510			(Self::MultiPolygon(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
511			(Self::MultiPolygon(_), Self::MultiLine(_)) => Some(Ordering::Greater),
512			(Self::MultiPolygon(_), Self::Collection(_)) => Some(Ordering::Less),
513			//
514			(Self::Collection(_), Self::Point(_)) => Some(Ordering::Greater),
515			(Self::Collection(_), Self::Line(_)) => Some(Ordering::Greater),
516			(Self::Collection(_), Self::Polygon(_)) => Some(Ordering::Greater),
517			(Self::Collection(_), Self::MultiPoint(_)) => Some(Ordering::Greater),
518			(Self::Collection(_), Self::MultiLine(_)) => Some(Ordering::Greater),
519			(Self::Collection(_), Self::MultiPolygon(_)) => Some(Ordering::Greater),
520			//
521			(Self::Point(a), Self::Point(b)) => point(a).partial_cmp(&point(b)),
522			(Self::Line(a), Self::Line(b)) => line(a).partial_cmp(line(b)),
523			(Self::Polygon(a), Self::Polygon(b)) => polygon(a).partial_cmp(polygon(b)),
524			(Self::MultiPoint(a), Self::MultiPoint(b)) => multipoint(a).partial_cmp(multipoint(b)),
525			(Self::MultiLine(a), Self::MultiLine(b)) => multiline(a).partial_cmp(multiline(b)),
526			(Self::MultiPolygon(a), Self::MultiPolygon(b)) => multipolygon(a).partial_cmp(multipolygon(b)),
527			(Self::Collection(a), Self::Collection(b)) => a.partial_cmp(b),
528		}
529	}
530}
531
532impl hash::Hash for Geometry {
533	fn hash<H: hash::Hasher>(&self, state: &mut H) {
534		match self {
535			Geometry::Point(p) => {
536				"Point".hash(state);
537				p.x().to_bits().hash(state);
538				p.y().to_bits().hash(state);
539			}
540			Geometry::Line(l) => {
541				"Line".hash(state);
542				l.points().for_each(|v| {
543					v.x().to_bits().hash(state);
544					v.y().to_bits().hash(state);
545				});
546			}
547			Geometry::Polygon(p) => {
548				"Polygon".hash(state);
549				p.exterior().points().for_each(|ext| {
550					ext.x().to_bits().hash(state);
551					ext.y().to_bits().hash(state);
552				});
553				p.interiors().iter().for_each(|int| {
554					int.points().for_each(|v| {
555						v.x().to_bits().hash(state);
556						v.y().to_bits().hash(state);
557					});
558				});
559			}
560			Geometry::MultiPoint(v) => {
561				"MultiPoint".hash(state);
562				v.0.iter().for_each(|v| {
563					v.x().to_bits().hash(state);
564					v.y().to_bits().hash(state);
565				});
566			}
567			Geometry::MultiLine(ml) => {
568				"MultiLine".hash(state);
569				ml.0.iter().for_each(|ls| {
570					ls.points().for_each(|p| {
571						p.x().to_bits().hash(state);
572						p.y().to_bits().hash(state);
573					});
574				});
575			}
576			Geometry::MultiPolygon(mp) => {
577				"MultiPolygon".hash(state);
578				mp.0.iter().for_each(|p| {
579					p.exterior().points().for_each(|ext| {
580						ext.x().to_bits().hash(state);
581						ext.y().to_bits().hash(state);
582					});
583					p.interiors().iter().for_each(|int| {
584						int.points().for_each(|v| {
585							v.x().to_bits().hash(state);
586							v.y().to_bits().hash(state);
587						});
588					});
589				});
590			}
591			Geometry::Collection(v) => {
592				"GeometryCollection".hash(state);
593				v.iter().for_each(|v| v.hash(state));
594			}
595		}
596	}
597}