2020-05-14 11:57:26 +02:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
2022-12-28 20:12:30 +01:00
|
|
|
"math"
|
2020-05-14 11:57:26 +02:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
|
2022-12-28 20:12:30 +01:00
|
|
|
"github.com/dsoprea/go-exif/v3"
|
|
|
|
|
2022-04-15 09:42:07 +02:00
|
|
|
"github.com/photoprism/photoprism/pkg/clean"
|
2022-12-28 20:12:30 +01:00
|
|
|
)
|
2022-04-14 10:49:56 +02:00
|
|
|
|
2022-12-28 20:12:30 +01:00
|
|
|
const (
|
|
|
|
LatMax = 90
|
|
|
|
LngMax = 180
|
2020-05-14 11:57:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var GpsCoordsRegexp = regexp.MustCompile("[0-9\\.]+")
|
|
|
|
var GpsRefRegexp = regexp.MustCompile("[NSEW]+")
|
2021-07-12 21:41:44 +02:00
|
|
|
var GpsFloatRegexp = regexp.MustCompile("[+\\-]?(?:(?:0|[1-9]\\d*)(?:\\.\\d*)?|\\.\\d+)")
|
2020-05-14 11:57:26 +02:00
|
|
|
|
|
|
|
// GpsToLatLng returns the GPS latitude and longitude as float point number.
|
2022-12-28 20:12:30 +01:00
|
|
|
func GpsToLatLng(s string) (lat, lng float64) {
|
2022-04-14 10:49:56 +02:00
|
|
|
// Emtpy?
|
2020-05-14 11:57:26 +02:00
|
|
|
if s == "" {
|
|
|
|
return 0, 0
|
|
|
|
}
|
|
|
|
|
2021-07-12 21:41:44 +02:00
|
|
|
// Floating point numbers?
|
|
|
|
if fl := GpsFloatRegexp.FindAllString(s, -1); len(fl) == 2 {
|
2022-04-14 10:49:56 +02:00
|
|
|
if lat, err := strconv.ParseFloat(fl[0], 64); err != nil {
|
2022-04-15 09:42:07 +02:00
|
|
|
log.Infof("metadata: %s is not a valid gps position", clean.Log(fl[0]))
|
2022-04-14 10:49:56 +02:00
|
|
|
} else if lng, err := strconv.ParseFloat(fl[1], 64); err == nil {
|
2022-12-28 20:12:30 +01:00
|
|
|
return lat, lng
|
2022-04-14 10:49:56 +02:00
|
|
|
}
|
2021-07-12 21:41:44 +02:00
|
|
|
}
|
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
// Parse string values.
|
2020-05-14 11:57:26 +02:00
|
|
|
co := GpsCoordsRegexp.FindAllString(s, -1)
|
|
|
|
re := GpsRefRegexp.FindAllString(s, -1)
|
|
|
|
|
|
|
|
if len(co) != 6 || len(re) != 2 {
|
|
|
|
return 0, 0
|
|
|
|
}
|
|
|
|
|
|
|
|
latDeg := exif.GpsDegrees{
|
|
|
|
Orientation: re[0][0],
|
2022-04-14 10:49:56 +02:00
|
|
|
Degrees: ParseFloat(co[0]),
|
|
|
|
Minutes: ParseFloat(co[1]),
|
|
|
|
Seconds: ParseFloat(co[2]),
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
lngDeg := exif.GpsDegrees{
|
|
|
|
Orientation: re[1][0],
|
2022-04-14 10:49:56 +02:00
|
|
|
Degrees: ParseFloat(co[3]),
|
|
|
|
Minutes: ParseFloat(co[4]),
|
|
|
|
Seconds: ParseFloat(co[5]),
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
2022-12-28 20:12:30 +01:00
|
|
|
return latDeg.Decimal(), lngDeg.Decimal()
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GpsToDecimal returns the GPS latitude or longitude as decimal float point number.
|
2022-12-28 20:12:30 +01:00
|
|
|
func GpsToDecimal(s string) float64 {
|
2022-04-14 10:49:56 +02:00
|
|
|
// Emtpy?
|
2020-05-14 11:57:26 +02:00
|
|
|
if s == "" {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
// Floating point number?
|
|
|
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
2022-12-28 20:12:30 +01:00
|
|
|
return f
|
2022-04-14 10:49:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse string value.
|
2020-05-14 11:57:26 +02:00
|
|
|
co := GpsCoordsRegexp.FindAllString(s, -1)
|
|
|
|
re := GpsRefRegexp.FindAllString(s, -1)
|
|
|
|
|
|
|
|
if len(co) != 3 || len(re) != 1 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
latDeg := exif.GpsDegrees{
|
|
|
|
Orientation: re[0][0],
|
2022-04-14 10:49:56 +02:00
|
|
|
Degrees: ParseFloat(co[0]),
|
|
|
|
Minutes: ParseFloat(co[1]),
|
|
|
|
Seconds: ParseFloat(co[2]),
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
2022-12-28 20:12:30 +01:00
|
|
|
return latDeg.Decimal()
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
// ParseFloat returns a single GPS coordinate value as floating point number (degree, minute or second).
|
|
|
|
func ParseFloat(s string) float64 {
|
|
|
|
// Empty?
|
2020-05-14 11:57:26 +02:00
|
|
|
if s == "" {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
// Parse floating point number.
|
|
|
|
if result, err := strconv.ParseFloat(s, 64); err != nil {
|
2022-04-15 09:42:07 +02:00
|
|
|
log.Debugf("metadata: %s is not a valid gps position", clean.Log(s))
|
2020-07-17 09:28:31 +02:00
|
|
|
return 0
|
2022-04-14 10:49:56 +02:00
|
|
|
} else {
|
|
|
|
return result
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-28 20:12:30 +01:00
|
|
|
|
|
|
|
// NormalizeGPS normalizes the longitude and latitude of the GPS position to a generally valid range.
|
|
|
|
func NormalizeGPS(lat, lng float64) (float32, float32) {
|
|
|
|
if lat < LatMax || lat > LatMax || lng < LngMax || lng > LngMax {
|
|
|
|
// Clip the latitude. Normalise the longitude.
|
|
|
|
lat, lng = clipLat(lat), normalizeLng(lng)
|
|
|
|
}
|
|
|
|
|
|
|
|
return float32(lat), float32(lng)
|
|
|
|
}
|
|
|
|
|
|
|
|
func clipLat(lat float64) float64 {
|
|
|
|
if lat > LatMax*2 {
|
|
|
|
return math.Mod(lat, LatMax)
|
|
|
|
} else if lat > LatMax {
|
|
|
|
return lat - LatMax
|
|
|
|
}
|
|
|
|
|
|
|
|
if lat < -LatMax*2 {
|
|
|
|
return math.Mod(lat, LatMax)
|
|
|
|
} else if lat < -LatMax {
|
|
|
|
return lat + LatMax
|
|
|
|
}
|
|
|
|
|
|
|
|
return lat
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeLng(value float64) float64 {
|
|
|
|
return normalizeCoord(value, LngMax)
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeCoord(value, max float64) float64 {
|
|
|
|
for value < -max {
|
|
|
|
value += 2 * max
|
|
|
|
}
|
|
|
|
for value >= max {
|
|
|
|
value -= 2 * max
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|