package geospatial import ( "math" ) // BearingCalculatorImpl implements BearingCalculator interface type BearingCalculatorImpl struct { config *Config } // NewBearingCalculator creates a new bearing calculator func NewBearingCalculator(config *Config) BearingCalculator { return &BearingCalculatorImpl{ config: config, } } // CalculateBearing calculates the initial bearing (direction) from point p1 to point p2 // Returns bearing in degrees (0-360), where 0 is North, 90 is East, 180 is South, 270 is West func (bc *BearingCalculatorImpl) CalculateBearing(from, to Point) (float64, error) { if err := validatePoint(from); err != nil { return 0, err } if err := validatePoint(to); err != nil { return 0, err } bearingRad := bc.calculateBearingRadians(from, to) bearingDeg := bearingRad * 180 / math.Pi // Normalize to 0-360 degrees if bearingDeg < 0 { bearingDeg += 360 } return bearingDeg, nil } // CalculateBearingRadians calculates the initial bearing in radians func (bc *BearingCalculatorImpl) CalculateBearingRadians(from, to Point) (float64, error) { if err := validatePoint(from); err != nil { return 0, err } if err := validatePoint(to); err != nil { return 0, err } return bc.calculateBearingRadians(from, to), nil } // calculateBearingRadians is the internal implementation func (bc *BearingCalculatorImpl) calculateBearingRadians(from, to Point) float64 { lat1 := from.Latitude * math.Pi / 180 lat2 := to.Latitude * math.Pi / 180 deltaLon := (to.Longitude - from.Longitude) * math.Pi / 180 y := math.Sin(deltaLon) * math.Cos(lat2) x := math.Cos(lat1)*math.Sin(lat2) - math.Sin(lat1)*math.Cos(lat2)*math.Cos(deltaLon) bearing := math.Atan2(y, x) return bearing } // CalculateMidpoint calculates the midpoint between two points func (bc *BearingCalculatorImpl) CalculateMidpoint(p1, p2 Point) (Point, error) { if err := validatePoint(p1); err != nil { return Point{}, err } if err := validatePoint(p2); err != nil { return Point{}, err } lat1 := p1.Latitude * math.Pi / 180 lon1 := p1.Longitude * math.Pi / 180 lat2 := p2.Latitude * math.Pi / 180 lon2 := p2.Longitude * math.Pi / 180 Bx := math.Cos(lat2) * math.Cos(lon2-lon1) By := math.Cos(lat2) * math.Sin(lon2-lon1) midLat := math.Atan2( math.Sin(lat1)+math.Sin(lat2), math.Sqrt((math.Cos(lat1)+Bx)*(math.Cos(lat1)+Bx)+By*By), ) midLon := lon1 + math.Atan2(By, math.Cos(lat1)+Bx) return Point{ Latitude: midLat * 180 / math.Pi, Longitude: midLon * 180 / math.Pi, }, nil } // CalculateDestination calculates a destination point given a starting point, bearing, and distance func (bc *BearingCalculatorImpl) CalculateDestination(origin Point, bearingDegrees, distanceKm float64) (Point, error) { if err := validatePoint(origin); err != nil { return Point{}, err } lat1 := origin.Latitude * math.Pi / 180 lon1 := origin.Longitude * math.Pi / 180 bearing := bearingDegrees * math.Pi / 180 distanceRad := distanceKm / bc.config.EarthRadiusKm lat2 := math.Asin( math.Sin(lat1)*math.Cos(distanceRad) + math.Cos(lat1)*math.Sin(distanceRad)*math.Cos(bearing), ) lon2 := lon1 + math.Atan2( math.Sin(bearing)*math.Sin(distanceRad)*math.Cos(lat1), math.Cos(distanceRad)-math.Sin(lat1)*math.Sin(lat2), ) return Point{ Latitude: lat2 * 180 / math.Pi, Longitude: lon2 * 180 / math.Pi, }, nil }