mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Add comprehensive geographical data models (GeographicalFeature, TransportMode, TransportProfile, TransportOption) - Implement geographical feature repository with PostGIS support and spatial queries - Create transportation service for cost calculation and route optimization - Build spatial resource matcher for geographical resource matching - Develop environmental impact service for site environmental scoring - Implement facility location optimizer with multi-criteria analysis - Add geographical data migration service for SQLite to PostgreSQL migration - Create database migrations for geographical features and site footprints - Update geospatial service integration and server initialization - Add CLI command for geographical data synchronization - Implement complete test coverage for all geographical components (28 test cases) - Update test infrastructure for geographical table creation and PostGIS handling This implements advanced geospatial capabilities including transportation cost modeling, environmental impact assessment, and facility location optimization for the Turash platform.
207 lines
6.8 KiB
Go
207 lines
6.8 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GeographicalFeatureRepository implements domain.GeographicalFeatureRepository with GORM and PostGIS
|
|
type GeographicalFeatureRepository struct {
|
|
*BaseRepository[domain.GeographicalFeature]
|
|
}
|
|
|
|
// NewGeographicalFeatureRepository creates a new GORM-based geographical feature repository
|
|
func NewGeographicalFeatureRepository(db *gorm.DB) domain.GeographicalFeatureRepository {
|
|
return &GeographicalFeatureRepository{
|
|
BaseRepository: NewBaseRepository[domain.GeographicalFeature](db),
|
|
}
|
|
}
|
|
|
|
// GetByType retrieves features by type
|
|
func (r *GeographicalFeatureRepository) GetByType(ctx context.Context, featureType domain.GeographicalFeatureType) ([]*domain.GeographicalFeature, error) {
|
|
return r.FindWhereWithContext(ctx, "feature_type = ?", featureType)
|
|
}
|
|
|
|
// GetWithinBounds retrieves features within geographical bounds using PostGIS
|
|
func (r *GeographicalFeatureRepository) GetWithinBounds(ctx context.Context, minLat, minLng, maxLat, maxLng float64) ([]*domain.GeographicalFeature, error) {
|
|
var features []*domain.GeographicalFeature
|
|
|
|
// Use PostGIS ST_MakeEnvelope for bounding box queries
|
|
query := `
|
|
SELECT * FROM geographical_features
|
|
WHERE ST_Intersects(
|
|
geometry,
|
|
ST_MakeEnvelope(?, ?, ?, ?, 4326)
|
|
)
|
|
`
|
|
|
|
result := r.DB().WithContext(ctx).Raw(query, minLng, minLat, maxLng, maxLat).Scan(&features)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
|
|
return features, nil
|
|
}
|
|
|
|
// GetIntersectingGeometry retrieves features that intersect with a given geometry (WKT format)
|
|
func (r *GeographicalFeatureRepository) GetIntersectingGeometry(ctx context.Context, wktGeometry string) ([]*domain.GeographicalFeature, error) {
|
|
var features []*domain.GeographicalFeature
|
|
|
|
query := `
|
|
SELECT * FROM geographical_features
|
|
WHERE ST_Intersects(
|
|
geometry,
|
|
ST_GeomFromText(?, 4326)
|
|
)
|
|
`
|
|
|
|
result := r.DB().WithContext(ctx).Raw(query, wktGeometry).Scan(&features)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
|
|
return features, nil
|
|
}
|
|
|
|
// GetByOSMID retrieves a feature by OSM type and ID
|
|
func (r *GeographicalFeatureRepository) GetByOSMID(ctx context.Context, osmType, osmID string) (*domain.GeographicalFeature, error) {
|
|
return r.FindOneWhereWithContext(ctx, "osm_type = ? AND osm_id = ?", osmType, osmID)
|
|
}
|
|
|
|
// BulkCreate inserts multiple geographical features efficiently
|
|
func (r *GeographicalFeatureRepository) BulkCreate(ctx context.Context, features []*domain.GeographicalFeature) error {
|
|
if len(features) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Use GORM's CreateInBatches for efficient bulk insertion
|
|
result := r.DB().WithContext(ctx).CreateInBatches(features, 100)
|
|
if result.Error != nil {
|
|
return fmt.Errorf("bulk create failed: %w", result.Error)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFeaturesWithinRadius retrieves features of a specific type within a radius of a point
|
|
func (r *GeographicalFeatureRepository) GetFeaturesWithinRadius(ctx context.Context, featureType domain.GeographicalFeatureType, lat, lng, radiusKm float64) ([]*domain.GeographicalFeature, error) {
|
|
var features []*domain.GeographicalFeature
|
|
|
|
query := `
|
|
SELECT * FROM geographical_features
|
|
WHERE feature_type = ?
|
|
AND ST_DWithin(
|
|
geometry::geography,
|
|
ST_GeogFromText('POINT(? ?)'),
|
|
? * 1000
|
|
)
|
|
ORDER BY ST_Distance(geometry::geography, ST_GeogFromText('POINT(? ?)'))
|
|
`
|
|
|
|
result := r.DB().WithContext(ctx).Raw(query, featureType, lng, lat, radiusKm, lng, lat).Scan(&features)
|
|
if result.Error != nil {
|
|
return nil, result.Error
|
|
}
|
|
|
|
return features, nil
|
|
}
|
|
|
|
// GetRoadsWithinRadius retrieves road features within a radius of a point
|
|
func (r *GeographicalFeatureRepository) GetRoadsWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*domain.GeographicalFeature, error) {
|
|
return r.GetFeaturesWithinRadius(ctx, domain.GeographicalFeatureTypeRoad, lat, lng, radiusKm)
|
|
}
|
|
|
|
// GetGreenSpacesWithinRadius retrieves green space features within a radius
|
|
func (r *GeographicalFeatureRepository) GetGreenSpacesWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*domain.GeographicalFeature, error) {
|
|
return r.GetFeaturesWithinRadius(ctx, domain.GeographicalFeatureTypeGreenSpace, lat, lng, radiusKm)
|
|
}
|
|
|
|
// GetTotalArea calculates total area for a feature type within bounds (for green spaces, etc.)
|
|
func (r *GeographicalFeatureRepository) GetTotalArea(ctx context.Context, featureType domain.GeographicalFeatureType, minLat, minLng, maxLat, maxLng float64) (float64, error) {
|
|
var totalArea float64
|
|
|
|
query := `
|
|
SELECT COALESCE(SUM(ST_Area(geometry::geography)), 0)
|
|
FROM geographical_features
|
|
WHERE feature_type = ?
|
|
AND ST_Intersects(
|
|
geometry,
|
|
ST_MakeEnvelope(?, ?, ?, ?, 4326)
|
|
)
|
|
`
|
|
|
|
result := r.DB().WithContext(ctx).Raw(query, featureType, minLng, minLat, maxLng, maxLat).Scan(&totalArea)
|
|
if result.Error != nil {
|
|
return 0, result.Error
|
|
}
|
|
|
|
return totalArea, nil
|
|
}
|
|
|
|
// GetRoadNetworkStatistics returns statistics about the road network
|
|
func (r *GeographicalFeatureRepository) GetRoadNetworkStatistics(ctx context.Context) (map[string]interface{}, error) {
|
|
var stats struct {
|
|
TotalRoads int64
|
|
TotalLengthKm float64
|
|
AvgLengthKm float64
|
|
MaxLengthKm float64
|
|
}
|
|
|
|
// Get basic road counts
|
|
r.DB().Raw("SELECT COUNT(*) FROM geographical_features WHERE feature_type = 'road'").Scan(&stats.TotalRoads)
|
|
|
|
// Get length statistics if we have roads
|
|
if stats.TotalRoads > 0 {
|
|
row := r.DB().Raw(`
|
|
SELECT
|
|
SUM(ST_Length(geometry::geography)) / 1000 as total_length_km,
|
|
AVG(ST_Length(geometry::geography)) / 1000 as avg_length_km,
|
|
MAX(ST_Length(geometry::geography)) / 1000 as max_length_km
|
|
FROM geographical_features
|
|
WHERE feature_type = 'road'
|
|
AND ST_IsValid(geometry)
|
|
`).Row()
|
|
|
|
row.Scan(&stats.TotalLengthKm, &stats.AvgLengthKm, &stats.MaxLengthKm)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"total_roads": stats.TotalRoads,
|
|
"total_length_km": stats.TotalLengthKm,
|
|
"avg_length_km": stats.AvgLengthKm,
|
|
"max_length_km": stats.MaxLengthKm,
|
|
}, nil
|
|
}
|
|
|
|
// Count returns the total number of geographical features
|
|
func (r *GeographicalFeatureRepository) Count(ctx context.Context) (int64, error) {
|
|
var count int64
|
|
result := r.DB().WithContext(ctx).Model(&domain.GeographicalFeature{}).Count(&count)
|
|
return count, result.Error
|
|
}
|
|
|
|
// CountByFeatureType returns the count of features grouped by feature_type
|
|
func (r *GeographicalFeatureRepository) CountByFeatureType(ctx context.Context) (map[domain.GeographicalFeatureType]int64, error) {
|
|
var results []struct {
|
|
FeatureType domain.GeographicalFeatureType
|
|
Count int64
|
|
}
|
|
err := r.DB().WithContext(ctx).Model(&domain.GeographicalFeature{}).
|
|
Select("feature_type, COUNT(*) as count").
|
|
Group("feature_type").
|
|
Scan(&results).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
counts := make(map[domain.GeographicalFeatureType]int64)
|
|
for _, res := range results {
|
|
counts[res.FeatureType] = res.Count
|
|
}
|
|
return counts, nil
|
|
}
|