Skip to content

Commit

Permalink
Refactor to not store location strings
Browse files Browse the repository at this point in the history
* Use standard LatLng for storing geolocation
* Fix migrations to take into account latest main
* Better location text construction
  • Loading branch information
SmilyOrg committed Aug 3, 2023
1 parent d7c78e5 commit 1168370
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 69 deletions.
3 changes: 0 additions & 3 deletions db/migrations/000012_add_gps_coords.down.sql

This file was deleted.

3 changes: 0 additions & 3 deletions db/migrations/000012_add_gps_coords.up.sql

This file was deleted.

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;
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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.13
Expand Down Expand Up @@ -52,7 +53,6 @@ require (
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 // indirect
Expand Down
45 changes: 29 additions & 16 deletions internal/image/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"log"
"math"
"net/http"
"path/filepath"
"strconv"
Expand All @@ -24,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 @@ -80,6 +80,7 @@ type InfoExistence struct {
SizeNull bool
OrientationNull bool
DateTimeNull bool
LatLngNull bool
ColorNull bool
}

Expand Down Expand Up @@ -208,7 +209,7 @@ 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, latitude, longitude, location)
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,
Expand All @@ -218,8 +219,7 @@ func (source *Database) writePendingInfosSqlite() {
? as created_at_unix,
? as created_at_tz_offset,
? as latitude,
? as longitude,
? as location
? as longitude
FROM prefix
WHERE str == ?
ON CONFLICT(path_prefix_id, filename) DO UPDATE SET
Expand All @@ -228,7 +228,6 @@ func (source *Database) writePendingInfosSqlite() {
orientation=excluded.orientation,
latitude=excluded.latitude,
longitude=excluded.longitude,
location=excluded.location,
created_at_unix=excluded.created_at_unix,
created_at_tz_offset=excluded.created_at_tz_offset;`)
defer updateMeta.Finalize()
Expand Down Expand Up @@ -402,15 +401,14 @@ func (source *Database) writePendingInfosSqlite() {
updateMeta.BindInt64(4, (int64)(imageInfo.Orientation))
updateMeta.BindInt64(5, imageInfo.DateTime.Unix())
updateMeta.BindInt64(6, int64(timezoneOffsetSeconds/60))
if math.IsNaN(imageInfo.Latitude) {
if IsNaNLatLng(imageInfo.LatLng) {
updateMeta.BindNull(7)
updateMeta.BindNull(8)
} else {
updateMeta.BindFloat(7, imageInfo.Latitude)
updateMeta.BindFloat(8, imageInfo.Longitude)
updateMeta.BindFloat(7, imageInfo.LatLng.Lat.Degrees())
updateMeta.BindFloat(8, imageInfo.LatLng.Lng.Degrees())
}
updateMeta.BindText(9, imageInfo.Location)
updateMeta.BindText(10, dir)
updateMeta.BindText(9, dir)

_, err := updateMeta.Step()
if err != nil {
Expand Down Expand Up @@ -679,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, location
SELECT width, height, orientation, color, created_at, latitude, longitude
FROM infos
WHERE id == ?;`)
defer stmt.Reset()
Expand All @@ -706,7 +704,12 @@ func (source *Database) Get(id ImageId) (InfoResult, bool) {
info.DateTime, _ = time.Parse(dateFormat, stmt.ColumnText(4))
info.DateTimeNull = stmt.ColumnType(4) == sqlite.TypeNull

info.Location = stmt.ColumnText(5)
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 @@ -719,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, location
SELECT id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude
FROM infos
WHERE id IN (`

Expand Down Expand Up @@ -762,7 +765,12 @@ 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.Location = stmt.ColumnText(7)
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 Expand Up @@ -1292,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, location
SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset, latitude, longitude
FROM infos
`

Expand Down Expand Up @@ -1388,7 +1396,12 @@ 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.Location = stmt.ColumnText(7)
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
21 changes: 14 additions & 7 deletions internal/image/exiftool-mostlygeek.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"time"

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

Expand Down Expand Up @@ -152,20 +153,26 @@ func (decoder *ExifToolMostlyGeekLoader) DecodeInfo(path string, info *Info) ([]
info.Orientation = getOrientationFromRotation(rotation)
}

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

if longitude != "" {
info.Longitude, err = strconv.ParseFloat(longitude, 64)
lng, err = strconv.ParseFloat(longitude, 64)
if err != nil {
info.Longitude = math.NaN()
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
17 changes: 0 additions & 17 deletions internal/image/indexMetadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package image

import (
"fmt"
"math"


)

func (source *Source) indexMetadata(in <-chan interface{}) {
Expand All @@ -19,20 +16,6 @@ func (source *Source) indexMetadata(in <-chan interface{}) {
fmt.Println("Unable to load image info meta", err, path)
continue
}

if !math.IsNaN(info.Latitude) {
loc, err := source.rg.ReverseGeocode([]float64{info.Longitude, info.Latitude})
if err != nil {
info.Location = ""
} else if loc.City == "" && loc.Country == "" {
info.Location = ""
} else if loc.City != "" {
info.Location = fmt.Sprintf("%s, %s, %s", loc.City, loc.Province, loc.Country)
} else {
info.Location = fmt.Sprintf("%s, %s", loc.Province, loc.Country)
}
}

source.database.Write(path, info, UpdateMeta)
if source.Config.TagConfig.Exif.Enable {
source.database.WriteTags(id, tags)
Expand Down
26 changes: 21 additions & 5 deletions 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,23 +18,35 @@ type Info struct {
DateTime time.Time
Color uint32
Orientation Orientation
Latitude float64
Longitude float64
Location string
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, location: %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.Location,
info.LatLng.String(),
)
}

Expand Down
23 changes: 22 additions & 1 deletion internal/image/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"photofield/tag"

"github.com/docker/go-units"
"github.com/golang/geo/s2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"

Expand Down Expand Up @@ -167,7 +168,7 @@ func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS) *S
source.database = NewDatabase(filepath.Join(config.DataDir, "photofield.cache.db"), migrations)
source.imageInfoCache = newInfoCache()
source.pathCache = newPathCache()

r, err := rgeo.New(rgeo.Provinces10, rgeo.Cities10)

if err != nil {
Expand Down Expand Up @@ -283,6 +284,26 @@ func NewSource(config Config, migrations embed.FS, migrationsThumbs embed.FS) *S
return &source
}

func (source *Source) ReverseGeocode(l s2.LatLng) (string, error) {
location, err := source.rg.ReverseGeocode([]float64{l.Lng.Degrees(), l.Lat.Degrees()})
if err != nil {
return "", err
}
loc := ""
if err == nil {
loc = location.City
if loc == "" {
loc = location.Province
}
if loc == "" {
loc = location.Country
} else if location.Country != "" {
loc = fmt.Sprintf("%s (%s)", loc, location.Country)
}
}
return loc, nil
}

func (source *Source) Vacuum() error {
return source.database.vacuum()
}
Expand Down
Loading

0 comments on commit 1168370

Please sign in to comment.