Skip to content

Commit

Permalink
Add location information to timeline (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmilyOrg committed Aug 12, 2023
2 parents 9c582f7 + d89cb90 commit 013ee69
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 14 deletions.
2 changes: 2 additions & 0 deletions db/migrations/000013_add_gps_coords.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE infos DROP COLUMN "longitude" text;
ALTER TABLE infos DROP COLUMN "latitude" text;
2 changes: 2 additions & 0 deletions db/migrations/000013_add_gps_coords.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE infos ADD COLUMN "latitude" REAL;
ALTER TABLE infos ADD COLUMN "longitude" REAL;
10 changes: 10 additions & 0 deletions defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ tags:
# exif:
# enable: true

geo:
# Reverse geocode coordinates to location names. Runs fully locally
# via the "rgeo" Golang library. Currently only supported in the
# timeline layout.
#
# Can delay startup by up to a minute as the local geolocation
# database is loaded.
#
# reverse_geocode: true

media:
# Extract metadata from this many files concurrently
concurrent_meta_loads: 8
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ require (
github.com/go-chi/render v1.0.1
github.com/goccy/go-yaml v1.7.17
github.com/golang-migrate/migrate/v4 v4.15.0-beta.1
github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35
github.com/gosimple/slug v1.10.0
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
github.com/imdario/mergo v0.3.12
github.com/imdario/mergo v0.3.13
github.com/joho/godotenv v1.3.0
github.com/karrick/godirwalk v1.15.6
github.com/kelindar/intmap v1.1.0
Expand All @@ -30,6 +31,7 @@ require (
github.com/prometheus/client_model v0.2.0
github.com/pyroscope-io/client v0.7.0
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sams96/rgeo v1.2.0
github.com/sheerun/queue v1.0.1
github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365
github.com/x448/float16 v0.8.4
Expand Down Expand Up @@ -74,6 +76,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/tdewolff/minify/v2 v2.7.1-0.20200112204046-70870d25a935 // indirect
github.com/tdewolff/parse/v2 v2.4.2 // indirect
github.com/twpayne/go-geom v1.4.4 // indirect
github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/mod v0.4.2 // indirect
Expand Down
17 changes: 12 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
Expand Down Expand Up @@ -249,6 +250,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35 h1:enTowfyfjtomBQhxX9mhUD+0tZhpe4rIzStO4aNlou8=
github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -357,8 +360,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
Expand Down Expand Up @@ -586,6 +589,8 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sams96/rgeo v1.2.0 h1:49cBHeXSADHGHYUsNByhzrzZXkOwYU4+/Vxv7lQn/mY=
github.com/sams96/rgeo v1.2.0/go.mod h1:ngWuABNhG1zT7afDMW5JGZHQgWeNUfPf8Ag5gaojKjA=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sheerun/queue v1.0.1 h1:TIAQyN0aRRvrJcNa2beZFfxwuxrfXBc9Mj+UWDNH7Ao=
github.com/sheerun/queue v1.0.1/go.mod h1:YtjrWT5jymvCLo/lEWDk3sv7A1Kgj0qcl3SZx7Zmcfo=
Expand Down Expand Up @@ -618,7 +623,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365 h1:iNyEAGvN8yNc5/maTbLhStXRIqp+PSxpjG68KRtU3Y4=
github.com/tdewolff/canvas v0.0.0-20200504121106-e2600b35c365/go.mod h1:DCuQBGs+Nm73wH9S/z1tlUKDbAPCGa6W7A/DHU1ENmQ=
github.com/tdewolff/minify/v2 v2.7.1-0.20200112204046-70870d25a935 h1:nRG5jPGtwJpQ8KtrqhGVdLAuOnk4YWfNxh4Kx9XMuAw=
Expand All @@ -628,6 +633,8 @@ github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1I
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/twpayne/go-geom v1.4.4 h1:bcCPAvvNSzjmpUqR0Uqh39ClCKtPx6kZVR7EakQaVJI=
github.com/twpayne/go-geom v1.4.4/go.mod h1:Kz4sX4LtdesDQgkhsMERazLlH/NiCg90s6FPaNr0KNI=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
Expand Down Expand Up @@ -1091,10 +1098,10 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
Expand Down
46 changes: 40 additions & 6 deletions internal/image/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/sqlite"
"github.com/golang-migrate/migrate/v4/source/httpfs"
"github.com/golang/geo/s2"
)

var dateFormat = "2006-01-02 15:04:05.999999 -07:00"
Expand Down Expand Up @@ -79,6 +80,7 @@ type InfoExistence struct {
SizeNull bool
OrientationNull bool
DateTimeNull bool
LatLngNull bool
ColorNull bool
}

Expand Down Expand Up @@ -207,21 +209,25 @@ func (source *Database) writePendingInfosSqlite() {
defer upsertPrefix.Finalize()

updateMeta := conn.Prep(`
INSERT INTO infos(path_prefix_id, filename, width, height, orientation, created_at_unix, created_at_tz_offset)
INSERT INTO infos(path_prefix_id, filename, width, height, orientation, created_at_unix, created_at_tz_offset, latitude, longitude)
SELECT
id as path_prefix_id,
? as filename,
? as width,
? as height,
? orientation,
? as created_at_unix,
? as created_at_tz_offset
? as created_at_tz_offset,
? as latitude,
? as longitude
FROM prefix
WHERE str == ?
ON CONFLICT(path_prefix_id, filename) DO UPDATE SET
width=excluded.width,
height=excluded.height,
orientation=excluded.orientation,
latitude=excluded.latitude,
longitude=excluded.longitude,
created_at_unix=excluded.created_at_unix,
created_at_tz_offset=excluded.created_at_tz_offset;`)
defer updateMeta.Finalize()
Expand Down Expand Up @@ -395,7 +401,14 @@ func (source *Database) writePendingInfosSqlite() {
updateMeta.BindInt64(4, (int64)(imageInfo.Orientation))
updateMeta.BindInt64(5, imageInfo.DateTime.Unix())
updateMeta.BindInt64(6, int64(timezoneOffsetSeconds/60))
updateMeta.BindText(7, dir)
if IsNaNLatLng(imageInfo.LatLng) {
updateMeta.BindNull(7)
updateMeta.BindNull(8)
} else {
updateMeta.BindFloat(7, imageInfo.LatLng.Lat.Degrees())
updateMeta.BindFloat(8, imageInfo.LatLng.Lng.Degrees())
}
updateMeta.BindText(9, dir)

_, err := updateMeta.Step()
if err != nil {
Expand Down Expand Up @@ -664,7 +677,7 @@ func (source *Database) Get(id ImageId) (InfoResult, bool) {
defer source.pool.Put(conn)

stmt := conn.Prep(`
SELECT width, height, orientation, color, created_at
SELECT width, height, orientation, color, created_at, latitude, longitude
FROM infos
WHERE id == ?;`)
defer stmt.Reset()
Expand All @@ -691,6 +704,13 @@ func (source *Database) Get(id ImageId) (InfoResult, bool) {
info.DateTime, _ = time.Parse(dateFormat, stmt.ColumnText(4))
info.DateTimeNull = stmt.ColumnType(4) == sqlite.TypeNull

info.LatLngNull = stmt.ColumnType(5) == sqlite.TypeNull || stmt.ColumnType(6) == sqlite.TypeNull
if info.LatLngNull {
info.LatLng = NaNLatLng()
} else {
info.LatLng = s2.LatLngFromDegrees(stmt.ColumnFloat(5), stmt.ColumnFloat(6))
}

return info, true
}

Expand All @@ -702,7 +722,7 @@ func (source *Database) GetBatch(ids []ImageId) <-chan InfoListResult {
defer source.pool.Put(conn)

sql := `
SELECT id, width, height, orientation, color, created_at_unix, created_at_tz_offset
SELECT id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude
FROM infos
WHERE id IN (`

Expand Down Expand Up @@ -745,6 +765,13 @@ func (source *Database) GetBatch(ids []ImageId) <-chan InfoListResult {
info.DateTime = time.Unix(unix, 0).In(time.FixedZone("tz_offset", timezoneOffset*60))
info.DateTimeNull = stmt.ColumnType(5) == sqlite.TypeNull

info.LatLngNull = stmt.ColumnType(7) == sqlite.TypeNull || stmt.ColumnType(8) == sqlite.TypeNull
if info.LatLngNull {
info.LatLng = NaNLatLng()
} else {
info.LatLng = s2.LatLngFromDegrees(stmt.ColumnFloat(7), stmt.ColumnFloat(8))
}

out <- info
}
close(out)
Expand Down Expand Up @@ -1273,7 +1300,7 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList
}

sql += `
SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset
SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude
FROM infos
`

Expand Down Expand Up @@ -1369,6 +1396,13 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList
info.DateTime = time.Unix(unix, 0).In(time.FixedZone("tz_offset", timezoneOffset*60))
info.DateTimeNull = stmt.ColumnType(5) == sqlite.TypeNull

info.LatLngNull = stmt.ColumnType(7) == sqlite.TypeNull || stmt.ColumnType(8) == sqlite.TypeNull
if info.LatLngNull {
info.LatLng = NaNLatLng()
} else {
info.LatLng = s2.LatLngFromDegrees(stmt.ColumnFloat(7), stmt.ColumnFloat(8))
}

out <- info
}

Expand Down
34 changes: 34 additions & 0 deletions internal/image/exiftool-mostlygeek.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package image
import (
"bufio"
"errors"
"math"
"photofield/tag"
"regexp"
"strconv"
"strings"
"time"

"github.com/golang/geo/s2"
"github.com/mostlygeek/go-exiftool"
)

Expand Down Expand Up @@ -50,6 +52,9 @@ func NewExifToolMostlyGeekLoader(exifToolCount int) (*ExifToolMostlyGeekLoader,
"-TimeStamp#",
"-FileModifyDate#",
"-FileCreateDate#",
// Location Info
"-GPSLatitude#",
"-GPSLongitude#",
)
return decoder, err
}
Expand All @@ -70,6 +75,8 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([]
rotation := ""
imageWidth := ""
imageHeight := ""
latitude := ""
longitude := ""

// var gpsTime time.Time

Expand All @@ -93,6 +100,13 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([]
imageWidth = value
case "ImageHeight":
imageHeight = value
case "GPSLatitude":
latitude = value
case "GPSLongitude":
longitude = value

// case "GPSDateTime":
// gpsTime, _ = parseDateTime(value)
default:
if name, ok := tag.ExifTagToName[name]; ok {
tags = append(tags, tag.NewExif(name, value))
Expand Down Expand Up @@ -139,6 +153,26 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([]
info.Orientation = getOrientationFromRotation(rotation)
}

lat := math.NaN()
lng := math.NaN()
if latitude != "" && longitude != "" {
lat, err = strconv.ParseFloat(latitude, 64)
if err != nil {
lat = math.NaN()
}

lng, err = strconv.ParseFloat(longitude, 64)
if err != nil {
lng = math.NaN()
}
}

if !math.IsNaN(lat) && !math.IsNaN(lng) {
info.LatLng = s2.LatLngFromDegrees(lat, lng)
} else {
info.LatLng = NaNLatLng()
}

if info.Orientation.SwapsDimensions() {
info.Width, info.Height = info.Height, info.Width
}
Expand Down
22 changes: 21 additions & 1 deletion internal/image/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"fmt"
"image"
"image/color"
"math"
"time"

"github.com/golang/geo/s1"
"github.com/golang/geo/s2"
)

type Size = image.Point
Expand All @@ -14,19 +18,35 @@ type Info struct {
DateTime time.Time
Color uint32
Orientation Orientation
LatLng s2.LatLng
}

const earthRadiusKm = 6371.01

func NaNLatLng() s2.LatLng {
return s2.LatLng{Lat: s1.Angle(math.NaN()), Lng: s1.Angle(math.NaN())}
}

func IsNaNLatLng(latlng s2.LatLng) bool {
return math.IsNaN(float64(latlng.Lat)) || math.IsNaN(float64(latlng.Lng))
}

func AngleToKm(a s1.Angle) float64 {
return a.Radians() * earthRadiusKm
}

func (info *Info) Size() Size {
return Size{X: info.Width, Y: info.Height}
}

func (info *Info) String() string {
return fmt.Sprintf("width: %v, height: %v, date: %v, color: %08x, orientation: %s",
return fmt.Sprintf("width: %v, height: %v, date: %v, color: %08x, orientation: %s, latlng: %s",
info.Width,
info.Height,
info.DateTime.String(),
info.Color,
info.Orientation,
info.LatLng.String(),
)
}

Expand Down
Loading

0 comments on commit 013ee69

Please sign in to comment.