package geospatial import ( "fmt" ) // GeospatialCalculator implements the main Calculator interface type GeospatialCalculator struct { config *Config distanceCalc DistanceCalculator bearingCalc BearingCalculator boundingBoxCalc BoundingBoxCalculator coordinateTransformer CoordinateTransformer validator SpatialValidator } // NewGeospatialCalculator creates a new geospatial calculator with all dependencies func NewGeospatialCalculator( config *Config, distanceCalc DistanceCalculator, bearingCalc BearingCalculator, boundingBoxCalc BoundingBoxCalculator, coordinateTransformer CoordinateTransformer, validator SpatialValidator, ) Calculator { return &GeospatialCalculator{ config: config, distanceCalc: distanceCalc, bearingCalc: bearingCalc, boundingBoxCalc: boundingBoxCalc, coordinateTransformer: coordinateTransformer, validator: validator, } } // CalculateDistance calculates distance between two points func (gc *GeospatialCalculator) CalculateDistance(p1, p2 Point) (DistanceResult, error) { var distanceKm float64 var err error switch gc.config.DefaultDistanceMethod { case "vincenty": if vincentyCalc, ok := gc.distanceCalc.(*DistanceCalculatorImpl); ok { distanceKm, err = vincentyCalc.VincentyDistance(p1, p2) } else { distanceKm, err = gc.distanceCalc.HaversineDistance(p1, p2) } default: distanceKm, err = gc.distanceCalc.HaversineDistance(p1, p2) } if err != nil { return DistanceResult{}, err } bearingDeg, err := gc.bearingCalc.CalculateBearing(p1, p2) if err != nil { return DistanceResult{}, err } return DistanceResult{ DistanceKm: distanceKm, DistanceMeters: distanceKm * 1000, Bearing: bearingDeg, BearingRadians: bearingDeg * 3.14159265359 / 180, }, nil } // CalculateDistanceMatrix calculates distances between multiple points func (gc *GeospatialCalculator) CalculateDistanceMatrix(points []Point) (*DistanceMatrix, error) { if len(points) == 0 { return nil, ErrEmptyPointList } if len(points) > gc.config.MaxDistanceMatrixSize { return nil, ErrMatrixTooLarge } matrix := make([][]float64, len(points)) for i := range matrix { matrix[i] = make([]float64, len(points)) } for i := 0; i < len(points); i++ { for j := 0; j < len(points); j++ { if i == j { matrix[i][j] = 0 } else { distance, err := gc.distanceCalc.HaversineDistance(points[i], points[j]) if err != nil { return nil, fmt.Errorf("failed to calculate distance between points %d and %d: %w", i, j, err) } matrix[i][j] = distance } } } return &DistanceMatrix{ Points: points, Distances: matrix, }, nil } // CalculateBearing calculates bearing between two points func (gc *GeospatialCalculator) CalculateBearing(from, to Point) (float64, error) { return gc.bearingCalc.CalculateBearing(from, to) } // CalculateBoundingBox calculates bounding box for points func (gc *GeospatialCalculator) CalculateBoundingBox(points []Point) (*BoundingBox, error) { return gc.boundingBoxCalc.CalculateBoundingBox(points) } // IsPointInBoundingBox checks if point is in bounding box func (gc *GeospatialCalculator) IsPointInBoundingBox(point Point, bbox BoundingBox) bool { return gc.boundingBoxCalc.IsPointInBoundingBox(point, bbox) } // CalculateRoute calculates a route through multiple points func (gc *GeospatialCalculator) CalculateRoute(points []Point) (*Route, error) { if len(points) < 2 { return nil, ErrInvalidRoute } var totalDistance float64 var segments []RouteSegment for i := 0; i < len(points)-1; i++ { distanceResult, err := gc.CalculateDistance(points[i], points[i+1]) if err != nil { return nil, fmt.Errorf("failed to calculate segment %d: %w", i, err) } totalDistance += distanceResult.DistanceKm // Estimate time if average speed is configured var timeMinutes *int if gc.config.AverageSpeedKmh > 0 { minutes := int((distanceResult.DistanceKm / gc.config.AverageSpeedKmh) * 60) timeMinutes = &minutes } segments = append(segments, RouteSegment{ From: points[i], To: points[i+1], DistanceKm: distanceResult.DistanceKm, Bearing: distanceResult.Bearing, TimeMinutes: timeMinutes, }) } // Calculate total time var totalTimeMinutes *int if gc.config.AverageSpeedKmh > 0 { totalMinutes := int((totalDistance / gc.config.AverageSpeedKmh) * 60) totalTimeMinutes = &totalMinutes } return &Route{ Points: points, TotalDistanceKm: totalDistance, Segments: segments, EstimatedTimeMinutes: totalTimeMinutes, }, nil } // ValidatePoint validates a point func (gc *GeospatialCalculator) ValidatePoint(point Point) error { return gc.validator.ValidatePoint(point) } // ValidateBoundingBox validates a bounding box func (gc *GeospatialCalculator) ValidateBoundingBox(bbox BoundingBox) error { return gc.validator.ValidateBoundingBox(bbox) }