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
|
||||
|
||||
triceratops:
|
||||
label: animal
|
||||
priority: -1
|
||||
categories:
|
||||
- reptile
|
||||
- animal
|
||||
|
||||
snake:
|
||||
label: snake
|
||||
|
|
|
@ -7,7 +7,7 @@ COPY . .
|
|||
# Build PhotoPrism
|
||||
RUN make dep build-js install
|
||||
|
||||
# Base base image as photoprism/development
|
||||
# Same base image as photoprism/development
|
||||
FROM ubuntu:18.04
|
||||
|
||||
# Set environment variables
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/maps"
|
||||
"github.com/photoprism/photoprism/internal/s2"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
|
||||
|
@ -14,8 +15,6 @@ type Location struct {
|
|||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
|
||||
PlaceID string `gorm:"type:varbinary(16);"`
|
||||
Place *Place
|
||||
LocLat float64
|
||||
LocLng float64
|
||||
LocName string `gorm:"type:varchar(100);"`
|
||||
LocCategory string `gorm:"type:varchar(50);"`
|
||||
LocSuburb string `gorm:"type:varchar(100);"`
|
||||
|
@ -27,9 +26,7 @@ type Location struct {
|
|||
func NewLocation(lat, lng float64) *Location {
|
||||
result := &Location{}
|
||||
|
||||
result.ID = maps.S2Token(lat, lng)
|
||||
result.LocLat = lat
|
||||
result.LocLng = lng
|
||||
result.ID = s2.Token(lat, lng)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -42,11 +39,9 @@ func (m *Location) Find(db *gorm.DB) error {
|
|||
|
||||
l := &maps.Location{
|
||||
ID: m.ID,
|
||||
LocLat: m.LocLat,
|
||||
LocLng: m.LocLng,
|
||||
}
|
||||
|
||||
if err := l.Query(); err != nil {
|
||||
if err := l.QueryPlaces(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -93,14 +88,6 @@ func (m *Location) Unknown() bool {
|
|||
return m.ID == ""
|
||||
}
|
||||
|
||||
func (m *Location) Latitude() float64 {
|
||||
return m.LocLat
|
||||
}
|
||||
|
||||
func (m *Location) Longitude() float64 {
|
||||
return m.LocLng
|
||||
}
|
||||
|
||||
func (m *Location) Name() string {
|
||||
return m.LocName
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/maps/osm"
|
||||
"github.com/photoprism/photoprism/internal/maps/places"
|
||||
)
|
||||
|
||||
/* TODO
|
||||
|
@ -24,8 +25,6 @@ ORDER BY loc_country, album_name, taken_year;
|
|||
// Photo location
|
||||
type Location struct {
|
||||
ID string
|
||||
LocLat float64
|
||||
LocLng float64
|
||||
LocName string
|
||||
LocCategory string
|
||||
LocSuburb string
|
||||
|
@ -37,9 +36,8 @@ type Location struct {
|
|||
}
|
||||
|
||||
type LocationSource interface {
|
||||
CellID() string
|
||||
CountryCode() string
|
||||
Latitude() float64
|
||||
Longitude() float64
|
||||
Category() string
|
||||
Name() string
|
||||
City() string
|
||||
|
@ -48,47 +46,53 @@ type LocationSource interface {
|
|||
Source() string
|
||||
}
|
||||
|
||||
func NewLocation(lat, lng float64) *Location {
|
||||
id := S2Token(lat, lng)
|
||||
|
||||
func NewLocation(id string) *Location {
|
||||
result := &Location{
|
||||
ID: id,
|
||||
LocLat: lat,
|
||||
LocLng: lng,
|
||||
ID: id,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *Location) Query() error {
|
||||
o, err := osm.FindLocation(l.LocLat, l.LocLng)
|
||||
func (l *Location) QueryPlaces() error {
|
||||
s, err := places.FindLocation(l.ID)
|
||||
|
||||
if err != nil {
|
||||
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 {
|
||||
l.LocSource = s.Source()
|
||||
|
||||
if l.LocLat == 0 {
|
||||
l.LocLat = s.Latitude()
|
||||
}
|
||||
if l.LocLng == 0 {
|
||||
l.LocLng = s.Longitude()
|
||||
}
|
||||
l.ID = s.CellID()
|
||||
|
||||
if l.Unknown() {
|
||||
l.LocCategory = "unknown"
|
||||
return errors.New("maps: unknown location")
|
||||
}
|
||||
|
||||
if l.ID == "" {
|
||||
l.ID = S2Token(l.LocLat, l.LocLng)
|
||||
}
|
||||
|
||||
l.LocName = s.Name()
|
||||
l.LocCity = s.City()
|
||||
l.LocSuburb = s.Suburb()
|
||||
|
@ -101,11 +105,7 @@ func (l *Location) Assign(s LocationSource) error {
|
|||
}
|
||||
|
||||
func (l *Location) Unknown() bool {
|
||||
if l.LocLng == 0.0 && l.LocLat == 0.0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
return l.ID == ""
|
||||
}
|
||||
|
||||
func (l *Location) label() string {
|
||||
|
@ -133,14 +133,6 @@ func (l *Location) label() string {
|
|||
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 {
|
||||
return l.LocName
|
||||
}
|
||||
|
|
|
@ -1,20 +1,40 @@
|
|||
package maps
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/maps/osm"
|
||||
"github.com/photoprism/photoprism/internal/s2"
|
||||
"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) {
|
||||
lat := 52.5208
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -27,8 +47,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
||||
lat := 52.5208
|
||||
lng := 13.40953
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -54,8 +75,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("SantaMonica", func(t *testing.T) {
|
||||
lat := 34.00909444444444
|
||||
lng := -118.49700833333334
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -82,8 +104,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("AirportZurich", func(t *testing.T) {
|
||||
lat := 47.45401666666667
|
||||
lng := 8.557494444444446
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -111,8 +134,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("AirportTegel", func(t *testing.T) {
|
||||
lat := 52.559864397033024
|
||||
lng := 13.28895092010498
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -140,8 +164,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("PinkBeach", func(t *testing.T) {
|
||||
lat := 35.26967222222222
|
||||
lng := 23.53711666666667
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -162,7 +187,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
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, "Chrisoskalitissa, Crete, Greece", l.LocLabel)
|
||||
})
|
||||
|
@ -170,8 +195,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("NewJersey", func(t *testing.T) {
|
||||
lat := 40.74290
|
||||
lng := -74.04862
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -192,7 +218,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
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, "Jersey City, New Jersey, USA", l.LocLabel)
|
||||
})
|
||||
|
@ -200,8 +226,9 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("SouthAfrica", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
o, err := osm.FindLocation(lat, lng)
|
||||
o, err := osm.FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -222,7 +249,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
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, "Eastern Cape, South Africa", l.LocLabel)
|
||||
})
|
||||
|
@ -230,11 +257,14 @@ func TestLocation_Assign(t *testing.T) {
|
|||
t.Run("Unknown", func(t *testing.T) {
|
||||
lat := -21.976301666666668
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
|
||||
assert.False(t, o.Cached)
|
||||
|
@ -250,16 +280,18 @@ func TestLocation_Unknown(t *testing.T) {
|
|||
t.Run("true", func(t *testing.T) {
|
||||
lat := 0.0
|
||||
lng := 0.0
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l := NewLocation(lat, lng)
|
||||
l := NewLocation(id)
|
||||
|
||||
assert.Equal(t, true, l.Unknown())
|
||||
})
|
||||
t.Run("false", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l := NewLocation(lat, lng)
|
||||
l := NewLocation(id)
|
||||
|
||||
assert.Equal(t, false, l.Unknown())
|
||||
})
|
||||
|
@ -269,49 +301,22 @@ func TestLocation_place(t *testing.T) {
|
|||
t.Run("unknown", func(t *testing.T) {
|
||||
lat := 0.0
|
||||
lng := 0.0
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l := NewLocation(lat, lng)
|
||||
l := NewLocation(id)
|
||||
|
||||
assert.Equal(t, "Unknown", l.label())
|
||||
})
|
||||
t.Run("Nürnberg, Bayern, Germany", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
||||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
||||
|
||||
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())
|
||||
assert.Equal(t, "Unknown", l.label())
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocation_Name(t *testing.T) {
|
||||
t.Run("Christkindlesmarkt", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||
|
||||
assert.Equal(t, "Christkindlesmarkt", l.Name())
|
||||
})
|
||||
|
@ -319,10 +324,7 @@ func TestLocation_Name(t *testing.T) {
|
|||
|
||||
func TestLocation_City(t *testing.T) {
|
||||
t.Run("Nürnberg", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt"}
|
||||
|
||||
assert.Equal(t, "Nürnberg", l.City())
|
||||
})
|
||||
|
@ -330,10 +332,7 @@ func TestLocation_City(t *testing.T) {
|
|||
|
||||
func TestLocation_Suburb(t *testing.T) {
|
||||
t.Run("Hauptmarkt", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "Hauptmarkt", l.Suburb())
|
||||
})
|
||||
|
@ -341,10 +340,7 @@ func TestLocation_Suburb(t *testing.T) {
|
|||
|
||||
func TestLocation_State(t *testing.T) {
|
||||
t.Run("Bayern", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "Bayern", l.State())
|
||||
})
|
||||
|
@ -352,10 +348,7 @@ func TestLocation_State(t *testing.T) {
|
|||
|
||||
func TestLocation_Category(t *testing.T) {
|
||||
t.Run("test", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "test", l.Category())
|
||||
})
|
||||
|
@ -363,10 +356,7 @@ func TestLocation_Category(t *testing.T) {
|
|||
|
||||
func TestLocation_Source(t *testing.T) {
|
||||
t.Run("source", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt", LocSource: "source"}
|
||||
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt", LocSource: "source"}
|
||||
|
||||
assert.Equal(t, "source", l.Source())
|
||||
})
|
||||
|
@ -374,10 +364,7 @@ func TestLocation_Source(t *testing.T) {
|
|||
|
||||
func TestLocation_Place(t *testing.T) {
|
||||
t.Run("test-label", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
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"}
|
||||
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "test-label", l.Label())
|
||||
})
|
||||
|
@ -385,10 +372,7 @@ func TestLocation_Place(t *testing.T) {
|
|||
|
||||
func TestLocation_CountryCode(t *testing.T) {
|
||||
t.Run("de", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
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"}
|
||||
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "de", l.CountryCode())
|
||||
})
|
||||
|
@ -396,10 +380,7 @@ func TestLocation_CountryCode(t *testing.T) {
|
|||
|
||||
func TestLocation_CountryName(t *testing.T) {
|
||||
t.Run("Germany", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
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"}
|
||||
l := &Location{LocCategory: "test", LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "Germany", l.CountryName())
|
||||
})
|
||||
|
|
|
@ -9,25 +9,25 @@ import (
|
|||
func TestOSM_Category(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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ package osm
|
|||
|
||||
import "fmt"
|
||||
|
||||
func (o Location) Category() (result string) {
|
||||
key := fmt.Sprintf("%s=%s", o.LocCategory, o.LocType)
|
||||
catKey := fmt.Sprintf("%s=*", o.LocCategory)
|
||||
typeKey := fmt.Sprintf("*=%s", o.LocType)
|
||||
func (l Location) Category() (result string) {
|
||||
key := fmt.Sprintf("%s=%s", l.LocCategory, l.LocType)
|
||||
catKey := fmt.Sprintf("%s=*", l.LocCategory)
|
||||
typeKey := fmt.Sprintf("*=%s", l.LocType)
|
||||
|
||||
if result, ok := osmCategories[key]; ok {
|
||||
return result
|
||||
|
|
|
@ -2,20 +2,20 @@ package osm
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/melihmucuk/geocache"
|
||||
"github.com/photoprism/photoprism/internal/s2"
|
||||
"github.com/photoprism/photoprism/internal/util"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
ID string `json:"-"`
|
||||
PlaceID int `json:"place_id"`
|
||||
LocLat string `json:"lat"`
|
||||
LocLng string `json:"lon"`
|
||||
LocName string `json:"name"`
|
||||
LocCategory string `json:"category"`
|
||||
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"
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
result.Cached = false
|
||||
|
@ -66,23 +80,27 @@ func FindLocation(lat, lng float64) (result Location, err error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (o Location) State() (result string) {
|
||||
result = o.Address.State
|
||||
func (l Location) CellID() (result string) {
|
||||
return l.ID
|
||||
}
|
||||
|
||||
func (l Location) State() (result string) {
|
||||
result = l.Address.State
|
||||
|
||||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
func (o Location) City() (result string) {
|
||||
if o.Address.City != "" {
|
||||
result = o.Address.City
|
||||
} else if o.Address.Town != "" {
|
||||
result = o.Address.Town
|
||||
} else if o.Address.Village != "" {
|
||||
result = o.Address.Village
|
||||
} else if o.Address.County != "" {
|
||||
result = o.Address.County
|
||||
} else if o.Address.State != "" {
|
||||
result = o.Address.State
|
||||
func (l Location) City() (result string) {
|
||||
if l.Address.City != "" {
|
||||
result = l.Address.City
|
||||
} else if l.Address.Town != "" {
|
||||
result = l.Address.Town
|
||||
} else if l.Address.Village != "" {
|
||||
result = l.Address.Village
|
||||
} else if l.Address.County != "" {
|
||||
result = l.Address.County
|
||||
} else if l.Address.State != "" {
|
||||
result = l.Address.State
|
||||
}
|
||||
|
||||
if len([]rune(result)) > 19 {
|
||||
|
@ -92,52 +110,22 @@ func (o Location) City() (result string) {
|
|||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
func (o Location) Suburb() (result string) {
|
||||
result = o.Address.Suburb
|
||||
func (l Location) Suburb() (result string) {
|
||||
result = l.Address.Suburb
|
||||
|
||||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
func (o Location) CountryCode() (result string) {
|
||||
result = o.Address.CountryCode
|
||||
func (l Location) CountryCode() (result string) {
|
||||
result = l.Address.CountryCode
|
||||
|
||||
return strings.ToLower(strings.TrimSpace(result))
|
||||
}
|
||||
|
||||
func (o Location) Latitude() (result float64) {
|
||||
if o.LocLat == "" {
|
||||
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 (l Location) Keywords() (result []string) {
|
||||
return util.Keywords(l.LocDisplayName)
|
||||
}
|
||||
|
||||
func (o Location) Longitude() (result float64) {
|
||||
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 {
|
||||
func (l Location) Source() string {
|
||||
return "osm"
|
||||
}
|
||||
|
|
|
@ -3,15 +3,17 @@ package osm
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/s2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFindLocation(t *testing.T) {
|
||||
t.Run("BerlinFernsehturm", func(t *testing.T) {
|
||||
t.Run("Fernsehturm Berlin 1", func(t *testing.T) {
|
||||
lat := 52.5208
|
||||
lng := 13.40953
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l, err := FindLocation(lat, lng)
|
||||
l, err := FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -29,7 +31,7 @@ func TestFindLocation(t *testing.T) {
|
|||
|
||||
assert.Equal(t, 123456, l.PlaceID)
|
||||
|
||||
cached, err := FindLocation(lat, lng)
|
||||
cached, err := FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -44,19 +46,20 @@ func TestFindLocation(t *testing.T) {
|
|||
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
|
||||
lng := 13.40889
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l, err := FindLocation(lat, lng)
|
||||
l, err := FindLocation(id)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.False(t, l.Cached)
|
||||
assert.Equal(t, 48287001, l.PlaceID)
|
||||
assert.Equal(t, "Menschen Museum", l.LocName)
|
||||
assert.Equal(t, 189675302, l.PlaceID)
|
||||
assert.Equal(t, "Fernsehturm Berlin", l.LocName)
|
||||
assert.Equal(t, "10178", l.Address.Postcode)
|
||||
assert.Equal(t, "Berlin", l.Address.State)
|
||||
assert.Equal(t, "de", l.Address.CountryCode)
|
||||
|
@ -66,14 +69,15 @@ func TestFindLocation(t *testing.T) {
|
|||
t.Run("No Location", func(t *testing.T) {
|
||||
lat := 0.0
|
||||
lng := 0.0
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l, err := FindLocation(lat, lng)
|
||||
l, err := FindLocation(id)
|
||||
|
||||
if err == 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)
|
||||
})
|
||||
}
|
||||
|
@ -82,7 +86,7 @@ func TestOSM_State(t *testing.T) {
|
|||
t.Run("Berlin", 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}
|
||||
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||
assert.Equal(t, "Berlin", l.State())
|
||||
})
|
||||
}
|
||||
|
@ -124,7 +128,7 @@ func TestOSM_Suburb(t *testing.T) {
|
|||
t.Run("Neukölln", 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}
|
||||
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||
assert.Equal(t, "Neukölln", l.Suburb())
|
||||
})
|
||||
}
|
||||
|
@ -133,34 +137,16 @@ func TestOSM_CountryCode(t *testing.T) {
|
|||
t.Run("de", 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}
|
||||
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||
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) {
|
||||
t.Run("cat", 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: "cat", Address: a}
|
||||
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
||||
assert.Equal(t, []string{"cat"}, l.Keywords())
|
||||
})
|
||||
}
|
||||
|
@ -168,6 +154,6 @@ func TestOSM_Keywords(t *testing.T) {
|
|||
func TestOSM_Source(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: "cat", Address: a}
|
||||
l := &Location{LocCategory: "natural", LocName: "Nice title", LocType: "hill", LocDisplayName: "cat", Address: a}
|
||||
assert.Equal(t, "osm", l.Source())
|
||||
}
|
||||
|
|
|
@ -11,16 +11,16 @@ var labelTitles = map[string]string{
|
|||
"visitor center": "Visitor Center",
|
||||
}
|
||||
|
||||
func (o Location) Name() (result string) {
|
||||
result = o.Category()
|
||||
func (l Location) Name() (result string) {
|
||||
result = l.Category()
|
||||
|
||||
if title, ok := labelTitles[result]; ok {
|
||||
title = strings.Replace(title, "%name%", o.LocName, 1)
|
||||
title = strings.Replace(title, "%name%", l.LocName, 1)
|
||||
return title
|
||||
}
|
||||
|
||||
if o.LocName != "" {
|
||||
result = o.LocName
|
||||
if l.LocName != "" {
|
||||
result = l.LocName
|
||||
}
|
||||
|
||||
result = strings.Replace(result, "_", " ", -1)
|
||||
|
|
|
@ -8,32 +8,32 @@ import (
|
|||
|
||||
func TestOSM_Name(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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
|
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
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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, "Japan", location.CountryName())
|
||||
assert.Equal(t, "", location.Category())
|
||||
assert.Equal(t, 34.79745, location.Latitude())
|
||||
assert.Equal(t, "3554df45c", location.ID)
|
||||
assert.True(t, strings.HasPrefix(location.ID, "3554df45"))
|
||||
location2, err := mediaFile.Location()
|
||||
|
||||
if err != nil {
|
||||
|
@ -67,8 +67,7 @@ func TestMediaFile_Location(t *testing.T) {
|
|||
assert.Equal(t, "Tübingen", location.City())
|
||||
assert.Equal(t, "de", location.CountryCode())
|
||||
assert.Equal(t, "Germany", location.CountryName())
|
||||
assert.Equal(t, 48.53870833333333, location.Latitude())
|
||||
assert.Equal(t, "4799e4a5c", location.ID)
|
||||
assert.True(t, strings.HasPrefix(location.ID, "4799e4a5"))
|
||||
})
|
||||
t.Run("dog_orange.jpg", func(t *testing.T) {
|
||||
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