gps.go (3459B)
1 package exif 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/dsoprea/go-logging" 9 "github.com/golang/geo/s2" 10 11 "github.com/dsoprea/go-exif/v3/common" 12 ) 13 14 var ( 15 // ErrGpsCoordinatesNotValid means that some part of the geographic data was 16 // unparseable. 17 ErrGpsCoordinatesNotValid = errors.New("GPS coordinates not valid") 18 ) 19 20 // GpsDegrees is a high-level struct representing geographic data. 21 type GpsDegrees struct { 22 // Orientation describes the N/E/S/W direction that this position is 23 // relative to. 24 Orientation byte 25 26 // Degrees is a simple float representing the underlying rational degrees 27 // amount. 28 Degrees float64 29 30 // Minutes is a simple float representing the underlying rational minutes 31 // amount. 32 Minutes float64 33 34 // Seconds is a simple float representing the underlying ration seconds 35 // amount. 36 Seconds float64 37 } 38 39 // NewGpsDegreesFromRationals returns a GpsDegrees struct given the EXIF-encoded 40 // information. The refValue is the N/E/S/W direction that this position is 41 // relative to. 42 func NewGpsDegreesFromRationals(refValue string, rawCoordinate []exifcommon.Rational) (gd GpsDegrees, err error) { 43 defer func() { 44 if state := recover(); state != nil { 45 err = log.Wrap(state.(error)) 46 } 47 }() 48 49 if len(rawCoordinate) != 3 { 50 log.Panicf("new GpsDegrees struct requires a raw-coordinate with exactly three rationals") 51 } 52 53 gd = GpsDegrees{ 54 Orientation: refValue[0], 55 Degrees: float64(rawCoordinate[0].Numerator) / float64(rawCoordinate[0].Denominator), 56 Minutes: float64(rawCoordinate[1].Numerator) / float64(rawCoordinate[1].Denominator), 57 Seconds: float64(rawCoordinate[2].Numerator) / float64(rawCoordinate[2].Denominator), 58 } 59 60 return gd, nil 61 } 62 63 // String provides returns a descriptive string. 64 func (d GpsDegrees) String() string { 65 return fmt.Sprintf("Degrees<O=[%s] D=(%g) M=(%g) S=(%g)>", string([]byte{d.Orientation}), d.Degrees, d.Minutes, d.Seconds) 66 } 67 68 // Decimal calculates and returns the simplified float representation of the 69 // component degrees. 70 func (d GpsDegrees) Decimal() float64 { 71 decimal := float64(d.Degrees) + float64(d.Minutes)/60.0 + float64(d.Seconds)/3600.0 72 73 if d.Orientation == 'S' || d.Orientation == 'W' { 74 return -decimal 75 } 76 77 return decimal 78 } 79 80 // Raw returns a Rational struct that can be used to *write* coordinates. In 81 // practice, the denominator are typically (1) in the original EXIF data, and, 82 // that being the case, this will best preserve precision. 83 func (d GpsDegrees) Raw() []exifcommon.Rational { 84 return []exifcommon.Rational{ 85 {Numerator: uint32(d.Degrees), Denominator: 1}, 86 {Numerator: uint32(d.Minutes), Denominator: 1}, 87 {Numerator: uint32(d.Seconds), Denominator: 1}, 88 } 89 } 90 91 // GpsInfo encapsulates all of the geographic information in one place. 92 type GpsInfo struct { 93 Latitude, Longitude GpsDegrees 94 Altitude int 95 Timestamp time.Time 96 } 97 98 // String returns a descriptive string. 99 func (gi *GpsInfo) String() string { 100 return fmt.Sprintf("GpsInfo<LAT=(%.05f) LON=(%.05f) ALT=(%d) TIME=[%s]>", 101 gi.Latitude.Decimal(), gi.Longitude.Decimal(), gi.Altitude, gi.Timestamp) 102 } 103 104 // S2CellId returns the cell-ID of the geographic location on the earth. 105 func (gi *GpsInfo) S2CellId() s2.CellID { 106 latitude := gi.Latitude.Decimal() 107 longitude := gi.Longitude.Decimal() 108 109 ll := s2.LatLngFromDegrees(latitude, longitude) 110 cellId := s2.CellIDFromLatLng(ll) 111 112 if cellId.IsValid() == false { 113 panic(ErrGpsCoordinatesNotValid) 114 } 115 116 return cellId 117 }