2023-09-19 22:03:40 +02:00
|
|
|
package clean
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2023-09-20 03:18:30 +02:00
|
|
|
"math"
|
2023-09-19 22:03:40 +02:00
|
|
|
"strings"
|
|
|
|
|
2023-09-20 16:56:38 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/geo"
|
2023-09-19 22:03:40 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/txt"
|
|
|
|
)
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// gpsCeil converts a GPS coordinate to a rounded float32 for use in queries.
|
|
|
|
func gpsCeil(f float64) float32 {
|
|
|
|
return float32((math.Ceil(f*10000) / 10000) + 0.0001)
|
|
|
|
}
|
|
|
|
|
|
|
|
// gpsFloor converts a GPS coordinate to a rounded float32 for use in queries.
|
|
|
|
func gpsFloor(f float64) float32 {
|
|
|
|
return float32((math.Floor(f*10000) / 10000) - 0.0001)
|
|
|
|
}
|
|
|
|
|
2023-09-19 22:03:40 +02:00
|
|
|
// GPSBounds parses the GPS bounds (Lat N, Lng E, Lat S, Lng W) and returns the coordinates if any.
|
2023-09-20 03:18:30 +02:00
|
|
|
func GPSBounds(bounds string) (latN, lngE, latS, lngW float32, err error) {
|
|
|
|
// Bounds string not long enough?
|
2023-09-19 22:03:40 +02:00
|
|
|
if len(bounds) < 7 {
|
|
|
|
return 0, 0, 0, 0, fmt.Errorf("no coordinates found")
|
|
|
|
}
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// Trim whitespace and invalid characters.
|
|
|
|
bounds = strings.Trim(bounds, " |\\<>\n\r\t\"'#$%!^*()[]{}")
|
|
|
|
|
|
|
|
// Split string into values.
|
2023-09-19 22:03:40 +02:00
|
|
|
values := strings.SplitN(bounds, ",", 5)
|
|
|
|
found := len(values)
|
|
|
|
|
|
|
|
// Invalid number of values?
|
|
|
|
if found != 4 {
|
|
|
|
return 0, 0, 0, 0, fmt.Errorf("invalid number of coordinates")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse floating point coordinates.
|
2023-09-20 03:18:30 +02:00
|
|
|
latNorth, lngEast, latSouth, lngWest := txt.Float(values[0]), txt.Float(values[1]), txt.Float(values[2]), txt.Float(values[3])
|
2023-09-19 22:03:40 +02:00
|
|
|
|
|
|
|
// Latitudes (from +90 to -90 degrees).
|
|
|
|
if latNorth > 90 {
|
|
|
|
latNorth = 90
|
|
|
|
} else if latNorth < -90 {
|
|
|
|
latNorth = -90
|
|
|
|
}
|
|
|
|
|
|
|
|
if latSouth > 90 {
|
|
|
|
latSouth = 90
|
|
|
|
} else if latSouth < -90 {
|
|
|
|
latSouth = -90
|
|
|
|
}
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// latSouth must be smaller.
|
|
|
|
if latSouth > latNorth {
|
2023-09-19 22:03:40 +02:00
|
|
|
latNorth, latSouth = latSouth, latNorth
|
|
|
|
}
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// Longitudes (from -180 to +180 degrees).
|
2023-09-19 22:03:40 +02:00
|
|
|
if lngEast > 180 {
|
|
|
|
lngEast = 180
|
|
|
|
} else if lngEast < -180 {
|
|
|
|
lngEast = -180
|
|
|
|
}
|
|
|
|
|
|
|
|
if lngWest > 180 {
|
|
|
|
lngWest = 180
|
|
|
|
} else if lngWest < -180 {
|
|
|
|
lngWest = -180
|
|
|
|
}
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// lngWest must be smaller.
|
|
|
|
if lngWest > lngEast {
|
2023-09-19 22:03:40 +02:00
|
|
|
lngEast, lngWest = lngWest, lngEast
|
|
|
|
}
|
|
|
|
|
2023-09-20 03:18:30 +02:00
|
|
|
// Return rounded coordinates.
|
|
|
|
return gpsCeil(latNorth), gpsCeil(lngEast), gpsFloor(latSouth), gpsFloor(lngWest), nil
|
2023-09-19 22:03:40 +02:00
|
|
|
}
|
2023-09-20 16:56:38 +02:00
|
|
|
|
|
|
|
// GPSLatRange returns a range based on the specified latitude and distance in km, or an error otherwise.
|
2023-09-20 18:37:41 +02:00
|
|
|
func GPSLatRange(lat float64, km float64) (latN, latS float32, err error) {
|
2023-09-20 16:56:38 +02:00
|
|
|
// Latitude (from +90 to -90 degrees).
|
|
|
|
if lat == 0 || lat < -90 || lat > 90 {
|
|
|
|
return 0, 0, fmt.Errorf("invalid latitude")
|
|
|
|
}
|
|
|
|
|
2023-09-20 17:42:38 +02:00
|
|
|
// Approximate range radius.
|
2023-09-20 18:37:41 +02:00
|
|
|
r := km * 0.75
|
2023-09-20 17:42:38 +02:00
|
|
|
|
|
|
|
// Approximate longitude range,
|
|
|
|
// see https://en.wikipedia.org/wiki/Decimal_degrees
|
|
|
|
latN = gpsCeil(lat + geo.Deg(r))
|
|
|
|
latS = gpsFloor(lat - geo.Deg(r))
|
2023-09-20 16:56:38 +02:00
|
|
|
|
|
|
|
if latN > 90 {
|
|
|
|
latN = 90
|
|
|
|
}
|
|
|
|
|
|
|
|
if latS < -90 {
|
|
|
|
latS = -90
|
|
|
|
}
|
|
|
|
|
|
|
|
return latN, latS, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GPSLngRange returns a range based on the specified longitude and distance in km, or an error otherwise.
|
2023-09-20 18:37:41 +02:00
|
|
|
func GPSLngRange(lng float64, km float64) (lngE, lngW float32, err error) {
|
2023-09-20 16:56:38 +02:00
|
|
|
// Longitude (from -180 to +180 degrees).
|
|
|
|
if lng == 0 || lng < -180 || lng > 180 {
|
|
|
|
return 0, 0, fmt.Errorf("invalid longitude")
|
|
|
|
}
|
|
|
|
|
2023-09-20 17:42:38 +02:00
|
|
|
// Approximate range radius.
|
2023-09-20 18:37:41 +02:00
|
|
|
r := km * 0.75
|
2023-09-20 17:42:38 +02:00
|
|
|
|
|
|
|
// Approximate longitude range,
|
|
|
|
// see https://en.wikipedia.org/wiki/Decimal_degrees
|
|
|
|
lngE = gpsCeil(lng + geo.Deg(r))
|
|
|
|
lngW = gpsFloor(lng - geo.Deg(r))
|
2023-09-20 16:56:38 +02:00
|
|
|
|
|
|
|
if lngE > 180 {
|
|
|
|
lngE = 180
|
|
|
|
}
|
|
|
|
|
|
|
|
if lngW < -180 {
|
|
|
|
lngW = -180
|
|
|
|
}
|
|
|
|
|
|
|
|
return lngE, lngW, nil
|
|
|
|
}
|