mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools
Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
* GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
* GET /api/v1/users/me/organizations - User organizations
* POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue
API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules
Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
141 lines
4.0 KiB
Go
141 lines
4.0 KiB
Go
package geospatial
|
|
|
|
import (
|
|
"math"
|
|
)
|
|
|
|
// BoundingBoxCalculatorImpl implements BoundingBoxCalculator interface
|
|
type BoundingBoxCalculatorImpl struct {
|
|
config *Config
|
|
}
|
|
|
|
// NewBoundingBoxCalculator creates a new bounding box calculator
|
|
func NewBoundingBoxCalculator(config *Config) BoundingBoxCalculator {
|
|
return &BoundingBoxCalculatorImpl{
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// CalculateBoundingBox calculates the bounding box that contains all given points
|
|
func (bbc *BoundingBoxCalculatorImpl) CalculateBoundingBox(points []Point) (*BoundingBox, error) {
|
|
if len(points) == 0 {
|
|
return nil, ErrEmptyPointList
|
|
}
|
|
|
|
minLat := points[0].Latitude
|
|
maxLat := points[0].Latitude
|
|
minLon := points[0].Longitude
|
|
maxLon := points[0].Longitude
|
|
|
|
for _, point := range points {
|
|
if err := validatePoint(point); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if point.Latitude < minLat {
|
|
minLat = point.Latitude
|
|
}
|
|
if point.Latitude > maxLat {
|
|
maxLat = point.Latitude
|
|
}
|
|
if point.Longitude < minLon {
|
|
minLon = point.Longitude
|
|
}
|
|
if point.Longitude > maxLon {
|
|
maxLon = point.Longitude
|
|
}
|
|
}
|
|
|
|
return &BoundingBox{
|
|
NorthEast: Point{
|
|
Latitude: maxLat,
|
|
Longitude: maxLon,
|
|
},
|
|
SouthWest: Point{
|
|
Latitude: minLat,
|
|
Longitude: minLon,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// ExpandBoundingBox expands a bounding box by a specified distance in kilometers
|
|
func (bbc *BoundingBoxCalculatorImpl) ExpandBoundingBox(bbox BoundingBox, expansionKm float64) (*BoundingBox, error) {
|
|
if err := bbc.ValidateBoundingBox(bbox); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Convert expansion from km to approximate degrees
|
|
// At equator: 1 degree latitude ≈ 111 km
|
|
// Longitude varies by latitude: 1 degree ≈ 111 km * cos(latitude)
|
|
latExpansion := expansionKm / 111.0
|
|
avgLat := (bbox.NorthEast.Latitude + bbox.SouthWest.Latitude) / 2
|
|
lonExpansion := expansionKm / (111.0 * math.Cos(avgLat*math.Pi/180))
|
|
|
|
expanded := BoundingBox{
|
|
NorthEast: Point{
|
|
Latitude: math.Min(bbox.NorthEast.Latitude+latExpansion, 90),
|
|
Longitude: math.Min(bbox.NorthEast.Longitude+lonExpansion, 180),
|
|
},
|
|
SouthWest: Point{
|
|
Latitude: math.Max(bbox.SouthWest.Latitude-latExpansion, -90),
|
|
Longitude: math.Max(bbox.SouthWest.Longitude-lonExpansion, -180),
|
|
},
|
|
}
|
|
|
|
return &expanded, nil
|
|
}
|
|
|
|
// IsPointInBoundingBox checks if a point is within a bounding box
|
|
func (bbc *BoundingBoxCalculatorImpl) IsPointInBoundingBox(point Point, bbox BoundingBox) bool {
|
|
if err := validatePoint(point); err != nil {
|
|
return false
|
|
}
|
|
if err := bbc.ValidateBoundingBox(bbox); err != nil {
|
|
return false
|
|
}
|
|
|
|
return point.Latitude >= bbox.SouthWest.Latitude &&
|
|
point.Latitude <= bbox.NorthEast.Latitude &&
|
|
point.Longitude >= bbox.SouthWest.Longitude &&
|
|
point.Longitude <= bbox.NorthEast.Longitude
|
|
}
|
|
|
|
// BoundingBoxArea calculates the area of a bounding box in square kilometers
|
|
func (bbc *BoundingBoxCalculatorImpl) BoundingBoxArea(bbox BoundingBox) (float64, error) {
|
|
if err := bbc.ValidateBoundingBox(bbox); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Calculate area using spherical approximation
|
|
latDiff := (bbox.NorthEast.Latitude - bbox.SouthWest.Latitude) * math.Pi / 180
|
|
avgLat := (bbox.NorthEast.Latitude + bbox.SouthWest.Latitude) / 2 * math.Pi / 180
|
|
lonDiff := (bbox.NorthEast.Longitude - bbox.SouthWest.Longitude) * math.Pi / 180
|
|
|
|
// Area = R² * lat_diff * lon_diff * cos(avg_lat)
|
|
area := bbc.config.EarthRadiusKm * bbc.config.EarthRadiusKm * latDiff * lonDiff * math.Cos(avgLat)
|
|
|
|
return math.Abs(area), nil
|
|
}
|
|
|
|
// ValidateBoundingBox validates a bounding box
|
|
func (bbc *BoundingBoxCalculatorImpl) ValidateBoundingBox(bbox BoundingBox) error {
|
|
if err := validatePoint(bbox.NorthEast); err != nil {
|
|
return err
|
|
}
|
|
if err := validatePoint(bbox.SouthWest); err != nil {
|
|
return err
|
|
}
|
|
|
|
if bbox.NorthEast.Latitude < bbox.SouthWest.Latitude {
|
|
return ErrInvalidBoundingBox
|
|
}
|
|
|
|
// Handle longitude wrap-around (e.g., -180 to 180)
|
|
if bbox.NorthEast.Longitude < bbox.SouthWest.Longitude {
|
|
// This is valid if the box crosses the date line
|
|
// For simplicity, we'll allow it but note it requires special handling
|
|
}
|
|
|
|
return nil
|
|
}
|