Source code for quantumengine.fields.geometry

from typing import Any, Dict, List, Optional

from .base import Field
from ..exceptions import ValidationError

[docs] class GeometryField(Field): """Field for handling geometric data in SurrealDB. This field validates and processes geometric data according to SurrealDB's geometry specification. It supports various geometry types including Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon. Attributes: required (bool): Whether the field is required. Defaults to False. Example: >>> class Location(Document): ... point = GeometryField() >>> >>> # Using GeometryPoint for precise coordinate handling >>> from quantumengine.geometry import GeometryPoint >>> loc = Location(point=GeometryPoint([-122.4194, 37.7749])) """
[docs] def __init__(self, required: bool = False, **kwargs): """Initialize a GeometryField. Args: required (bool, optional): Whether this field is required. Defaults to False. **kwargs: Additional field options to be passed to the parent Field class. """ super().__init__(required=required, **kwargs) self.py_type = dict
[docs] def validate(self, value): """Validate geometry data. Ensures the geometry data follows SurrealDB's expected format with proper structure and coordinates. Does not modify the numeric values to preserve SurrealDB's native geometry handling. Args: value: The geometry value to validate. Can be a GeometryPoint object or a dict with 'type' and 'coordinates' fields. Returns: dict: The validated geometry data. Raises: ValidationError: If the geometry data is invalid or improperly formatted. """ if value is None: if self.required: raise ValidationError("This field is required") return None # Handle GeometryPoint and other Geometry objects if hasattr(value, 'to_json'): return value.to_json() # Handle GeometryPoint from surrealdb.data if hasattr(value, 'get_coordinates') and hasattr(value, 'longitude') and hasattr(value, 'latitude'): coords = value.get_coordinates() # Use simple tuple format as preferred by SurrealDB return list(coords) # Convert tuple to list for JSON serialization # Handle simple coordinate arrays for Point geometry (longitude, latitude) if isinstance(value, (list, tuple)) and len(value) == 2: try: # Validate that coordinates are numeric float(value[0]) # longitude float(value[1]) # latitude return list(value) # Convert to list for consistency except (TypeError, ValueError): pass # Fall through to other validation if not isinstance(value, dict): raise ValidationError("Geometry value must be a dictionary or coordinate array") if "type" not in value or "coordinates" not in value: raise ValidationError("Geometry must have 'type' and 'coordinates' fields") if not isinstance(value["coordinates"], list): raise ValidationError("Coordinates must be a list") # Validate structure based on geometry type without modifying values if value["type"] == "Point": if len(value["coordinates"]) != 2: raise ValidationError("Point coordinates must be a list of two numbers") elif value["type"] in ("LineString", "MultiPoint"): if not all(isinstance(point, list) and len(point) == 2 for point in value["coordinates"]): raise ValidationError("LineString/MultiPoint coordinates must be a list of [x,y] points") elif value["type"] in ("Polygon", "MultiLineString"): if not all(isinstance(line, list) and all(isinstance(point, list) and len(point) == 2 for point in line) for line in value["coordinates"]): raise ValidationError("Polygon/MultiLineString must be a list of coordinate arrays") elif value["type"] == "MultiPolygon": if not all(isinstance(polygon, list) and all(isinstance(line, list) and all(isinstance(point, list) and len(point) == 2 for point in line) for line in polygon) for polygon in value["coordinates"]): raise ValidationError("MultiPolygon must be a list of polygon arrays") return value