mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
74 lines
2.7 KiB
Go
74 lines
2.7 KiB
Go
package geospatial
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GeoHelper centralizes common, parameterized PostGIS fragments and checks.
|
|
// Purpose: avoid repeated SQL fragments and parameter ordering bugs like 42P18
|
|
type GeoHelper struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewGeoHelper(db *gorm.DB) *GeoHelper {
|
|
return &GeoHelper{db: db}
|
|
}
|
|
|
|
// PointExpr returns a parameterized point expression suitable for use in
|
|
// Raw SQL with placeholder parameters for longitude and latitude (in that order).
|
|
// Example: ST_SetSRID(ST_MakePoint(?::double precision, ?::double precision), 4326)
|
|
func (g *GeoHelper) PointExpr() string {
|
|
return "ST_SetSRID(ST_MakePoint(?::double precision, ?::double precision), 4326)"
|
|
}
|
|
|
|
// DWithinExpr returns a parameterized ST_DWithin expression using the provided
|
|
// geometry column name and the helper's PointExpr. The final parameter expected
|
|
// by the expression is the radius (in meters if used with geography).
|
|
func (g *GeoHelper) DWithinExpr(geomCol string) string {
|
|
return fmt.Sprintf("ST_DWithin(%s::geography, %s::geography, ? * 1000)", geomCol, g.PointExpr())
|
|
}
|
|
|
|
// OrderByDistanceExpr returns an ORDER BY fragment that orders by distance
|
|
// between the geometry column and a parameterized point.
|
|
func (g *GeoHelper) OrderByDistanceExpr(geomCol string) string {
|
|
return fmt.Sprintf("%s <-> %s", geomCol, g.PointExpr())
|
|
}
|
|
|
|
// PointArgs returns ordered args for the point placeholders: longitude, latitude
|
|
func (g *GeoHelper) PointArgs(lng, lat float64) []interface{} {
|
|
return []interface{}{lng, lat}
|
|
}
|
|
|
|
// PointRadiusArgs returns args in order for queries that need (lng, lat, radius)
|
|
// - common for ST_DWithin where we use point twice (distance and order by) set includeOrderBy
|
|
// If includeOrderBy is true, it returns [lng, lat, radius, lng, lat]
|
|
// otherwise [lng, lat, radius]
|
|
func (g *GeoHelper) PointRadiusArgs(lng, lat, radiusKm float64, includeOrderBy bool) []interface{} {
|
|
if includeOrderBy {
|
|
return []interface{}{lng, lat, radiusKm, lng, lat}
|
|
}
|
|
return []interface{}{lng, lat, radiusKm}
|
|
}
|
|
|
|
// PostGISAvailable checks if PostGIS extension exists on the connected DB.
|
|
// Returns true if extension is present; errors are returned for DB issues.
|
|
func (g *GeoHelper) PostGISAvailable() (bool, error) {
|
|
var exists bool
|
|
if err := g.db.Raw("SELECT EXISTS(SELECT 1 FROM pg_extension WHERE extname = 'postgis')").Scan(&exists).Error; err != nil {
|
|
return false, err
|
|
}
|
|
return exists, nil
|
|
}
|
|
|
|
// ColumnExists checks information_schema for a table/column existence
|
|
func (g *GeoHelper) ColumnExists(table, column string) (bool, error) {
|
|
var exists bool
|
|
q := `SELECT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = ? AND column_name = ?)`
|
|
if err := g.db.Raw(q, table, column).Scan(&exists).Error; err != nil {
|
|
return false, err
|
|
}
|
|
return exists, nil
|
|
}
|