...

Source file src/github.com/dsoprea/go-exif/v3/gps.go

Documentation: github.com/dsoprea/go-exif/v3

     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  }
   118  

View as plain text