Backend: Add support for new Places API #173
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
cf909e2a4e
commit
6da8bd098a
|
@ -475,10 +475,8 @@ american alligator:
|
||||||
- alligator
|
- alligator
|
||||||
|
|
||||||
triceratops:
|
triceratops:
|
||||||
|
label: animal
|
||||||
priority: -1
|
priority: -1
|
||||||
categories:
|
|
||||||
- reptile
|
|
||||||
- animal
|
|
||||||
|
|
||||||
snake:
|
snake:
|
||||||
label: snake
|
label: snake
|
||||||
|
|
|
@ -7,7 +7,7 @@ COPY . .
|
||||||
# Build PhotoPrism
|
# Build PhotoPrism
|
||||||
RUN make dep build-js install
|
RUN make dep build-js install
|
||||||
|
|
||||||
# Base base image as photoprism/development
|
# Same base image as photoprism/development
|
||||||
FROM ubuntu:18.04
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
# Set environment variables
|
# Set environment variables
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/photoprism/photoprism/internal/maps"
|
"github.com/photoprism/photoprism/internal/maps"
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,8 +15,6 @@ type Location struct {
|
||||||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
|
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
|
||||||
PlaceID string `gorm:"type:varbinary(16);"`
|
PlaceID string `gorm:"type:varbinary(16);"`
|
||||||
Place *Place
|
Place *Place
|
||||||
LocLat float64
|
|
||||||
LocLng float64
|
|
||||||
LocName string `gorm:"type:varchar(100);"`
|
LocName string `gorm:"type:varchar(100);"`
|
||||||
LocCategory string `gorm:"type:varchar(50);"`
|
LocCategory string `gorm:"type:varchar(50);"`
|
||||||
LocSuburb string `gorm:"type:varchar(100);"`
|
LocSuburb string `gorm:"type:varchar(100);"`
|
||||||
|
@ -27,9 +26,7 @@ type Location struct {
|
||||||
func NewLocation(lat, lng float64) *Location {
|
func NewLocation(lat, lng float64) *Location {
|
||||||
result := &Location{}
|
result := &Location{}
|
||||||
|
|
||||||
result.ID = maps.S2Token(lat, lng)
|
result.ID = s2.Token(lat, lng)
|
||||||
result.LocLat = lat
|
|
||||||
result.LocLng = lng
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -42,11 +39,9 @@ func (m *Location) Find(db *gorm.DB) error {
|
||||||
|
|
||||||
l := &maps.Location{
|
l := &maps.Location{
|
||||||
ID: m.ID,
|
ID: m.ID,
|
||||||
LocLat: m.LocLat,
|
|
||||||
LocLng: m.LocLng,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.Query(); err != nil {
|
if err := l.QueryPlaces(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,14 +88,6 @@ func (m *Location) Unknown() bool {
|
||||||
return m.ID == ""
|
return m.ID == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Location) Latitude() float64 {
|
|
||||||
return m.LocLat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Location) Longitude() float64 {
|
|
||||||
return m.LocLng
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Location) Name() string {
|
func (m *Location) Name() string {
|
||||||
return m.LocName
|
return m.LocName
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/maps/osm"
|
"github.com/photoprism/photoprism/internal/maps/osm"
|
||||||
|
"github.com/photoprism/photoprism/internal/maps/places"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* TODO
|
/* TODO
|
||||||
|
@ -24,8 +25,6 @@ ORDER BY loc_country, album_name, taken_year;
|
||||||
// Photo location
|
// Photo location
|
||||||
type Location struct {
|
type Location struct {
|
||||||
ID string
|
ID string
|
||||||
LocLat float64
|
|
||||||
LocLng float64
|
|
||||||
LocName string
|
LocName string
|
||||||
LocCategory string
|
LocCategory string
|
||||||
LocSuburb string
|
LocSuburb string
|
||||||
|
@ -37,9 +36,8 @@ type Location struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationSource interface {
|
type LocationSource interface {
|
||||||
|
CellID() string
|
||||||
CountryCode() string
|
CountryCode() string
|
||||||
Latitude() float64
|
|
||||||
Longitude() float64
|
|
||||||
Category() string
|
Category() string
|
||||||
Name() string
|
Name() string
|
||||||
City() string
|
City() string
|
||||||
|
@ -48,47 +46,53 @@ type LocationSource interface {
|
||||||
Source() string
|
Source() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocation(lat, lng float64) *Location {
|
func NewLocation(id string) *Location {
|
||||||
id := S2Token(lat, lng)
|
|
||||||
|
|
||||||
result := &Location{
|
result := &Location{
|
||||||
ID: id,
|
ID: id,
|
||||||
LocLat: lat,
|
|
||||||
LocLng: lng,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Location) Query() error {
|
func (l *Location) QueryPlaces() error {
|
||||||
o, err := osm.FindLocation(l.LocLat, l.LocLng)
|
s, err := places.FindLocation(l.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return l.Assign(o)
|
l.LocSource = s.Source()
|
||||||
|
l.LocName = s.Name()
|
||||||
|
l.LocCity = s.City()
|
||||||
|
l.LocSuburb = s.Suburb()
|
||||||
|
l.LocState = s.State()
|
||||||
|
l.LocCountry = s.CountryCode()
|
||||||
|
l.LocCategory = s.Category()
|
||||||
|
l.LocLabel = s.Label()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Location) QueryOSM() error {
|
||||||
|
s, err := osm.FindLocation(l.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.Assign(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Location) Assign(s LocationSource) error {
|
func (l *Location) Assign(s LocationSource) error {
|
||||||
l.LocSource = s.Source()
|
l.LocSource = s.Source()
|
||||||
|
|
||||||
if l.LocLat == 0 {
|
l.ID = s.CellID()
|
||||||
l.LocLat = s.Latitude()
|
|
||||||
}
|
|
||||||
if l.LocLng == 0 {
|
|
||||||
l.LocLng = s.Longitude()
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Unknown() {
|
if l.Unknown() {
|
||||||
l.LocCategory = "unknown"
|
l.LocCategory = "unknown"
|
||||||
return errors.New("maps: unknown location")
|
return errors.New("maps: unknown location")
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.ID == "" {
|
|
||||||
l.ID = S2Token(l.LocLat, l.LocLng)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.LocName = s.Name()
|
l.LocName = s.Name()
|
||||||
l.LocCity = s.City()
|
l.LocCity = s.City()
|
||||||
l.LocSuburb = s.Suburb()
|
l.LocSuburb = s.Suburb()
|
||||||
|
@ -101,11 +105,7 @@ func (l *Location) Assign(s LocationSource) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Location) Unknown() bool {
|
func (l *Location) Unknown() bool {
|
||||||
if l.LocLng == 0.0 && l.LocLat == 0.0 {
|
return l.ID == ""
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Location) label() string {
|
func (l *Location) label() string {
|
||||||
|
@ -133,14 +133,6 @@ func (l *Location) label() string {
|
||||||
return strings.Join(loc[:], ", ")
|
return strings.Join(loc[:], ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) Latitude() float64 {
|
|
||||||
return l.LocLat
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Location) Longitude() float64 {
|
|
||||||
return l.LocLng
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Location) Name() string {
|
func (l Location) Name() string {
|
||||||
return l.LocName
|
return l.LocName
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,40 @@
|
||||||
package maps
|
package maps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/maps/osm"
|
"github.com/photoprism/photoprism/internal/maps/osm"
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLocation_Query(t *testing.T) {
|
func TestLocation_QueryPlaces(t *testing.T) {
|
||||||
|
t.Run("U Berliner Rathaus", func(t *testing.T) {
|
||||||
|
lat := 52.51961810676184
|
||||||
|
lng := 13.40806264572578
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
|
l := NewLocation(id)
|
||||||
|
|
||||||
|
if err := l.QueryPlaces(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "U Berliner Rathaus", l.LocName)
|
||||||
|
assert.Equal(t, "Berlin, Germany", l.LocLabel)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocation_QueryOSM(t *testing.T) {
|
||||||
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
||||||
lat := 52.5208
|
lat := 52.5208
|
||||||
lng := 13.40953
|
lng := 13.40953
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l := NewLocation(lat, lng)
|
l := NewLocation(id)
|
||||||
|
|
||||||
if err := l.Query(); err != nil {
|
if err := l.QueryOSM(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +47,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
||||||
lat := 52.5208
|
lat := 52.5208
|
||||||
lng := 13.40953
|
lng := 13.40953
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -54,8 +75,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("SantaMonica", func(t *testing.T) {
|
t.Run("SantaMonica", func(t *testing.T) {
|
||||||
lat := 34.00909444444444
|
lat := 34.00909444444444
|
||||||
lng := -118.49700833333334
|
lng := -118.49700833333334
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -82,8 +104,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("AirportZurich", func(t *testing.T) {
|
t.Run("AirportZurich", func(t *testing.T) {
|
||||||
lat := 47.45401666666667
|
lat := 47.45401666666667
|
||||||
lng := 8.557494444444446
|
lng := 8.557494444444446
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -111,8 +134,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("AirportTegel", func(t *testing.T) {
|
t.Run("AirportTegel", func(t *testing.T) {
|
||||||
lat := 52.559864397033024
|
lat := 52.559864397033024
|
||||||
lng := 13.28895092010498
|
lng := 13.28895092010498
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -140,8 +164,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("PinkBeach", func(t *testing.T) {
|
t.Run("PinkBeach", func(t *testing.T) {
|
||||||
lat := 35.26967222222222
|
lat := 35.26967222222222
|
||||||
lng := 23.53711666666667
|
lng := 23.53711666666667
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -162,7 +187,7 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "149ce7854", l.ID)
|
assert.True(t, strings.HasPrefix(l.ID, "149ce785"))
|
||||||
assert.Equal(t, "Pink Beach", l.LocName)
|
assert.Equal(t, "Pink Beach", l.LocName)
|
||||||
assert.Equal(t, "Chrisoskalitissa, Crete, Greece", l.LocLabel)
|
assert.Equal(t, "Chrisoskalitissa, Crete, Greece", l.LocLabel)
|
||||||
})
|
})
|
||||||
|
@ -170,8 +195,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("NewJersey", func(t *testing.T) {
|
t.Run("NewJersey", func(t *testing.T) {
|
||||||
lat := 40.74290
|
lat := 40.74290
|
||||||
lng := -74.04862
|
lng := -74.04862
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -192,7 +218,7 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "89c25741c", l.ID)
|
assert.True(t, strings.HasPrefix(l.ID, "89c25741"))
|
||||||
assert.Equal(t, "", l.LocName)
|
assert.Equal(t, "", l.LocName)
|
||||||
assert.Equal(t, "Jersey City, New Jersey, USA", l.LocLabel)
|
assert.Equal(t, "Jersey City, New Jersey, USA", l.LocLabel)
|
||||||
})
|
})
|
||||||
|
@ -200,8 +226,9 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("SouthAfrica", func(t *testing.T) {
|
t.Run("SouthAfrica", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
lat := -31.976301666666668
|
||||||
lng := 29.148046666666666
|
lng := 29.148046666666666
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -222,7 +249,7 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "1e5e4205c", l.ID)
|
assert.True(t, strings.HasPrefix(l.ID, "1e5e4205"))
|
||||||
assert.Equal(t, "R411", l.LocName)
|
assert.Equal(t, "R411", l.LocName)
|
||||||
assert.Equal(t, "Eastern Cape, South Africa", l.LocLabel)
|
assert.Equal(t, "Eastern Cape, South Africa", l.LocLabel)
|
||||||
})
|
})
|
||||||
|
@ -230,11 +257,14 @@ func TestLocation_Assign(t *testing.T) {
|
||||||
t.Run("Unknown", func(t *testing.T) {
|
t.Run("Unknown", func(t *testing.T) {
|
||||||
lat := -21.976301666666668
|
lat := -21.976301666666668
|
||||||
lng := 49.148046666666666
|
lng := 49.148046666666666
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
log.Printf("ID: %s", id)
|
||||||
|
o, err := osm.FindLocation(id)
|
||||||
|
|
||||||
o, err := osm.FindLocation(lat, lng)
|
log.Printf("Output: %+v", o)
|
||||||
|
|
||||||
if err != nil {
|
if err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.False(t, o.Cached)
|
assert.False(t, o.Cached)
|
||||||
|
@ -250,16 +280,18 @@ func TestLocation_Unknown(t *testing.T) {
|
||||||
t.Run("true", func(t *testing.T) {
|
t.Run("true", func(t *testing.T) {
|
||||||
lat := 0.0
|
lat := 0.0
|
||||||
lng := 0.0
|
lng := 0.0
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l := NewLocation(lat, lng)
|
l := NewLocation(id)
|
||||||
|
|
||||||
assert.Equal(t, true, l.Unknown())
|
assert.Equal(t, true, l.Unknown())
|
||||||
})
|
})
|
||||||
t.Run("false", func(t *testing.T) {
|
t.Run("false", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
lat := -31.976301666666668
|
||||||
lng := 29.148046666666666
|
lng := 29.148046666666666
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l := NewLocation(lat, lng)
|
l := NewLocation(id)
|
||||||
|
|
||||||
assert.Equal(t, false, l.Unknown())
|
assert.Equal(t, false, l.Unknown())
|
||||||
})
|
})
|
||||||
|
@ -269,49 +301,22 @@ func TestLocation_place(t *testing.T) {
|
||||||
t.Run("unknown", func(t *testing.T) {
|
t.Run("unknown", func(t *testing.T) {
|
||||||
lat := 0.0
|
lat := 0.0
|
||||||
lng := 0.0
|
lng := 0.0
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l := NewLocation(lat, lng)
|
l := NewLocation(id)
|
||||||
|
|
||||||
assert.Equal(t, "Unknown", l.label())
|
assert.Equal(t, "Unknown", l.label())
|
||||||
})
|
})
|
||||||
t.Run("Nürnberg, Bayern, Germany", func(t *testing.T) {
|
t.Run("Nürnberg, Bayern, Germany", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
assert.Equal(t, "Unknown", l.label())
|
||||||
|
|
||||||
assert.Equal(t, "Nürnberg, Bayern, Germany", l.label())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocation_Latitude(t *testing.T) {
|
|
||||||
t.Run("-31.976301666666668", func(t *testing.T) {
|
|
||||||
lat := -31.976301666666668
|
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
|
||||||
|
|
||||||
assert.Equal(t, -31.976301666666668, l.Latitude())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocation_Longitude(t *testing.T) {
|
|
||||||
t.Run("29.148046666666666", func(t *testing.T) {
|
|
||||||
lat := -31.976301666666668
|
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
|
||||||
|
|
||||||
assert.Equal(t, 29.148046666666666, l.Longitude())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocation_Name(t *testing.T) {
|
func TestLocation_Name(t *testing.T) {
|
||||||
t.Run("Christkindlesmarkt", func(t *testing.T) {
|
t.Run("Christkindlesmarkt", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "Christkindlesmarkt", l.Name())
|
assert.Equal(t, "Christkindlesmarkt", l.Name())
|
||||||
})
|
})
|
||||||
|
@ -319,10 +324,7 @@ func TestLocation_Name(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_City(t *testing.T) {
|
func TestLocation_City(t *testing.T) {
|
||||||
t.Run("Nürnberg", func(t *testing.T) {
|
t.Run("Nürnberg", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "Nürnberg", l.City())
|
assert.Equal(t, "Nürnberg", l.City())
|
||||||
})
|
})
|
||||||
|
@ -330,10 +332,7 @@ func TestLocation_City(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_Suburb(t *testing.T) {
|
func TestLocation_Suburb(t *testing.T) {
|
||||||
t.Run("Hauptmarkt", func(t *testing.T) {
|
t.Run("Hauptmarkt", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "Hauptmarkt", l.Suburb())
|
assert.Equal(t, "Hauptmarkt", l.Suburb())
|
||||||
})
|
})
|
||||||
|
@ -341,10 +340,7 @@ func TestLocation_Suburb(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_State(t *testing.T) {
|
func TestLocation_State(t *testing.T) {
|
||||||
t.Run("Bayern", func(t *testing.T) {
|
t.Run("Bayern", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "Bayern", l.State())
|
assert.Equal(t, "Bayern", l.State())
|
||||||
})
|
})
|
||||||
|
@ -352,10 +348,7 @@ func TestLocation_State(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_Category(t *testing.T) {
|
func TestLocation_Category(t *testing.T) {
|
||||||
t.Run("test", func(t *testing.T) {
|
t.Run("test", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "test", l.Category())
|
assert.Equal(t, "test", l.Category())
|
||||||
})
|
})
|
||||||
|
@ -363,10 +356,7 @@ func TestLocation_Category(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_Source(t *testing.T) {
|
func TestLocation_Source(t *testing.T) {
|
||||||
t.Run("source", func(t *testing.T) {
|
t.Run("source", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt", LocSource: "source"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt", LocSource: "source"}
|
|
||||||
|
|
||||||
assert.Equal(t, "source", l.Source())
|
assert.Equal(t, "source", l.Source())
|
||||||
})
|
})
|
||||||
|
@ -374,10 +364,7 @@ func TestLocation_Source(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_Place(t *testing.T) {
|
func TestLocation_Place(t *testing.T) {
|
||||||
t.Run("test-label", func(t *testing.T) {
|
t.Run("test-label", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "test-label", l.Label())
|
assert.Equal(t, "test-label", l.Label())
|
||||||
})
|
})
|
||||||
|
@ -385,10 +372,7 @@ func TestLocation_Place(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_CountryCode(t *testing.T) {
|
func TestLocation_CountryCode(t *testing.T) {
|
||||||
t.Run("de", func(t *testing.T) {
|
t.Run("de", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "de", l.CountryCode())
|
assert.Equal(t, "de", l.CountryCode())
|
||||||
})
|
})
|
||||||
|
@ -396,10 +380,7 @@ func TestLocation_CountryCode(t *testing.T) {
|
||||||
|
|
||||||
func TestLocation_CountryName(t *testing.T) {
|
func TestLocation_CountryName(t *testing.T) {
|
||||||
t.Run("Germany", func(t *testing.T) {
|
t.Run("Germany", func(t *testing.T) {
|
||||||
lat := -31.976301666666668
|
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||||
lng := 29.148046666666666
|
|
||||||
|
|
||||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
|
||||||
|
|
||||||
assert.Equal(t, "Germany", l.CountryName())
|
assert.Equal(t, "Germany", l.CountryName())
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,25 +9,25 @@ import (
|
||||||
func TestOSM_Category(t *testing.T) {
|
func TestOSM_Category(t *testing.T) {
|
||||||
t.Run("hill", func(t *testing.T) {
|
t.Run("hill", func(t *testing.T) {
|
||||||
|
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "hill", l.Category())
|
assert.Equal(t, "hill", l.Category())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("water", func(t *testing.T) {
|
t.Run("water", func(t *testing.T) {
|
||||||
|
|
||||||
l := &Location{LocCategory: "", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "water", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "", LocName: "Nice title", LocType: "water", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "water", l.Category())
|
assert.Equal(t, "water", l.Category())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("shop", func(t *testing.T) {
|
t.Run("shop", func(t *testing.T) {
|
||||||
|
|
||||||
l := &Location{LocCategory: "shop", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "shop", LocName: "Nice title", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "shop", l.Category())
|
assert.Equal(t, "shop", l.Category())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no label found", func(t *testing.T) {
|
t.Run("no label found", func(t *testing.T) {
|
||||||
|
|
||||||
l := &Location{LocCategory: "xxx", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "xxx", LocName: "Nice title", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "", l.Category())
|
assert.Equal(t, "", l.Category())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package osm
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func (o Location) Category() (result string) {
|
func (l Location) Category() (result string) {
|
||||||
key := fmt.Sprintf("%s=%s", o.LocCategory, o.LocType)
|
key := fmt.Sprintf("%s=%s", l.LocCategory, l.LocType)
|
||||||
catKey := fmt.Sprintf("%s=*", o.LocCategory)
|
catKey := fmt.Sprintf("%s=*", l.LocCategory)
|
||||||
typeKey := fmt.Sprintf("*=%s", o.LocType)
|
typeKey := fmt.Sprintf("*=%s", l.LocType)
|
||||||
|
|
||||||
if result, ok := osmCategories[key]; ok {
|
if result, ok := osmCategories[key]; ok {
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -2,20 +2,20 @@ package osm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/melihmucuk/geocache"
|
"github.com/melihmucuk/geocache"
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
"github.com/photoprism/photoprism/internal/util"
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
|
ID string `json:"-"`
|
||||||
PlaceID int `json:"place_id"`
|
PlaceID int `json:"place_id"`
|
||||||
LocLat string `json:"lat"`
|
|
||||||
LocLng string `json:"lon"`
|
|
||||||
LocName string `json:"name"`
|
LocName string `json:"name"`
|
||||||
LocCategory string `json:"category"`
|
LocCategory string `json:"category"`
|
||||||
LocType string `json:"type"`
|
LocType string `json:"type"`
|
||||||
|
@ -27,7 +27,13 @@ type Location struct {
|
||||||
var ReverseLookupURL = "https://nominatim.openstreetmap.org/reverse?lat=%f&lon=%f&format=jsonv2&accept-language=en&zoom=18"
|
var ReverseLookupURL = "https://nominatim.openstreetmap.org/reverse?lat=%f&lon=%f&format=jsonv2&accept-language=en&zoom=18"
|
||||||
|
|
||||||
// API docs see https://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding
|
// API docs see https://wiki.openstreetmap.org/wiki/Nominatim#Reverse_Geocoding
|
||||||
func FindLocation(lat, lng float64) (result Location, err error) {
|
func FindLocation(id string) (result Location, err error) {
|
||||||
|
if len(id) > 16 || len(id) == 0 {
|
||||||
|
return result, errors.New("osm: invalid location id")
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lng := s2.LatLng(id)
|
||||||
|
|
||||||
if lat == 0.0 || lng == 0.0 {
|
if lat == 0.0 || lng == 0.0 {
|
||||||
return result, fmt.Errorf("osm: skipping lat %f, lng %f", lat, lng)
|
return result, fmt.Errorf("osm: skipping lat %f, lng %f", lat, lng)
|
||||||
}
|
}
|
||||||
|
@ -59,6 +65,14 @@ func FindLocation(lat, lng float64) (result Location, err error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if result.PlaceID == 0 {
|
||||||
|
result.ID = ""
|
||||||
|
|
||||||
|
return result, fmt.Errorf("osm: no result for %s", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ID = id
|
||||||
|
|
||||||
geoCache.Set(point, result, time.Hour)
|
geoCache.Set(point, result, time.Hour)
|
||||||
|
|
||||||
result.Cached = false
|
result.Cached = false
|
||||||
|
@ -66,23 +80,27 @@ func FindLocation(lat, lng float64) (result Location, err error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) State() (result string) {
|
func (l Location) CellID() (result string) {
|
||||||
result = o.Address.State
|
return l.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) State() (result string) {
|
||||||
|
result = l.Address.State
|
||||||
|
|
||||||
return strings.TrimSpace(result)
|
return strings.TrimSpace(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) City() (result string) {
|
func (l Location) City() (result string) {
|
||||||
if o.Address.City != "" {
|
if l.Address.City != "" {
|
||||||
result = o.Address.City
|
result = l.Address.City
|
||||||
} else if o.Address.Town != "" {
|
} else if l.Address.Town != "" {
|
||||||
result = o.Address.Town
|
result = l.Address.Town
|
||||||
} else if o.Address.Village != "" {
|
} else if l.Address.Village != "" {
|
||||||
result = o.Address.Village
|
result = l.Address.Village
|
||||||
} else if o.Address.County != "" {
|
} else if l.Address.County != "" {
|
||||||
result = o.Address.County
|
result = l.Address.County
|
||||||
} else if o.Address.State != "" {
|
} else if l.Address.State != "" {
|
||||||
result = o.Address.State
|
result = l.Address.State
|
||||||
}
|
}
|
||||||
|
|
||||||
if len([]rune(result)) > 19 {
|
if len([]rune(result)) > 19 {
|
||||||
|
@ -92,52 +110,22 @@ func (o Location) City() (result string) {
|
||||||
return strings.TrimSpace(result)
|
return strings.TrimSpace(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) Suburb() (result string) {
|
func (l Location) Suburb() (result string) {
|
||||||
result = o.Address.Suburb
|
result = l.Address.Suburb
|
||||||
|
|
||||||
return strings.TrimSpace(result)
|
return strings.TrimSpace(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) CountryCode() (result string) {
|
func (l Location) CountryCode() (result string) {
|
||||||
result = o.Address.CountryCode
|
result = l.Address.CountryCode
|
||||||
|
|
||||||
return strings.ToLower(strings.TrimSpace(result))
|
return strings.ToLower(strings.TrimSpace(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) Latitude() (result float64) {
|
func (l Location) Keywords() (result []string) {
|
||||||
if o.LocLat == "" {
|
return util.Keywords(l.LocDisplayName)
|
||||||
log.Warn("osm: no latitude")
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := strconv.ParseFloat(o.LocLat, 64)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("osm: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) Longitude() (result float64) {
|
func (l Location) Source() string {
|
||||||
if o.LocLng == "" {
|
|
||||||
log.Warn("osm: no longitude")
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := strconv.ParseFloat(o.LocLng, 64)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("osm: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Location) Keywords() (result []string) {
|
|
||||||
return util.Keywords(o.LocDisplayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Location) Source() string {
|
|
||||||
return "osm"
|
return "osm"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,17 @@ package osm
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindLocation(t *testing.T) {
|
func TestFindLocation(t *testing.T) {
|
||||||
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
t.Run("Fernsehturm Berlin 1", func(t *testing.T) {
|
||||||
lat := 52.5208
|
lat := 52.5208
|
||||||
lng := 13.40953
|
lng := 13.40953
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l, err := FindLocation(lat, lng)
|
l, err := FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -29,7 +31,7 @@ func TestFindLocation(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, 123456, l.PlaceID)
|
assert.Equal(t, 123456, l.PlaceID)
|
||||||
|
|
||||||
cached, err := FindLocation(lat, lng)
|
cached, err := FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -44,19 +46,20 @@ func TestFindLocation(t *testing.T) {
|
||||||
assert.Equal(t, l.Address.Country, cached.Address.Country)
|
assert.Equal(t, l.Address.Country, cached.Address.Country)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("BerlinMuseum", func(t *testing.T) {
|
t.Run("Fernsehturm Berlin 2", func(t *testing.T) {
|
||||||
lat := 52.52057
|
lat := 52.52057
|
||||||
lng := 13.40889
|
lng := 13.40889
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l, err := FindLocation(lat, lng)
|
l, err := FindLocation(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.False(t, l.Cached)
|
assert.False(t, l.Cached)
|
||||||
assert.Equal(t, 48287001, l.PlaceID)
|
assert.Equal(t, 189675302, l.PlaceID)
|
||||||
assert.Equal(t, "Menschen Museum", l.LocName)
|
assert.Equal(t, "Fernsehturm Berlin", l.LocName)
|
||||||
assert.Equal(t, "10178", l.Address.Postcode)
|
assert.Equal(t, "10178", l.Address.Postcode)
|
||||||
assert.Equal(t, "Berlin", l.Address.State)
|
assert.Equal(t, "Berlin", l.Address.State)
|
||||||
assert.Equal(t, "de", l.Address.CountryCode)
|
assert.Equal(t, "de", l.Address.CountryCode)
|
||||||
|
@ -66,14 +69,15 @@ func TestFindLocation(t *testing.T) {
|
||||||
t.Run("No Location", func(t *testing.T) {
|
t.Run("No Location", func(t *testing.T) {
|
||||||
lat := 0.0
|
lat := 0.0
|
||||||
lng := 0.0
|
lng := 0.0
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
l, err := FindLocation(lat, lng)
|
l, err := FindLocation(id)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("err should not be nil")
|
t.Fatal("err should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "osm: skipping lat 0.000000, lng 0.000000", err.Error())
|
assert.Equal(t, "osm: invalid location id", err.Error())
|
||||||
assert.False(t, l.Cached)
|
assert.False(t, l.Cached)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -82,7 +86,7 @@ func TestOSM_State(t *testing.T) {
|
||||||
t.Run("Berlin", func(t *testing.T) {
|
t.Run("Berlin", func(t *testing.T) {
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||||
assert.Equal(t, "Berlin", l.State())
|
assert.Equal(t, "Berlin", l.State())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -124,7 +128,7 @@ func TestOSM_Suburb(t *testing.T) {
|
||||||
t.Run("Neukölln", func(t *testing.T) {
|
t.Run("Neukölln", func(t *testing.T) {
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||||
assert.Equal(t, "Neukölln", l.Suburb())
|
assert.Equal(t, "Neukölln", l.Suburb())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -133,34 +137,16 @@ func TestOSM_CountryCode(t *testing.T) {
|
||||||
t.Run("de", func(t *testing.T) {
|
t.Run("de", func(t *testing.T) {
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||||
assert.Equal(t, "de", l.CountryCode())
|
assert.Equal(t, "de", l.CountryCode())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOSM_Latitude(t *testing.T) {
|
|
||||||
t.Run("52.5208", func(t *testing.T) {
|
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
|
||||||
assert.Equal(t, 52.5208, l.Latitude())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOSM_Longitude(t *testing.T) {
|
|
||||||
t.Run("13.40953", func(t *testing.T) {
|
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
|
||||||
assert.Equal(t, 13.40953, l.Longitude())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOSM_Keywords(t *testing.T) {
|
func TestOSM_Keywords(t *testing.T) {
|
||||||
t.Run("cat", func(t *testing.T) {
|
t.Run("cat", func(t *testing.T) {
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
||||||
assert.Equal(t, []string{"cat"}, l.Keywords())
|
assert.Equal(t, []string{"cat"}, l.Keywords())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -168,6 +154,6 @@ func TestOSM_Keywords(t *testing.T) {
|
||||||
func TestOSM_Source(t *testing.T) {
|
func TestOSM_Source(t *testing.T) {
|
||||||
|
|
||||||
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
a := Address{CountryCode: "de", City: "Berlin", State: "Berlin", HouseNumber: "63", Suburb: "Neukölln"}
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
||||||
assert.Equal(t, "osm", l.Source())
|
assert.Equal(t, "osm", l.Source())
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,16 @@ var labelTitles = map[string]string{
|
||||||
"visitor center": "Visitor Center",
|
"visitor center": "Visitor Center",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Location) Name() (result string) {
|
func (l Location) Name() (result string) {
|
||||||
result = o.Category()
|
result = l.Category()
|
||||||
|
|
||||||
if title, ok := labelTitles[result]; ok {
|
if title, ok := labelTitles[result]; ok {
|
||||||
title = strings.Replace(title, "%name%", o.LocName, 1)
|
title = strings.Replace(title, "%name%", l.LocName, 1)
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.LocName != "" {
|
if l.LocName != "" {
|
||||||
result = o.LocName
|
result = l.LocName
|
||||||
}
|
}
|
||||||
|
|
||||||
result = strings.Replace(result, "_", " ", -1)
|
result = strings.Replace(result, "_", " ", -1)
|
||||||
|
|
|
@ -8,32 +8,32 @@ import (
|
||||||
|
|
||||||
func TestOSM_Name(t *testing.T) {
|
func TestOSM_Name(t *testing.T) {
|
||||||
t.Run("Nice Name", func(t *testing.T) {
|
t.Run("Nice Name", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice Name", LocType: "hill", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "natural", LocName: "Nice Name", LocType: "hill", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Nice Name", l.Name())
|
assert.Equal(t, "Nice Name", l.Name())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Water", func(t *testing.T) {
|
t.Run("Water", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "", LocLat: "52.5208", LocLng: "13.40953", LocName: "", LocType: "water", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "", LocName: "", LocType: "water", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Water", l.Name())
|
assert.Equal(t, "Water", l.Name())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Nice Name 2", func(t *testing.T) {
|
t.Run("Nice Name 2", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "shop", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice Name 2", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "shop", LocName: "Nice Name 2", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Nice Name 2", l.Name())
|
assert.Equal(t, "Nice Name 2", l.Name())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Cat", func(t *testing.T) {
|
t.Run("Cat", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "xxx", LocLat: "52.5208", LocLng: "13.40953", LocName: "Cat,Dog", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "xxx", LocName: "Cat,Dog", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Cat", l.Name())
|
assert.Equal(t, "Cat", l.Name())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("airport", func(t *testing.T) {
|
t.Run("airport", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "aeroway", LocLat: "52.5208", LocLng: "13.40953", LocName: "", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "aeroway", LocName: "", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Airport", l.Name())
|
assert.Equal(t, "Airport", l.Name())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Cow", func(t *testing.T) {
|
t.Run("Cow", func(t *testing.T) {
|
||||||
l := &Location{LocCategory: "xxx", LocLat: "52.5208", LocLng: "13.40953", LocName: "Cow - Cat - Dog", LocType: "", LocDisplayName: "dipslay name"}
|
l := &Location{LocCategory: "xxx", LocName: "Cow - Cat - Dog", LocType: "", LocDisplayName: "dipslay name"}
|
||||||
assert.Equal(t, "Cow", l.Name())
|
assert.Equal(t, "Cow", l.Name())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
9
internal/maps/places/cache.go
Normal file
9
internal/maps/places/cache.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package places
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gc "github.com/patrickmn/go-cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cache = gc.New(15*time.Minute, 5*time.Minute)
|
121
internal/maps/places/location.go
Normal file
121
internal/maps/places/location.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package places
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
gc "github.com/patrickmn/go-cache"
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
|
"github.com/photoprism/photoprism/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Location
|
||||||
|
type Location struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
LocLat float64 `json:"lat"`
|
||||||
|
LocLng float64 `json:"lng"`
|
||||||
|
LocName string `json:"name"`
|
||||||
|
LocCategory string `json:"category"`
|
||||||
|
LocSuburb string `json:"suburb"`
|
||||||
|
Place Place `json:"place"`
|
||||||
|
Cached bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var ReverseLookupURL = "https://places.photoprism.org/v1/location/%s"
|
||||||
|
|
||||||
|
func FindLocation(id string) (result Location, err error) {
|
||||||
|
if len(id) > 16 || len(id) == 0 {
|
||||||
|
return result, fmt.Errorf("places: invalid location id %s", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
lat, lng := s2.LatLng(id)
|
||||||
|
|
||||||
|
if lat == 0.0 || lng == 0.0 {
|
||||||
|
return result, fmt.Errorf("places: skipping lat %f, lng %f", lat, lng)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hit, ok := cache.Get(id); ok {
|
||||||
|
log.Debugf("places: cache hit for lat %f, lng %f", lat, lng)
|
||||||
|
result = hit.(Location)
|
||||||
|
result.Cached = true
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf(ReverseLookupURL, id)
|
||||||
|
|
||||||
|
log.Debugf("places: query %s", url)
|
||||||
|
|
||||||
|
r, err := http.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("places: %s", err.Error())
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&result)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("places: %s", err.Error())
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.ID == "" {
|
||||||
|
log.Debugf("result: %+v", result)
|
||||||
|
return result, fmt.Errorf("places: no result for %s", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Set(id, result, gc.DefaultExpiration)
|
||||||
|
|
||||||
|
result.Cached = false
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) CellID() (result string) {
|
||||||
|
return l.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Name() (result string) {
|
||||||
|
return l.LocName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Category() (result string) {
|
||||||
|
return l.LocCategory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Label() (result string) {
|
||||||
|
return l.Place.LocLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) State() (result string) {
|
||||||
|
return l.Place.LocState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) City() (result string) {
|
||||||
|
return l.Place.LocCity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Suburb() (result string) {
|
||||||
|
return l.LocSuburb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) CountryCode() (result string) {
|
||||||
|
return l.Place.LocCountry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Latitude() (result float64) {
|
||||||
|
return l.LocLat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Longitude() (result float64) {
|
||||||
|
return l.LocLng
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Keywords() (result []string) {
|
||||||
|
return util.Keywords(l.Label())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Location) Source() string {
|
||||||
|
return "places"
|
||||||
|
}
|
27
internal/maps/places/location_test.go
Normal file
27
internal/maps/places/location_test.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package places
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/s2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindLocation(t *testing.T) {
|
||||||
|
t.Run("U Berliner Rathaus", func(t *testing.T) {
|
||||||
|
lat := 52.51961810676184
|
||||||
|
lng := 13.40806264572578
|
||||||
|
id := s2.Token(lat, lng)
|
||||||
|
|
||||||
|
l, err := FindLocation(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, l.Cached)
|
||||||
|
assert.Equal(t, "U Berliner Rathaus", l.Name())
|
||||||
|
assert.Equal(t, "Berlin", l.City())
|
||||||
|
assert.Equal(t, "de", l.CountryCode())
|
||||||
|
})
|
||||||
|
}
|
10
internal/maps/places/place.go
Normal file
10
internal/maps/places/place.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package places
|
||||||
|
|
||||||
|
// Place
|
||||||
|
type Place struct {
|
||||||
|
PlaceID string `json:"id"`
|
||||||
|
LocLabel string `json:"label"`
|
||||||
|
LocCity string `json:"city"`
|
||||||
|
LocState string `json:"state"`
|
||||||
|
LocCountry string `json:"country"`
|
||||||
|
}
|
14
internal/maps/places/places.go
Normal file
14
internal/maps/places/places.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
This package encapsulates the PhotoPrism Places API.
|
||||||
|
|
||||||
|
Additional information can be found in our Developer Guide:
|
||||||
|
|
||||||
|
https://github.com/photoprism/photoprism/wiki
|
||||||
|
*/
|
||||||
|
package places
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = event.Log
|
|
@ -1,26 +0,0 @@
|
||||||
package maps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/geo/s2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var S2Level = 15
|
|
||||||
|
|
||||||
func S2Token(lat, lng float64) string {
|
|
||||||
return S2TokenLevel(lat, lng, S2Level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func S2TokenLevel(lat, lng float64, level int) string {
|
|
||||||
if lat < -90 || lat > 90 {
|
|
||||||
log.Warnf("olc: latitude out of range (%f)", lat)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if lng < -180 || lng > 180 {
|
|
||||||
log.Warnf("olc: longitude out of range (%f)", lng)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
l := s2.LatLngFromDegrees(lat, lng)
|
|
||||||
return s2.CellIDFromLatLng(l).Parent(level).ToToken()
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package maps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestS2Token(t *testing.T) {
|
|
||||||
t.Run("Wildgehege", func(t *testing.T) {
|
|
||||||
plusCode := S2Token(48.56344833333333, 8.996878333333333)
|
|
||||||
expected := "4799e370c"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LatOverflow", func(t *testing.T) {
|
|
||||||
plusCode := S2Token(548.56344833333333, 8.996878333333333)
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LongOverflow", func(t *testing.T) {
|
|
||||||
plusCode := S2Token(48.56344833333333, 258.996878333333333)
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestS2TokenLevel(t *testing.T) {
|
|
||||||
t.Run("Wildgehege30", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344833333333, 8.996878333333333, 30)
|
|
||||||
expected := "4799e370ca54c8b9"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NearWildgehege30", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344839999999, 8.996878339999999, 30)
|
|
||||||
expected := "4799e370ca54c8b7"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Wildgehege18", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344833333333, 8.996878333333333, 18)
|
|
||||||
expected := "4799e370cb"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NearWildgehege18", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344839999999, 8.996878339999999, 18)
|
|
||||||
expected := "4799e370cb"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("NearWildgehege15", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344833333333, 8.996878333333333, 15)
|
|
||||||
expected := "4799e370c"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Wildgehege10", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344833333333, 8.996878333333333, 10)
|
|
||||||
expected := "4799e3"
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LatOverflow", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(548.56344833333333, 8.996878333333333, 30)
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("LongOverflow", func(t *testing.T) {
|
|
||||||
plusCode := S2TokenLevel(48.56344833333333, 258.996878333333333, 30)
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
assert.Equal(t, expected, plusCode)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package photoprism
|
package photoprism
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
@ -31,8 +32,7 @@ func TestMediaFile_Location(t *testing.T) {
|
||||||
assert.Equal(t, "Kinki Region", location.State())
|
assert.Equal(t, "Kinki Region", location.State())
|
||||||
assert.Equal(t, "Japan", location.CountryName())
|
assert.Equal(t, "Japan", location.CountryName())
|
||||||
assert.Equal(t, "", location.Category())
|
assert.Equal(t, "", location.Category())
|
||||||
assert.Equal(t, 34.79745, location.Latitude())
|
assert.True(t, strings.HasPrefix(location.ID, "3554df45"))
|
||||||
assert.Equal(t, "3554df45c", location.ID)
|
|
||||||
location2, err := mediaFile.Location()
|
location2, err := mediaFile.Location()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,8 +67,7 @@ func TestMediaFile_Location(t *testing.T) {
|
||||||
assert.Equal(t, "Tübingen", location.City())
|
assert.Equal(t, "Tübingen", location.City())
|
||||||
assert.Equal(t, "de", location.CountryCode())
|
assert.Equal(t, "de", location.CountryCode())
|
||||||
assert.Equal(t, "Germany", location.CountryName())
|
assert.Equal(t, "Germany", location.CountryName())
|
||||||
assert.Equal(t, 48.53870833333333, location.Latitude())
|
assert.True(t, strings.HasPrefix(location.ID, "4799e4a5"))
|
||||||
assert.Equal(t, "4799e4a5c", location.ID)
|
|
||||||
})
|
})
|
||||||
t.Run("dog_orange.jpg", func(t *testing.T) {
|
t.Run("dog_orange.jpg", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
conf := config.TestConfig()
|
||||||
|
|
44
internal/s2/s2.go
Normal file
44
internal/s2/s2.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
gs2 "github.com/golang/geo/s2"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = event.Log
|
||||||
|
var Level = 18
|
||||||
|
|
||||||
|
func Token(lat, lng float64) string {
|
||||||
|
return TokenLevel(lat, lng, Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TokenLevel(lat, lng float64, level int) string {
|
||||||
|
if lat == 0.0 && lng == 0.0 {
|
||||||
|
log.Debugf("s2: no values for latitude and longitude")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if lat < -90 || lat > 90 {
|
||||||
|
log.Warnf("s2: latitude out of range (%f)", lat)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if lng < -180 || lng > 180 {
|
||||||
|
log.Warnf("s2: longitude out of range (%f)", lng)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
l := gs2.LatLngFromDegrees(lat, lng)
|
||||||
|
return gs2.CellIDFromLatLng(l).Parent(level).ToToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func LatLng(token string) (lat, lng float64) {
|
||||||
|
if token == "" || token == "-" {
|
||||||
|
log.Warn("s2: empty token")
|
||||||
|
return 0.0, 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
c := gs2.CellIDFromToken(token)
|
||||||
|
l := c.LatLng()
|
||||||
|
return l.Lat.Degrees(), l.Lng.Degrees()
|
||||||
|
}
|
89
internal/s2/s2_test.go
Normal file
89
internal/s2/s2_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package s2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToken(t *testing.T) {
|
||||||
|
t.Run("Wildgehege", func(t *testing.T) {
|
||||||
|
token := Token(48.56344833333333, 8.996878333333333)
|
||||||
|
expected := "4799e370"
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(token, expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LatOverflow", func(t *testing.T) {
|
||||||
|
token := Token(548.56344833333333, 8.996878333333333)
|
||||||
|
expected := ""
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LongOverflow", func(t *testing.T) {
|
||||||
|
token := Token(48.56344833333333, 258.996878333333333)
|
||||||
|
expected := ""
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenLevel(t *testing.T) {
|
||||||
|
t.Run("Wildgehege30", func(t *testing.T) {
|
||||||
|
token := TokenLevel(48.56344833333333, 8.996878333333333, 30)
|
||||||
|
expected := "4799e370ca54c8b9"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NearWildgehege30", func(t *testing.T) {
|
||||||
|
plusCode := TokenLevel(48.56344839999999, 8.996878339999999, 30)
|
||||||
|
expected := "4799e370ca54c8b7"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, plusCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Wildgehege18", func(t *testing.T) {
|
||||||
|
token := TokenLevel(48.56344833333333, 8.996878333333333, 18)
|
||||||
|
expected := "4799e370cb"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NearWildgehege18", func(t *testing.T) {
|
||||||
|
token := TokenLevel(48.56344839999999, 8.996878339999999, 18)
|
||||||
|
expected := "4799e370cb"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NearWildgehege15", func(t *testing.T) {
|
||||||
|
plusCode := TokenLevel(48.56344833333333, 8.996878333333333, 15)
|
||||||
|
expected := "4799e370c"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, plusCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Wildgehege10", func(t *testing.T) {
|
||||||
|
token := TokenLevel(48.56344833333333, 8.996878333333333, 10)
|
||||||
|
expected := "4799e3"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LatOverflow", func(t *testing.T) {
|
||||||
|
token := TokenLevel(548.56344833333333, 8.996878333333333, 30)
|
||||||
|
expected := ""
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("LongOverflow", func(t *testing.T) {
|
||||||
|
token := TokenLevel(48.56344833333333, 258.996878333333333, 30)
|
||||||
|
expected := ""
|
||||||
|
|
||||||
|
assert.Equal(t, expected, token)
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue