mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2024-12-22 10:43:12 +00:00
117 lines
3.4 KiB
Go
117 lines
3.4 KiB
Go
package exif
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/dsoprea/go-logging"
|
|
"github.com/golang/geo/s2"
|
|
|
|
"github.com/dsoprea/go-exif/v3/common"
|
|
)
|
|
|
|
var (
|
|
// ErrGpsCoordinatesNotValid means that some part of the geographic data was
|
|
// unparseable.
|
|
ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid")
|
|
)
|
|
|
|
// GpsDegrees is a high-level struct representing geographic data.
|
|
type GpsDegrees struct {
|
|
// Orientation describes the N/E/S/W direction that this position is
|
|
// relative to.
|
|
Orientation byte
|
|
|
|
// Degrees is a simple float representing the underlying rational degrees
|
|
// amount.
|
|
Degrees float64
|
|
|
|
// Minutes is a simple float representing the underlying rational minutes
|
|
// amount.
|
|
Minutes float64
|
|
|
|
// Seconds is a simple float representing the underlying ration seconds
|
|
// amount.
|
|
Seconds float64
|
|
}
|
|
|
|
// NewGpsDegreesFromRationals returns a GpsDegrees struct given the EXIF-encoded
|
|
// information. The refValue is the N/E/S/W direction that this position is
|
|
// relative to.
|
|
func NewGpsDegreesFromRationals(refValue string, rawCoordinate []exifcommon.Rational) (gd GpsDegrees, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
if len(rawCoordinate) != 3 {
|
|
log.Panicf("new GpsDegrees struct requires a raw-coordinate with exactly three rationals")
|
|
}
|
|
|
|
gd = GpsDegrees{
|
|
Orientation: refValue[0],
|
|
Degrees: float64(rawCoordinate[0].Numerator) / float64(rawCoordinate[0].Denominator),
|
|
Minutes: float64(rawCoordinate[1].Numerator) / float64(rawCoordinate[1].Denominator),
|
|
Seconds: float64(rawCoordinate[2].Numerator) / float64(rawCoordinate[2].Denominator),
|
|
}
|
|
|
|
return gd, nil
|
|
}
|
|
|
|
// String provides returns a descriptive string.
|
|
func (d GpsDegrees) String() string {
|
|
return fmt.Sprintf("Degrees<O=[%s] D=(%g) M=(%g) S=(%g)>", string([]byte{d.Orientation}), d.Degrees, d.Minutes, d.Seconds)
|
|
}
|
|
|
|
// Decimal calculates and returns the simplified float representation of the
|
|
// component degrees.
|
|
func (d GpsDegrees) Decimal() float64 {
|
|
decimal := float64(d.Degrees) + float64(d.Minutes)/60.0 + float64(d.Seconds)/3600.0
|
|
|
|
if d.Orientation == 'S' || d.Orientation == 'W' {
|
|
return -decimal
|
|
}
|
|
|
|
return decimal
|
|
}
|
|
|
|
// Raw returns a Rational struct that can be used to *write* coordinates. In
|
|
// practice, the denominator are typically (1) in the original EXIF data, and,
|
|
// that being the case, this will best preserve precision.
|
|
func (d GpsDegrees) Raw() []exifcommon.Rational {
|
|
return []exifcommon.Rational{
|
|
{Numerator: uint32(d.Degrees), Denominator: 1},
|
|
{Numerator: uint32(d.Minutes), Denominator: 1},
|
|
{Numerator: uint32(d.Seconds), Denominator: 1},
|
|
}
|
|
}
|
|
|
|
// GpsInfo encapsulates all of the geographic information in one place.
|
|
type GpsInfo struct {
|
|
Latitude, Longitude GpsDegrees
|
|
Altitude int
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// String returns a descriptive string.
|
|
func (gi *GpsInfo) String() string {
|
|
return fmt.Sprintf("GpsInfo<LAT=(%.05f) LON=(%.05f) ALT=(%d) TIME=[%s]>",
|
|
gi.Latitude.Decimal(), gi.Longitude.Decimal(), gi.Altitude, gi.Timestamp)
|
|
}
|
|
|
|
// S2CellId returns the cell-ID of the geographic location on the earth.
|
|
func (gi *GpsInfo) S2CellId() s2.CellID {
|
|
latitude := gi.Latitude.Decimal()
|
|
longitude := gi.Longitude.Decimal()
|
|
|
|
ll := s2.LatLngFromDegrees(latitude, longitude)
|
|
cellId := s2.CellIDFromLatLng(ll)
|
|
|
|
if cellId.IsValid() == false {
|
|
panic(ErrGpsCoordinatesNotValid)
|
|
}
|
|
|
|
return cellId
|
|
}
|