Rename "location" to "geo" to have a short, common prefix for geo data

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer 2020-07-11 23:43:29 +02:00
parent 8fd381860a
commit e1c45c4d5f
43 changed files with 452 additions and 476 deletions

View file

@ -116,7 +116,7 @@
{
text: showName ? this.$gettext('Name') : this.$gettext('Location'),
class: 'hidden-xs-only',
value: showName ? 'FileName' : 'LocLabel',
value: showName ? 'FileName' : 'GeoLabel',
sortable: false
},
{text: '', value: '', align: 'center', sortable: false},

View file

@ -145,7 +145,7 @@
<td>
<v-text-field
@change="save"
flat solo dense hide-details v-model="model.GPSAccuracy"
flat solo dense hide-details v-model="model.GeoAccuracy"
color="secondary-dark"
type="number"
suffix="m"

View file

@ -72,7 +72,6 @@ export class Photo extends RestModel {
Lat: 0.0,
Lng: 0.0,
Altitude: 0,
GPSAccuracy: 0,
Iso: 0,
FocalLength: 0,
FNumber: 0.0,
@ -100,16 +99,17 @@ export class Photo extends RestModel {
Labels: [],
Keywords: [],
Albums: [],
Location: {},
Geo: {},
Place: {},
PlaceID: "",
LocationID: "",
LocationSrc: "",
GeoID: "",
GeoSrc: "",
GeoAccuracy: 0,
// Additional data in result lists.
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
GeoLabel: "",
GeoCity: "",
GeoState: "",
GeoCountry: "",
FileUID: "",
FileRoot: "",
FileName: "",
@ -469,7 +469,7 @@ export class Photo extends RestModel {
}
}
return this.LocLabel ? this.LocLabel : $gettext("Unknown");
return this.GeoLabel ? this.GeoLabel : $gettext("Unknown");
}
addSizeInfo(file, info) {
@ -636,7 +636,7 @@ export class Photo extends RestModel {
}
if (values.Lat || values.Lng || values.Country) {
values.LocationSrc = SrcManual;
values.GeoSrc = SrcManual;
}
if (values.TakenAt || values.TimeZone || values.Day || values.Month || values.Year) {

View file

@ -130,8 +130,8 @@
openLocation(index) {
const photo = this.results[index];
if (photo.LocationID && photo.LocationID !== "zz") {
this.$router.push({name: "place", params: {q: photo.LocationID}});
if (photo.GeoID && photo.GeoID !== "zz") {
this.$router.push({name: "place", params: {q: photo.GeoID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
@ -178,11 +178,7 @@
return true;
},
viewerResults() {
if (this.loading || this.viewer.loading) {
return Promise.reject();
}
if (this.complete) {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}

View file

@ -170,8 +170,8 @@
openLocation(index) {
const photo = this.results[index];
if (photo.LocationID && photo.LocationID !== "zz") {
this.$router.push({name: "place", params: {q: photo.LocationID}});
if (photo.GeoID && photo.GeoID !== "zz") {
this.$router.push({name: "place", params: {q: photo.GeoID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
@ -216,11 +216,7 @@
}
},
viewerResults() {
if (this.loading || this.viewer.loading) {
return Promise.reject();
}
if (this.complete) {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}

View file

@ -92,7 +92,7 @@
{text: this.$gettext('Title'), value: 'Title', sortable: false},
{text: this.$gettext('Taken'), class: 'hidden-xs-only', value: 'TakenAt', sortable: false},
{text: this.$gettext('Camera'), class: 'hidden-sm-and-down', value: 'CameraModel', sortable: false},
{text: showName ? this.$gettext('Name') : this.$gettext('Location'), class: 'hidden-xs-only', value: showName ? 'FileName' : 'LocLabel', sortable: false},
{text: showName ? this.$gettext('Name') : this.$gettext('Location'), class: 'hidden-xs-only', value: showName ? 'FileName' : 'GeoLabel', sortable: false},
],
showName: showName,
showLocation: this.$config.settings().features.places,

View file

@ -173,8 +173,8 @@
openLocation(index) {
const photo = this.results[index];
if (photo.LocationID && photo.LocationID !== "zz") {
this.$router.push({name: "place", params: {q: photo.LocationID}});
if (photo.GeoID && photo.GeoID !== "zz") {
this.$router.push({name: "place", params: {q: photo.GeoID}});
} else if (photo.PlaceID && photo.PlaceID !== "zz") {
this.$router.push({name: "place", params: {q: photo.PlaceID}});
} else if (photo.Country && photo.Country !== "zz") {
@ -221,11 +221,7 @@
return true;
},
viewerResults() {
if (this.loading || this.viewer.loading) {
return Promise.reject();
}
if (this.complete) {
if (this.complete || this.loading || this.viewer.loading) {
return Promise.resolve(this.results);
}

View file

@ -178,28 +178,28 @@ describe("model/photo", () => {
});
it("should get location", () => {
const values = {ID: 5, Title: "Crazy Cat", LocUID: 6, LocType: "viewpoint", LocLabel: "Cape Point, South Africa", LocCountry: "South Africa"};
const values = {ID: 5, Title: "Crazy Cat", GeoID: 6, GeoType: "viewpoint", GeoLabel: "Cape Point, South Africa", GeoCountry: "South Africa"};
const photo = new Photo(values);
const result = photo.locationInfo();
assert.equal(result, "Cape Point, South Africa");
});
it("should get location", () => {
const values = {ID: 5, Title: "Crazy Cat", LocUID: 6, LocType: "viewpoint", LocLabel: "Cape Point, State, South Africa", LocCountry: "South Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
const values = {ID: 5, Title: "Crazy Cat", GeoID: 6, GeoType: "viewpoint", GeoLabel: "Cape Point, State, South Africa", GeoCountry: "South Africa", GeoCity: "Cape Town", GeoCounty: "County", GeoState: "State"};
const photo = new Photo(values);
const result = photo.locationInfo();
assert.equal(result, "Cape Point, State, South Africa");
});
it("should get location", () => {
const values = {ID: 5, Title: "Crazy Cat", LocType: "viewpoint", LocName: "Cape Point", LocCountry: "Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
const values = {ID: 5, Title: "Crazy Cat", GeoType: "viewpoint", GeoName: "Cape Point", GeoCountry: "Africa", GeoCity: "Cape Town", GeoCounty: "County", GeoState: "State"};
const photo = new Photo(values);
const result = photo.locationInfo();
assert.equal(result, "Unknown");
});
it("should get location", () => {
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", LocCity: "Cape Town"};
const values = {ID: 5, Title: "Crazy Cat", CountryName: "Africa", GeoCity: "Cape Town"};
const photo = new Photo(values);
const result = photo.locationInfo();
assert.equal(result, "Unknown");
@ -425,7 +425,7 @@ describe("model/photo", () => {
});
it("should get location info", () => {
const values = {ID: 5, UID: "ABC123", Country: "zz", PlaceID: "zz", LocLabel: "Nice beach"};
const values = {ID: 5, UID: "ABC123", Country: "zz", PlaceID: "zz", GeoLabel: "Nice beach"};
const photo = new Photo(values);
assert.equal(photo.locationInfo(), "Nice beach");
const values2 = {ID: 5, UID: "ABC123", Country: "es", PlaceID: "zz"};

View file

@ -17,7 +17,7 @@ const (
ResourceLabels Resource = "labels"
ResourceLenses Resource = "lenses"
ResourceLinks Resource = "links"
ResourceLocations Resource = "locations"
ResourceGeo Resource = "geo"
ResourcePasswords Resource = "passwords"
ResourcePeople Resource = "people"
ResourcePhotos Resource = "photos"

View file

@ -15,7 +15,7 @@ type Label struct {
Categories []string `json:"categories"` // List of similar labels
}
// LocationLabel returns a new labels for a location and expects name, uncertainty and priority as arguments.
// LocationLabel returns a new location label.
func LocationLabel(name string, uncertainty int, priority int) Label {
if index := strings.Index(name, " / "); index > 1 {
name = name[:index]

View file

@ -77,11 +77,11 @@ type CategoryLabel struct {
}
type ClientPosition struct {
PhotoUID string `json:"uid"`
LocationID string `json:"loc"`
TakenAt time.Time `json:"utc"`
PhotoLat float64 `json:"lat"`
PhotoLng float64 `json:"lng"`
PhotoUID string `json:"uid"`
GeoID string `json:"geo"`
TakenAt time.Time `json:"utc"`
PhotoLat float64 `json:"lat"`
PhotoLng float64 `json:"lng"`
}
// Flags returns config flags as string slice.
@ -220,7 +220,7 @@ func (c *Config) UserConfig() ClientConfig {
}
c.Db().Table("photos").
Select("photo_uid, location_id, photo_lat, photo_lng, taken_at").
Select("photo_uid, geo_id, photo_lat, photo_lng, taken_at").
Where("deleted_at IS NULL AND photo_lat != 0 AND photo_lng != 0").
Order("taken_at DESC").
Limit(1).Offset(0).

View file

@ -39,7 +39,7 @@ var Entities = Types{
"photos": &Photo{},
"details": &Details{},
"places": &Place{},
"locations": &Location{},
"geo": &Geo{},
"cameras": &Camera{},
"lenses": &Lens{},
"countries": &Country{},
@ -121,7 +121,7 @@ func (list Types) Drop() {
func CreateDefaultFixtures() {
CreateDefaultUsers()
CreateUnknownPlace()
CreateUnknownLocation()
CreateUnknownGeo()
CreateUnknownCountry()
CreateUnknownCamera()
CreateUnknownLens()

View file

@ -19,7 +19,7 @@ func CreateTestFixtures() {
CreateKeywordFixtures()
CreatePhotoKeywordFixtures()
CreateCategoryFixtures()
CreateLocationFixtures()
CreateGeoFixtures()
CreatePlaceFixtures()
CreateFileShareFixtures()
CreateFileSyncFixtures()

View file

@ -10,36 +10,41 @@ import (
"github.com/photoprism/photoprism/pkg/txt"
)
// Location used to associate photos to location
type Location struct {
// Geo used to associate photos to location
type Geo struct {
ID string `gorm:"type:varbinary(42);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
PlaceID string `gorm:"type:varbinary(42);" json:"-" yaml:"PlaceID"`
Place *Place `gorm:"PRELOAD:true" json:"Place" yaml:"-"`
LocName string `gorm:"type:varchar(255);" json:"Name" yaml:"Name,omitempty"`
LocCategory string `gorm:"type:varchar(64);" json:"Category" yaml:"Category,omitempty"`
LocSource string `gorm:"type:varbinary(16);" json:"Source" yaml:"Source,omitempty"`
GeoName string `gorm:"type:varchar(255);" json:"Name" yaml:"Name,omitempty"`
GeoCategory string `gorm:"type:varchar(64);" json:"Category" yaml:"Category,omitempty"`
GeoSource string `gorm:"type:varbinary(16);" json:"Source" yaml:"Source,omitempty"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
}
// UnknownLocation is PhotoPrism's default location.
var UnknownLocation = Location{
// TableName return the database table name.
func (Geo) TableName() string {
return "geo"
}
// UnknownGeo is PhotoPrism's default location.
var UnknownGeo = Geo{
ID: "zz",
Place: &UnknownPlace,
PlaceID: "zz",
LocName: "",
LocCategory: "",
LocSource: SrcAuto,
GeoName: "",
GeoCategory: "",
GeoSource: SrcAuto,
}
// CreateUnknownLocation creates the default location if not exists.
func CreateUnknownLocation() {
FirstOrCreateLocation(&UnknownLocation)
// CreateUnknownGeo creates the default location if not exists.
func CreateUnknownGeo() {
FirstOrCreateGeo(&UnknownGeo)
}
// NewLocation creates a location using a token extracted from coordinate
func NewLocation(lat, lng float32) *Location {
result := &Location{}
// NewGeo creates a location using a token extracted from coordinate
func NewGeo(lat, lng float32) *Geo {
result := &Geo{}
result.ID = s2.PrefixedToken(float64(lat), float64(lng))
@ -47,12 +52,12 @@ func NewLocation(lat, lng float32) *Location {
}
// Find retrieves location data from the database or an external api if not known already.
func (m *Location) Find(api string) error {
func (m *Geo) Find(api string) error {
start := time.Now()
db := Db()
if err := db.Preload("Place").First(m, "id = ?", m.ID).Error; err == nil {
log.Infof("location: found %s (%+v)", m.ID, m)
log.Infof("geo: found %s (%+v)", m.ID, m)
return nil
}
@ -61,7 +66,7 @@ func (m *Location) Find(api string) error {
}
if err := l.QueryApi(api); err != nil {
log.Errorf("location: %s failed %s", m.ID, err)
log.Errorf("geo: %s failed %s", m.ID, err)
return err
}
@ -70,11 +75,11 @@ func (m *Location) Find(api string) error {
} else {
place := &Place{
ID: l.PrefixedToken(),
LocLabel: l.Label(),
LocCity: l.City(),
LocState: l.State(),
LocCountry: l.CountryCode(),
LocKeywords: l.KeywordString(),
GeoLabel: l.Label(),
GeoCity: l.City(),
GeoState: l.State(),
GeoCountry: l.CountryCode(),
GeoKeywords: l.KeywordString(),
PhotoCount: 1,
}
@ -95,41 +100,41 @@ func (m *Location) Find(api string) error {
}
m.PlaceID = m.Place.ID
m.LocName = l.Name()
m.LocCategory = l.Category()
m.LocSource = l.Source()
m.GeoName = l.Name()
m.GeoCategory = l.Category()
m.GeoSource = l.Source()
if err := db.Create(m).Error; err == nil {
log.Infof("location: added %s [%s]", m.ID, time.Since(start))
log.Infof("geo: added %s [%s]", m.ID, time.Since(start))
return nil
} else if err := db.Preload("Place").First(m, "id = ?", m.ID).Error; err != nil {
log.Errorf("location: failed adding %s %s [%s]", m.ID, err.Error(), time.Since(start))
log.Errorf("geo: failed adding %s %s [%s]", m.ID, err.Error(), time.Since(start))
return err
} else {
log.Infof("location: found %s after second try [%s]", m.ID, time.Since(start))
log.Infof("geo: found %s after second try [%s]", m.ID, time.Since(start))
}
return nil
}
// Create inserts a new row to the database.
func (m *Location) Create() error {
func (m *Geo) Create() error {
return Db().Create(m).Error
}
// FirstOrCreateLocation fetches an existing row, inserts a new row or nil in case of errors.
func FirstOrCreateLocation(m *Location) *Location {
// FirstOrCreateGeo fetches an existing row, inserts a new row or nil in case of errors.
func FirstOrCreateGeo(m *Geo) *Geo {
if m.ID == "" {
log.Errorf("location: id must not be empty")
log.Errorf("geo: id must not be empty")
return nil
}
if m.PlaceID == "" {
log.Errorf("location: place_id must not be empty (first or create %s)", m.ID)
log.Errorf("geo: place_id must not be empty (first or create %s)", m.ID)
return nil
}
result := Location{}
result := Geo{}
if findErr := Db().Where("id = ?", m.ID).First(&result).Error; findErr == nil {
return &result
@ -138,16 +143,16 @@ func FirstOrCreateLocation(m *Location) *Location {
} else if err := Db().Where("id = ?", m.ID).First(&result).Error; err == nil {
return &result
} else {
log.Errorf("location: %s (first or create %s)", createErr, m.ID)
log.Errorf("geo: %s (first or create %s)", createErr, m.ID)
}
return nil
}
// Keywords returns search keywords for a location.
func (m *Location) Keywords() (result []string) {
func (m *Geo) Keywords() (result []string) {
if m.Place == nil {
log.Errorf("location: place for %s is nil - you might have found a bug", m.ID)
log.Errorf("geo: place for %s is nil - you might have found a bug", m.ID)
return result
}
@ -156,7 +161,7 @@ func (m *Location) Keywords() (result []string) {
result = append(result, txt.Keywords(txt.ReplaceSpaces(m.CountryName(), "-"))...)
result = append(result, txt.Keywords(m.Category())...)
result = append(result, txt.Keywords(m.Name())...)
result = append(result, txt.Keywords(m.Place.LocKeywords)...)
result = append(result, txt.Keywords(m.Place.GeoKeywords)...)
result = txt.UniqueWords(result)
@ -164,81 +169,81 @@ func (m *Location) Keywords() (result []string) {
}
// Unknown checks if the location has no id
func (m *Location) Unknown() bool {
return m.ID == "" || m.ID == UnknownLocation.ID
func (m *Geo) Unknown() bool {
return m.ID == "" || m.ID == UnknownGeo.ID
}
// Name returns name of location
func (m *Location) Name() string {
return m.LocName
func (m *Geo) Name() string {
return m.GeoName
}
// NoName checks if the location has no name
func (m *Location) NoName() bool {
return m.LocName == ""
func (m *Geo) NoName() bool {
return m.GeoName == ""
}
// Category returns the location category
func (m *Location) Category() string {
return m.LocCategory
func (m *Geo) Category() string {
return m.GeoCategory
}
// NoCategory checks id the location has no category
func (m *Location) NoCategory() bool {
return m.LocCategory == ""
func (m *Geo) NoCategory() bool {
return m.GeoCategory == ""
}
// Label returns the location place label
func (m *Location) Label() string {
func (m *Geo) Label() string {
return m.Place.Label()
}
// City returns the location place city
func (m *Location) City() string {
func (m *Geo) City() string {
return m.Place.City()
}
// LongCity checks if the city name is more than 16 char
func (m *Location) LongCity() bool {
func (m *Geo) LongCity() bool {
return len(m.City()) > 16
}
// NoCity checks if the location has no city
func (m *Location) NoCity() bool {
func (m *Geo) NoCity() bool {
return m.City() == ""
}
// CityContains checks if the location city contains the text string
func (m *Location) CityContains(text string) bool {
func (m *Geo) CityContains(text string) bool {
return strings.Contains(text, m.City())
}
// State returns the location place state
func (m *Location) State() string {
func (m *Geo) State() string {
return m.Place.State()
}
// NoState checks if the location place has no state
func (m *Location) NoState() bool {
func (m *Geo) NoState() bool {
return m.Place.State() == ""
}
// CountryCode returns the location place country code
func (m *Location) CountryCode() string {
func (m *Geo) CountryCode() string {
return m.Place.CountryCode()
}
// CountryName returns the location place country name
func (m *Location) CountryName() string {
func (m *Geo) CountryName() string {
return m.Place.CountryName()
}
// Notes returns the locations place notes
func (m *Location) Notes() string {
func (m *Geo) Notes() string {
return m.Place.Notes()
}
// Source returns the source of location information
func (m *Location) Source() string {
return m.LocSource
func (m *Geo) Source() string {
return m.GeoSource
}

View file

@ -4,32 +4,32 @@ import (
"github.com/photoprism/photoprism/pkg/s2"
)
type LocationMap map[string]Location
type GeoMap map[string]Geo
func (m LocationMap) Get(name string) Location {
func (m GeoMap) Get(name string) Geo {
if result, ok := m[name]; ok {
return result
}
return UnknownLocation
return UnknownGeo
}
func (m LocationMap) Pointer(name string) *Location {
func (m GeoMap) Pointer(name string) *Geo {
if result, ok := m[name]; ok {
return &result
}
return &UnknownLocation
return &UnknownGeo
}
var LocationFixtures = LocationMap{
var GeoFixtures = GeoMap{
"mexico": {
ID: s2.TokenPrefix + "85d1ea7d382c",
PlaceID: PlaceFixtures.Get("mexico").ID,
LocName: "Adosada Platform",
LocCategory: "botanical garden",
GeoName: "Adosada Platform",
GeoCategory: "botanical garden",
Place: PlaceFixtures.Pointer("mexico"),
LocSource: "places",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -38,16 +38,16 @@ var LocationFixtures = LocationMap{
PlaceID: s2.TokenPrefix + "1ef75a71a36c",
Place: &Place{
ID: s2.TokenPrefix + "1ef75a71a36",
LocLabel: "Mandeni, KwaZulu-Natal, South Africa",
LocCity: "Mandeni",
LocState: "KwaZulu-Natal",
LocCountry: "za",
GeoLabel: "Mandeni, KwaZulu-Natal, South Africa",
GeoCity: "Mandeni",
GeoState: "KwaZulu-Natal",
GeoCountry: "za",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
LocName: "Lobotes Caravan Park",
LocCategory: "camping",
LocSource: "manual",
GeoName: "Lobotes Caravan Park",
GeoCategory: "camping",
GeoSource: "manual",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -55,9 +55,9 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e28c",
PlaceID: PlaceFixtures.Get("zinkwazi").ID,
Place: PlaceFixtures.Pointer("zinkwazi"),
LocName: "Zinkwazi Beach",
LocCategory: "beach",
LocSource: "places",
GeoName: "Zinkwazi Beach",
GeoCategory: "beach",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -65,9 +65,9 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e280",
PlaceID: PlaceFixtures.Get("holidaypark").ID,
Place: PlaceFixtures.Pointer("holidaypark"),
LocName: "Holiday Park",
LocCategory: "park",
LocSource: "places",
GeoName: "Holiday Park",
GeoCategory: "park",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -75,9 +75,9 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e281",
PlaceID: PlaceFixtures.Get("emptyNameLongCity").ID,
Place: PlaceFixtures.Pointer("emptyNameLongCity"),
LocName: "",
LocCategory: "botanical garden",
LocSource: "places",
GeoName: "",
GeoCategory: "botanical garden",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -85,9 +85,9 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e282",
PlaceID: PlaceFixtures.Get("emptyNameShortCity").ID,
Place: PlaceFixtures.Pointer("emptyNameShortCity"),
LocName: "",
LocCategory: "botanical garden",
LocSource: "places",
GeoName: "",
GeoCategory: "botanical garden",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -95,9 +95,9 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e283",
PlaceID: PlaceFixtures.Get("veryLongLocName").ID,
Place: PlaceFixtures.Pointer("veryLongLocName"),
LocName: "longlonglonglonglonglonglonglonglonglonglonglonglongName",
LocCategory: "cape",
LocSource: "places",
GeoName: "longlonglonglonglonglonglonglonglonglonglonglonglongName",
GeoCategory: "cape",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
@ -105,17 +105,17 @@ var LocationFixtures = LocationMap{
ID: s2.TokenPrefix + "1ef744d1e283",
PlaceID: PlaceFixtures.Get("mediumLongLocName").ID,
Place: PlaceFixtures.Pointer("mediumLongLocName"),
LocName: "longlonglonglonglonglongName",
LocCategory: "botanical garden",
LocSource: "places",
GeoName: "longlonglonglonglonglongName",
GeoCategory: "botanical garden",
GeoSource: "places",
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
}
// CreateLocationFixtures inserts known entities into the database for testing.
func CreateLocationFixtures() {
for _, entity := range LocationFixtures {
// CreateGeoFixtures inserts known entities into the database for testing.
func CreateGeoFixtures() {
for _, entity := range GeoFixtures {
Db().Create(&entity)
}
}

View file

@ -7,28 +7,28 @@ import (
func TestLocationMap_Get(t *testing.T) {
t.Run("get existing location", func(t *testing.T) {
r := LocationFixtures.Get("mexico")
assert.Equal(t, "Adosada Platform", r.LocName)
r := GeoFixtures.Get("mexico")
assert.Equal(t, "Adosada Platform", r.GeoName)
assert.Equal(t, "s2:85d1ea7d382c", r.ID)
assert.IsType(t, Location{}, r)
assert.IsType(t, Geo{}, r)
})
t.Run("get not existing location", func(t *testing.T) {
r := LocationFixtures.Get("Fusion 3333")
r := GeoFixtures.Get("Fusion 3333")
assert.Equal(t, "zz", r.ID)
assert.IsType(t, Location{}, r)
assert.IsType(t, Geo{}, r)
})
}
func TestLocationMap_Pointer(t *testing.T) {
t.Run("get existing location pointer", func(t *testing.T) {
r := LocationFixtures.Pointer("mexico")
assert.Equal(t, "Adosada Platform", r.LocName)
r := GeoFixtures.Pointer("mexico")
assert.Equal(t, "Adosada Platform", r.GeoName)
assert.Equal(t, "s2:85d1ea7d382c", r.ID)
assert.IsType(t, &Location{}, r)
assert.IsType(t, &Geo{}, r)
})
t.Run("get not existing location pointer", func(t *testing.T) {
r := LocationFixtures.Pointer("Fusion 444")
r := GeoFixtures.Pointer("Fusion 444")
assert.Equal(t, "zz", r.ID)
assert.IsType(t, &Location{}, r)
assert.IsType(t, &Geo{}, r)
})
}

View file

@ -8,11 +8,11 @@ import (
func TestNewLocation(t *testing.T) {
t.Run("new label", func(t *testing.T) {
l := NewLocation(1, 1)
l.LocCategory = "restaurant"
l.LocName = "LocationName"
l := NewGeo(1, 1)
l.GeoCategory = "restaurant"
l.GeoName = "LocationName"
l.Place = PlaceFixtures.Pointer("zinkwazi")
l.LocSource = "places"
l.GeoSource = "places"
assert.Equal(t, "restaurant", l.Category())
assert.Equal(t, false, l.NoCategory())
@ -35,17 +35,17 @@ func TestNewLocation(t *testing.T) {
func TestLocation_Keywords(t *testing.T) {
t.Run("mexico", func(t *testing.T) {
m := LocationFixtures["mexico"]
m := GeoFixtures["mexico"]
r := m.Keywords()
assert.Equal(t, []string{"adosada", "ancient", "botanical", "garden", "mexico", "platform", "pyramid", "state-of-mexico", "teotihuacán"}, r)
})
t.Run("caravan park", func(t *testing.T) {
m := LocationFixtures["caravan park"]
m := GeoFixtures["caravan park"]
r := m.Keywords()
assert.Equal(t, []string{"camping", "caravan", "kwazulu-natal", "lobotes", "mandeni", "park", "south-africa"}, r)
})
t.Run("place id empty", func(t *testing.T) {
m := &Location{}
m := &Geo{}
r := m.Keywords()
assert.Empty(t, r)
})
@ -53,12 +53,12 @@ func TestLocation_Keywords(t *testing.T) {
func TestLocation_Find(t *testing.T) {
t.Run("place in db", func(t *testing.T) {
m := LocationFixtures["mexico"]
m := GeoFixtures["mexico"]
r := m.Find("")
assert.Nil(t, r)
})
t.Run("invalid api", func(t *testing.T) {
l := NewLocation(2, 1)
l := NewGeo(2, 1)
err := l.Find("")
if err == nil {
@ -71,19 +71,19 @@ func TestLocation_Find(t *testing.T) {
func TestFirstOrCreateLocation(t *testing.T) {
t.Run("id empty", func(t *testing.T) {
loc := &Location{}
loc := &Geo{}
assert.Nil(t, FirstOrCreateLocation(loc))
assert.Nil(t, FirstOrCreateGeo(loc))
})
t.Run("place id empty", func(t *testing.T) {
loc := &Location{ID: "1234jhy"}
loc := &Geo{ID: "1234jhy"}
assert.Nil(t, FirstOrCreateLocation(loc))
assert.Nil(t, FirstOrCreateGeo(loc))
})
t.Run("success", func(t *testing.T) {
loc := LocationFixtures.Pointer("caravan park")
loc := GeoFixtures.Pointer("caravan park")
result := FirstOrCreateLocation(loc)
result := FirstOrCreateGeo(loc)
if result == nil {
t.Fatal("result should not be nil")

View file

@ -51,9 +51,9 @@ type Photo struct {
PhotoScan bool `json:"Scan" yaml:"Scan,omitempty"`
TimeZone string `gorm:"type:varbinary(64);" json:"TimeZone" yaml:"-"`
PlaceID string `gorm:"type:varbinary(42);index;" json:"PlaceID" yaml:"-"`
LocationID string `gorm:"type:varbinary(42);index;" json:"LocationID" yaml:"-"`
LocationSrc string `gorm:"type:varbinary(8);" json:"LocationSrc" yaml:"LocationSrc,omitempty"`
GPSAccuracy int `gorm:"column:gps_accuracy" json:"GPSAccuracy" yaml:"GPSAccuracy,omitempty"`
GeoID string `gorm:"type:varbinary(42);index;" json:"GeoID" yaml:"-"`
GeoSrc string `gorm:"type:varbinary(8);" json:"GeoSrc" yaml:"GeoSrc,omitempty"`
GeoAccuracy int `json:"GeoAccuracy" yaml:"GeoAccuracy,omitempty"`
PhotoAltitude int `json:"Altitude" yaml:"Altitude,omitempty"`
PhotoLat float32 `gorm:"type:FLOAT;index;" json:"Lat" yaml:"Lat,omitempty"`
PhotoLng float32 `gorm:"type:FLOAT;index;" json:"Lng" yaml:"Lng,omitempty"`
@ -74,7 +74,7 @@ type Photo struct {
Details *Details `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Details" yaml:"Details"`
Camera *Camera `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Camera" yaml:"-"`
Lens *Lens `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Lens" yaml:"-"`
Location *Location `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Location" yaml:"-"`
Geo *Geo `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Geo" yaml:"-"`
Place *Place `gorm:"association_autoupdate:false;association_autocreate:false;association_save_reference:false" json:"Place" yaml:"-"`
Keywords []Keyword `json:"-" yaml:"-"`
Albums []Album `json:"-" yaml:"-"`
@ -94,11 +94,11 @@ func NewPhoto() Photo {
PhotoCountry: UnknownCountry.ID,
CameraID: UnknownCamera.ID,
LensID: UnknownLens.ID,
LocationID: UnknownLocation.ID,
GeoID: UnknownGeo.ID,
PlaceID: UnknownPlace.ID,
Camera: &UnknownCamera,
Lens: &UnknownLens,
Location: &UnknownLocation,
Geo: &UnknownGeo,
Place: &UnknownPlace,
}
}
@ -127,7 +127,7 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
details.Keywords = strings.Join(txt.UniqueWords(txt.Words(details.Keywords)), ", ")
}
if locChanged && model.LocationSrc == SrcManual {
if locChanged && model.GeoSrc == SrcManual {
locKeywords, labels := model.UpdateLocation(geoApi)
model.AddLabels(labels)
@ -238,8 +238,8 @@ func (m *Photo) Find() error {
Preload("Lens").
Preload("Details").
Preload("Place").
Preload("Location").
Preload("Location.Place")
Preload("Geo").
Preload("Geo.Place")
if rnd.IsPPID(m.PhotoUID, 'p') {
if err := q.First(m, "photo_uid = ?", m.PhotoUID).Error; err != nil {
@ -469,9 +469,9 @@ func (m *Photo) HasID() bool {
return m.ID > 0 && m.PhotoUID != ""
}
// UnknownLocation checks if the photo has an unknown location.
// UnknownGeo checks if the photo has an unknown location.
func (m *Photo) UnknownLocation() bool {
return m.LocationID == "" || m.LocationID == UnknownLocation.ID
return m.GeoID == "" || m.GeoID == UnknownGeo.ID
}
// HasLocation checks if the photo has a known location.
@ -481,15 +481,15 @@ func (m *Photo) HasLocation() bool {
// LocationLoaded checks if the photo has a known location that is currently loaded.
func (m *Photo) LocationLoaded() bool {
if m.Location == nil {
if m.Geo == nil {
return false
}
if m.Location.Place == nil {
if m.Geo.Place == nil {
return false
}
return !m.Location.Unknown() && m.Location.ID == m.LocationID
return !m.Geo.Unknown() && m.Geo.ID == m.GeoID
}
// LoadLocation loads the photo location from the database if not done already.
@ -502,9 +502,9 @@ func (m *Photo) LoadLocation() error {
return fmt.Errorf("photo: unknown location (%s)", m)
}
var location Location
var location Geo
err := Db().Preload("Place").First(&location, "id = ?", m.LocationID).Error
err := Db().Preload("Place").First(&location, "id = ?", m.GeoID).Error
if err != nil {
return err
@ -515,7 +515,7 @@ func (m *Photo) LoadLocation() error {
location.PlaceID = UnknownPlace.ID
}
m.Location = &location
m.Geo = &location
return nil
}
@ -670,7 +670,7 @@ func (m *Photo) UpdateTitle(labels classify.Labels) error {
if m.LocationLoaded() {
knownLocation = true
loc := m.Location
loc := m.Geo
// TODO: User defined title format
if title := labels.Title(loc.Name()); title != "" {
@ -865,14 +865,14 @@ func (m *Photo) SetCoordinates(lat, lng float32, altitude int, source string) {
return
}
if m.LocationSrc != SrcAuto && m.LocationSrc != source && source != SrcManual {
if m.GeoSrc != SrcAuto && m.GeoSrc != source && source != SrcManual {
return
}
m.PhotoLat = lat
m.PhotoLng = lng
m.PhotoAltitude = altitude
m.LocationSrc = source
m.GeoSrc = source
}
// AllFilesMissing returns true, if all files for this photo are missing.

View file

@ -54,14 +54,14 @@ var PhotoFixtures = PhotoMap{
LensID: LensFixtures.Pointer("lens-f-380").ID,
CameraSerial: "",
CameraSrc: "",
LocationSrc: "",
GeoSrc: "",
TimeZone: "",
PhotoYear: 2790,
PhotoMonth: 2,
Details: DetailsFixtures.Pointer("lake", 1000000),
DescriptionSrc: "",
LocationID: UnknownLocation.ID,
Location: &UnknownLocation,
GeoID: UnknownGeo.ID,
Geo: &UnknownGeo,
PlaceID: UnknownPlace.ID,
Place: &UnknownPlace,
PhotoCountry: UnknownPlace.CountryCode(),
@ -111,9 +111,9 @@ var PhotoFixtures = PhotoMap{
CameraSrc: "",
Place: &UnknownPlace,
PlaceID: UnknownPlace.ID,
Location: &UnknownLocation,
LocationID: UnknownLocation.ID,
LocationSrc: "",
Geo: &UnknownGeo,
GeoID: UnknownGeo.ID,
GeoSrc: "",
TimeZone: "",
PhotoCountry: UnknownPlace.CountryCode(),
PhotoYear: 2790,
@ -154,7 +154,7 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "",
CameraSrc: "",
LocationSrc: "",
GeoSrc: "",
TimeZone: "",
PhotoCountry: UnknownPlace.CountryCode(),
PhotoYear: 1990,
@ -165,8 +165,8 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: &UnknownLocation,
LocationID: UnknownLocation.ID,
Geo: &UnknownGeo,
GeoID: UnknownGeo.ID,
Place: &UnknownPlace,
PlaceID: UnknownPlace.ID,
Keywords: []Keyword{},
@ -202,13 +202,13 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "",
CameraSrc: "",
Place: LocationFixtures.Pointer("caravan park").Place,
PlaceID: LocationFixtures.Pointer("caravan park").Place.ID,
Location: LocationFixtures.Pointer("caravan park"),
LocationID: LocationFixtures.Pointer("caravan park").ID,
LocationSrc: "",
Place: GeoFixtures.Pointer("caravan park").Place,
PlaceID: GeoFixtures.Pointer("caravan park").Place.ID,
Geo: GeoFixtures.Pointer("caravan park"),
GeoID: GeoFixtures.Pointer("caravan park").ID,
GeoSrc: "",
TimeZone: "",
PhotoCountry: LocationFixtures.Pointer("caravan park").Place.CountryCode(),
PhotoCountry: GeoFixtures.Pointer("caravan park").Place.CountryCode(),
PhotoYear: 1990,
PhotoMonth: 4,
Details: DetailsFixtures.Pointer("bridge", 1000003),
@ -256,9 +256,9 @@ var PhotoFixtures = PhotoMap{
CameraSrc: "",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "",
TimeZone: "",
PhotoCountry: PlaceFixtures.Pointer("mexico").CountryCode(),
PhotoYear: 2014,
@ -306,9 +306,9 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "123",
CameraSrc: "",
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
TimeZone: "",
@ -354,9 +354,9 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "",
CameraSrc: "",
Location: &UnknownLocation,
LocationID: UnknownLocation.ID,
LocationSrc: "",
Geo: &UnknownGeo,
GeoID: UnknownGeo.ID,
GeoSrc: "",
Place: &UnknownPlace,
PlaceID: UnknownPlace.ID,
TimeZone: "",
@ -402,9 +402,9 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "",
CameraSrc: "",
Location: &UnknownLocation,
LocationID: UnknownLocation.ID,
LocationSrc: "",
Geo: &UnknownGeo,
GeoID: UnknownGeo.ID,
GeoSrc: "",
Place: &UnknownPlace,
PlaceID: UnknownPlace.ID,
TimeZone: "",
@ -460,9 +460,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "manual",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "manual",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
Keywords: []Keyword{},
@ -508,9 +508,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
Keywords: []Keyword{},
@ -556,9 +556,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("hassloch"),
LocationID: LocationFixtures.Pointer("hassloch").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("hassloch"),
GeoID: GeoFixtures.Pointer("hassloch").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("holidaypark"),
PlaceID: PlaceFixtures.Pointer("holidaypark").ID,
Keywords: []Keyword{},
@ -604,9 +604,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("emptyNameLongCity"),
LocationID: LocationFixtures.Pointer("emptyNameLongCity").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("emptyNameLongCity"),
GeoID: GeoFixtures.Pointer("emptyNameLongCity").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("emptyNameLongCity"),
PlaceID: PlaceFixtures.Pointer("emptyNameLongCity").ID,
Keywords: []Keyword{},
@ -646,9 +646,9 @@ var PhotoFixtures = PhotoMap{
LensID: LensFixtures.Pointer("lens-f-380").ID,
CameraSerial: "",
CameraSrc: "",
Location: LocationFixtures.Pointer("emptyNameShortCity"),
LocationID: LocationFixtures.Pointer("emptyNameShortCity").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("emptyNameShortCity"),
GeoID: GeoFixtures.Pointer("emptyNameShortCity").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("emptyNameShortCity"),
PlaceID: PlaceFixtures.Pointer("emptyNameShortCity").ID,
TimeZone: "",
@ -690,9 +690,9 @@ var PhotoFixtures = PhotoMap{
PhotoExposure: "",
CameraSerial: "",
CameraSrc: "",
Location: LocationFixtures.Pointer("veryLongLocName"),
LocationID: LocationFixtures.Pointer("veryLongLocName").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("veryLongLocName"),
GeoID: GeoFixtures.Pointer("veryLongLocName").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("veryLongLocName"),
PlaceID: PlaceFixtures.Pointer("veryLongLocName").ID,
TimeZone: "",
@ -748,9 +748,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("mediumLongLocName"),
LocationID: LocationFixtures.Pointer("mediumLongLocName").ID,
LocationSrc: "",
Geo: GeoFixtures.Pointer("mediumLongLocName"),
GeoID: GeoFixtures.Pointer("mediumLongLocName").ID,
GeoSrc: "",
Place: PlaceFixtures.Pointer("mediumLongLocName"),
PlaceID: PlaceFixtures.Pointer("mediumLongLocName").ID,
Keywords: []Keyword{},
@ -788,10 +788,10 @@ var PhotoFixtures = PhotoMap{
CameraSerial: "",
CameraSrc: "",
Place: &UnknownPlace,
Location: &UnknownLocation,
Geo: &UnknownGeo,
PlaceID: UnknownPlace.ID,
LocationID: UnknownLocation.ID,
LocationSrc: "location",
GeoID: UnknownGeo.ID,
GeoSrc: "location",
TimeZone: "",
PhotoCountry: UnknownCountry.ID,
PhotoYear: 0,
@ -840,10 +840,10 @@ var PhotoFixtures = PhotoMap{
CameraSerial: "",
CameraSrc: "",
Place: &UnknownPlace,
Location: &UnknownLocation,
Geo: &UnknownGeo,
PlaceID: UnknownPlace.ID,
LocationID: UnknownLocation.ID,
LocationSrc: "location",
GeoID: UnknownGeo.ID,
GeoSrc: "location",
TimeZone: "",
PhotoCountry: UnknownCountry.ID,
PhotoYear: 0,
@ -887,9 +887,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "location",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "location",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
TimeZone: "",
@ -937,9 +937,9 @@ var PhotoFixtures = PhotoMap{
CameraID: CameraFixtures.Pointer("canon-eos-6d").ID,
Lens: LensFixtures.Pointer("lens-f-380"),
LensID: LensFixtures.Pointer("lens-f-380").ID,
Location: LocationFixtures.Pointer("mexico"),
LocationID: LocationFixtures.Pointer("mexico").ID,
LocationSrc: "location",
Geo: GeoFixtures.Pointer("mexico"),
GeoID: GeoFixtures.Pointer("mexico").ID,
GeoSrc: "location",
Place: PlaceFixtures.Pointer("mexico"),
PlaceID: PlaceFixtures.Pointer("mexico").ID,
TimeZone: "",
@ -984,10 +984,10 @@ var PhotoFixtures = PhotoMap{
CameraSerial: "",
CameraSrc: "",
Place: &UnknownPlace,
Location: &UnknownLocation,
Geo: &UnknownGeo,
PlaceID: UnknownPlace.ID,
LocationID: UnknownLocation.ID,
LocationSrc: "",
GeoID: UnknownGeo.ID,
GeoSrc: "",
TimeZone: "",
PhotoCountry: UnknownPlace.CountryCode(),
PhotoYear: 1990,

View file

@ -62,7 +62,7 @@ func (m *Photo) GetTakenAt() time.Time {
// UpdateLocation updates location and labels based on latitude and longitude.
func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classify.Labels) {
if m.HasLatLng() {
var location = NewLocation(m.PhotoLat, m.PhotoLng)
var location = NewGeo(m.PhotoLat, m.PhotoLng)
err := location.Find(geoApi)
@ -70,9 +70,9 @@ func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classif
log.Warnf("photo: location place is nil (uid %s, location %s) - bug?", m.PhotoUID, location.ID)
}
if err == nil && location.Place != nil && location.ID != UnknownLocation.ID {
m.Location = location
m.LocationID = location.ID
if err == nil && location.Place != nil && location.ID != UnknownGeo.ID {
m.Geo = location
m.GeoID = location.ID
m.Place = location.Place
m.PlaceID = location.PlaceID
m.PhotoCountry = location.CountryCode()
@ -100,11 +100,11 @@ func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classif
labels = classify.Labels{}
if m.UnknownLocation() {
m.Location = &UnknownLocation
m.LocationID = UnknownLocation.ID
m.Geo = &UnknownGeo
m.GeoID = UnknownGeo.ID
} else if err := m.LoadLocation(); err == nil {
m.Place = m.Location.Place
m.PlaceID = m.Location.PlaceID
m.Place = m.Geo.Place
m.PlaceID = m.Geo.PlaceID
} else {
log.Warn(err)
}

View file

@ -13,7 +13,7 @@ import (
// EstimateCountry updates the photo with an estimated country if possible.
func (m *Photo) EstimateCountry() {
if m.HasLatLng() || m.HasLocation() || m.HasPlace() || m.HasCountry() && m.LocationSrc != SrcAuto && m.LocationSrc != SrcEstimate {
if m.HasLatLng() || m.HasLocation() || m.HasPlace() || m.HasCountry() && m.GeoSrc != SrcAuto && m.GeoSrc != SrcEstimate {
// Do nothing.
return
}
@ -41,14 +41,14 @@ func (m *Photo) EstimateCountry() {
if countryCode != unknown {
m.PhotoCountry = countryCode
m.LocationSrc = SrcEstimate
m.GeoSrc = SrcEstimate
log.Debugf("photo: probable country for %s is %s", m, txt.Quote(m.CountryName()))
}
}
// EstimatePlace updates the photo with an estimated place and country if possible.
func (m *Photo) EstimatePlace() {
if m.HasLatLng() || m.HasLocation() || m.HasPlace() && m.LocationSrc != SrcAuto && m.LocationSrc != SrcEstimate {
if m.HasLatLng() || m.HasLocation() || m.HasPlace() && m.GeoSrc != SrcAuto && m.GeoSrc != SrcEstimate {
// Do nothing.
return
}
@ -79,11 +79,11 @@ func (m *Photo) EstimatePlace() {
m.Place = recentPhoto.Place
m.PlaceID = recentPhoto.PlaceID
m.PhotoCountry = recentPhoto.PhotoCountry
m.LocationSrc = SrcEstimate
m.GeoSrc = SrcEstimate
log.Debugf("photo: approximate position of %s is %s (id %s)", m, txt.Quote(m.CountryName()), recentPhoto.PlaceID)
} else if recentPhoto.HasCountry() {
m.PhotoCountry = recentPhoto.PhotoCountry
m.LocationSrc = SrcEstimate
m.GeoSrc = SrcEstimate
log.Debugf("photo: probable country for %s is %s", m, txt.Quote(m.CountryName()))
} else {
m.EstimateCountry()

View file

@ -31,8 +31,8 @@ func TestSavePhotoForm(t *testing.T) {
CameraID: uint(3),
CameraSrc: "meta",
LensID: uint(6),
LocationID: "1234",
LocationSrc: "manual",
GeoID: "1234",
GeoSrc: "manual",
PlaceID: "765",
PhotoCountry: "de",
Details: form.Details{
@ -93,8 +93,8 @@ func TestPhoto_SaveLabels(t *testing.T) {
CameraID: uint(3),
CameraSrc: "meta",
LensID: uint(6),
LocationID: "1234",
LocationSrc: "geo",
GeoID: "1234",
GeoSrc: "geo",
PlaceID: "765",
PhotoCountry: "de",
Keywords: []Keyword{},
@ -748,8 +748,8 @@ func TestPhoto_LocationLoaded(t *testing.T) {
assert.False(t, photo.LocationLoaded())
})
t.Run("false", func(t *testing.T) {
location := &Location{Place: nil}
photo := Photo{PhotoName: "Holiday", Location: location}
location := &Geo{Place: nil}
photo := Photo{PhotoName: "Holiday", Geo: location}
assert.False(t, photo.LocationLoaded())
})
}
@ -763,8 +763,8 @@ func TestPhoto_LoadLocation(t *testing.T) {
}
})
t.Run("unknown location", func(t *testing.T) {
location := &Location{Place: nil}
photo := Photo{PhotoName: "Holiday", Location: location}
location := &Geo{Place: nil}
photo := Photo{PhotoName: "Holiday", Geo: location}
assert.Error(t, photo.LoadLocation())
})
}
@ -785,8 +785,8 @@ func TestPhoto_LoadPlace(t *testing.T) {
}
})
t.Run("unknown location", func(t *testing.T) {
location := &Location{Place: nil}
photo := Photo{PhotoName: "Holiday", Location: location}
location := &Geo{Place: nil}
photo := Photo{PhotoName: "Holiday", Geo: location}
assert.Error(t, photo.LoadPlace())
})
}

View file

@ -2,6 +2,8 @@ package entity
import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
@ -23,25 +25,20 @@ func TestPhoto_SaveAsYaml(t *testing.T) {
t.Run("create from fixture", func(t *testing.T) {
m := PhotoFixtures.Get("Photo01")
m.PreloadFiles()
err := m.SaveAsYaml("test")
if err != nil {
fileName := filepath.Join(os.TempDir(), ".photoprism_test.yml")
if err := m.SaveAsYaml(fileName); err != nil {
t.Fatal(err)
}
})
}
func TestPhoto_LoadFromYaml(t *testing.T) {
t.Run("create from fixture", func(t *testing.T) {
m := PhotoFixtures.Get("Photo01")
m.PreloadFiles()
err := m.LoadFromYaml("test")
if err != nil {
if err := m.LoadFromYaml(fileName); err != nil {
t.Fatal(err)
}
if err := os.Remove(fileName); err != nil {
t.Fatal(err)
}
})
}

View file

@ -12,13 +12,13 @@ import (
// Place used to associate photos to places
type Place struct {
ID string `gorm:"type:varbinary(42);primary_key;auto_increment:false;" json:"PlaceID" yaml:"PlaceID"`
LocLabel string `gorm:"type:varbinary(768);unique_index;" json:"Label" yaml:"Label"`
LocCity string `gorm:"type:varchar(255);" json:"City" yaml:"City,omitempty"`
LocState string `gorm:"type:varchar(255);" json:"State" yaml:"State,omitempty"`
LocCountry string `gorm:"type:varbinary(2);" json:"Country" yaml:"Country,omitempty"`
LocKeywords string `gorm:"type:varchar(255);" json:"Keywords" yaml:"Keywords,omitempty"`
LocNotes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
LocFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
GeoLabel string `gorm:"type:varbinary(768);unique_index;" json:"Label" yaml:"Label"`
GeoCity string `gorm:"type:varchar(255);" json:"City" yaml:"City,omitempty"`
GeoState string `gorm:"type:varchar(255);" json:"State" yaml:"State,omitempty"`
GeoCountry string `gorm:"type:varbinary(2);" json:"Country" yaml:"Country,omitempty"`
GeoKeywords string `gorm:"type:varchar(255);" json:"Keywords" yaml:"Keywords,omitempty"`
GeoNotes string `gorm:"type:text;" json:"Notes" yaml:"Notes,omitempty"`
GeoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
PhotoCount int `gorm:"default:1" json:"PhotoCount" yaml:"-"`
CreatedAt time.Time `json:"CreatedAt" yaml:"-"`
UpdatedAt time.Time `json:"UpdatedAt" yaml:"-"`
@ -28,13 +28,13 @@ type Place struct {
// UnknownPlace is PhotoPrism's default place.
var UnknownPlace = Place{
ID: "zz",
LocLabel: "Unknown",
LocCity: "Unknown",
LocState: "Unknown",
LocCountry: "zz",
LocKeywords: "",
LocNotes: "",
LocFavorite: false,
GeoLabel: "Unknown",
GeoCity: "Unknown",
GeoState: "Unknown",
GeoCountry: "zz",
GeoKeywords: "",
GeoNotes: "",
GeoFavorite: false,
PhotoCount: -1,
}
@ -58,7 +58,7 @@ func FindPlace(id string, label string) *Place {
log.Debugf("place: %s for id %s", err.Error(), id)
return nil
}
} else if err := Db().First(place, "id = ? OR loc_label = ?", id, label).Error; err != nil {
} else if err := Db().First(place, "id = ? OR geo_label = ?", id, label).Error; err != nil {
log.Debugf("place: %s for id %s / label %s", err.Error(), id, txt.Quote(label))
return nil
}
@ -87,18 +87,18 @@ func FirstOrCreatePlace(m *Place) *Place {
return nil
}
if m.LocLabel == "" {
if m.GeoLabel == "" {
log.Errorf("place: label must not be empty (first or create %s)", m.ID)
return nil
}
result := Place{}
if findErr := Db().Where("id = ? OR loc_label = ?", m.ID, m.LocLabel).First(&result).Error; findErr == nil {
if findErr := Db().Where("id = ? OR geo_label = ?", m.ID, m.GeoLabel).First(&result).Error; findErr == nil {
return &result
} else if createErr := m.Create(); createErr == nil {
return m
} else if err := Db().Where("id = ? OR loc_label = ?", m.ID, m.LocLabel).First(&result).Error; err == nil {
} else if err := Db().Where("id = ? OR geo_label = ?", m.ID, m.GeoLabel).First(&result).Error; err == nil {
return &result
} else {
log.Errorf("place: %s (first or create %s)", createErr, m.ID)
@ -114,45 +114,45 @@ func (m Place) Unknown() bool {
// Label returns place label
func (m Place) Label() string {
return m.LocLabel
return m.GeoLabel
}
// City returns place City
func (m Place) City() string {
return m.LocCity
return m.GeoCity
}
// LongCity checks if the city name is more than 16 char.
func (m Place) LongCity() bool {
return len(m.LocCity) > 16
return len(m.GeoCity) > 16
}
// NoCity checks if the location has no city
func (m Place) NoCity() bool {
return m.LocCity == ""
return m.GeoCity == ""
}
// CityContains checks if the location city contains the text string
func (m Place) CityContains(text string) bool {
return strings.Contains(text, m.LocCity)
return strings.Contains(text, m.GeoCity)
}
// State returns place State
func (m Place) State() string {
return m.LocState
return m.GeoState
}
// CountryCode returns place CountryCode
func (m Place) CountryCode() string {
return m.LocCountry
return m.GeoCountry
}
// CountryName returns place CountryName
func (m Place) CountryName() string {
return maps.CountryNames[m.LocCountry]
return maps.CountryNames[m.GeoCountry]
}
// Notes returns place Notes
func (m Place) Notes() string {
return m.LocNotes
return m.GeoNotes
}

View file

@ -25,91 +25,91 @@ func (m PlacesMap) Pointer(name string) *Place {
var PlaceFixtures = PlacesMap{
"mexico": {
ID: s2.TokenPrefix + "85d1ea7d3278",
LocLabel: "Teotihuacán, Mexico, Mexico",
LocCity: "Teotihuacán",
LocState: "State of Mexico",
LocCountry: "mx",
LocKeywords: "ancient, pyramid",
LocNotes: "",
LocFavorite: false,
GeoLabel: "Teotihuacán, Mexico, Mexico",
GeoCity: "Teotihuacán",
GeoState: "State of Mexico",
GeoCountry: "mx",
GeoKeywords: "ancient, pyramid",
GeoNotes: "",
GeoFavorite: false,
PhotoCount: 1,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"zinkwazi": {
ID: s2.TokenPrefix + "1ef744d1e279",
LocLabel: "KwaDukuza, KwaZulu-Natal, South Africa",
LocCity: "KwaDukuza",
LocState: "KwaZulu-Natal",
LocCountry: "za",
LocKeywords: "",
LocNotes: "africa",
LocFavorite: true,
GeoLabel: "KwaDukuza, KwaZulu-Natal, South Africa",
GeoCity: "KwaDukuza",
GeoState: "KwaZulu-Natal",
GeoCountry: "za",
GeoKeywords: "",
GeoNotes: "africa",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"holidaypark": {
ID: s2.TokenPrefix + "1ef744d1e280",
LocLabel: "Holiday Park, Amusement",
LocCity: "",
LocState: "Rheinland-Pfalz",
LocCountry: "de",
LocKeywords: "",
LocNotes: "germany",
LocFavorite: true,
GeoLabel: "Holiday Park, Amusement",
GeoCity: "",
GeoState: "Rheinland-Pfalz",
GeoCountry: "de",
GeoKeywords: "",
GeoNotes: "germany",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"emptyNameLongCity": {
ID: s2.TokenPrefix + "1ef744d1e281",
LocLabel: "labelEmptyNameLongCity",
LocCity: "longlonglonglonglongcity",
LocState: "Rheinland-Pfalz",
LocCountry: "de",
LocKeywords: "",
LocNotes: "germany",
LocFavorite: true,
GeoLabel: "labelEmptyNameLongCity",
GeoCity: "longlonglonglonglongcity",
GeoState: "Rheinland-Pfalz",
GeoCountry: "de",
GeoKeywords: "",
GeoNotes: "germany",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"emptyNameShortCity": {
ID: s2.TokenPrefix + "1ef744d1e282",
LocLabel: "labelEmptyNameShortCity",
LocCity: "shortcity",
LocState: "Rheinland-Pfalz",
LocCountry: "de",
LocKeywords: "",
LocNotes: "germany",
LocFavorite: true,
GeoLabel: "labelEmptyNameShortCity",
GeoCity: "shortcity",
GeoState: "Rheinland-Pfalz",
GeoCountry: "de",
GeoKeywords: "",
GeoNotes: "germany",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"veryLongLocName": {
ID: s2.TokenPrefix + "1ef744d1e283",
LocLabel: "labelVeryLongLocName",
LocCity: "Mainz",
LocState: "Rheinland-Pfalz",
LocCountry: "de",
LocKeywords: "",
LocNotes: "germany",
LocFavorite: true,
GeoLabel: "labelVeryLongLocName",
GeoCity: "Mainz",
GeoState: "Rheinland-Pfalz",
GeoCountry: "de",
GeoKeywords: "",
GeoNotes: "germany",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
},
"mediumLongLocName": {
ID: s2.TokenPrefix + "1ef744d1e284",
LocLabel: "labelMediumLongLocName",
LocCity: "New york",
LocState: "New york",
LocCountry: "us",
LocKeywords: "",
LocNotes: "",
LocFavorite: true,
GeoLabel: "labelMediumLongLocName",
GeoCity: "New york",
GeoState: "New york",
GeoCountry: "us",
GeoKeywords: "",
GeoNotes: "",
GeoFavorite: true,
PhotoCount: 2,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),

View file

@ -8,14 +8,14 @@ import (
func TestPlaceMap_Get(t *testing.T) {
t.Run("get existing place", func(t *testing.T) {
r := PlaceFixtures.Get("mexico")
assert.Equal(t, "Teotihuacán", r.LocCity)
assert.Equal(t, "State of Mexico", r.LocState)
assert.Equal(t, "Teotihuacán", r.GeoCity)
assert.Equal(t, "State of Mexico", r.GeoState)
assert.IsType(t, Place{}, r)
})
t.Run("get not existing place", func(t *testing.T) {
r := PlaceFixtures.Get("xxx")
assert.Equal(t, "Unknown", r.LocCity)
assert.Equal(t, "Unknown", r.LocState)
assert.Equal(t, "Unknown", r.GeoCity)
assert.Equal(t, "Unknown", r.GeoState)
assert.IsType(t, Place{}, r)
})
}
@ -23,14 +23,14 @@ func TestPlaceMap_Get(t *testing.T) {
func TestPlaceMap_Pointer(t *testing.T) {
t.Run("get existing place pointer", func(t *testing.T) {
r := PlaceFixtures.Pointer("mexico")
assert.Equal(t, "Teotihuacán", r.LocCity)
assert.Equal(t, "State of Mexico", r.LocState)
assert.Equal(t, "Teotihuacán", r.GeoCity)
assert.Equal(t, "State of Mexico", r.GeoState)
assert.IsType(t, &Place{}, r)
})
t.Run("get not existing place pointer", func(t *testing.T) {
r := PlaceFixtures.Pointer("xxx")
assert.Equal(t, "Unknown", r.LocCity)
assert.Equal(t, "Unknown", r.LocState)
assert.Equal(t, "Unknown", r.GeoCity)
assert.Equal(t, "Unknown", r.GeoState)
assert.IsType(t, &Place{}, r)
})
}

View file

@ -20,7 +20,7 @@ func TestFindPlaceByLabel(t *testing.T) {
t.Fatal("result should not be nil")
}
assert.Equal(t, "de", r.LocCountry)
assert.Equal(t, "de", r.GeoCountry)
})
t.Run("find by id", func(t *testing.T) {
r := FindPlace(s2.TokenPrefix+"85d1ea7d3278", "")
@ -28,7 +28,7 @@ func TestFindPlaceByLabel(t *testing.T) {
if r == nil {
t.Fatal("result should not be nil")
}
assert.Equal(t, "mx", r.LocCountry)
assert.Equal(t, "mx", r.GeoCountry)
})
t.Run("find by label", func(t *testing.T) {
r := FindPlace("", "KwaDukuza, KwaZulu-Natal, South Africa")
@ -37,7 +37,7 @@ func TestFindPlaceByLabel(t *testing.T) {
t.Fatal("result should not be nil")
}
assert.Equal(t, "za", r.LocCountry)
assert.Equal(t, "za", r.GeoCountry)
})
t.Run("not matching", func(t *testing.T) {
r := FindPlace("111", "xxx")
@ -65,13 +65,13 @@ func TestPlace_Find(t *testing.T) {
t.Run("record does not exist", func(t *testing.T) {
place := &Place{
ID: s2.TokenPrefix + "1110",
LocLabel: "test",
LocCity: "testCity",
LocState: "",
LocCountry: "",
LocKeywords: "",
LocNotes: "",
LocFavorite: false,
GeoLabel: "test",
GeoCity: "testCity",
GeoState: "",
GeoCountry: "",
GeoKeywords: "",
GeoNotes: "",
GeoFavorite: false,
PhotoCount: 0,
CreatedAt: Timestamp(),
UpdatedAt: Timestamp(),
@ -86,47 +86,47 @@ func TestFirstOrCreatePlace(t *testing.T) {
t.Run("existing place", func(t *testing.T) {
m := PlaceFixtures.Pointer("zinkwazi")
r := FirstOrCreatePlace(m)
assert.Equal(t, "KwaDukuza, KwaZulu-Natal, South Africa", r.LocLabel)
assert.Equal(t, "KwaDukuza, KwaZulu-Natal, South Africa", r.GeoLabel)
})
t.Run("ID empty", func(t *testing.T) {
p := &Place{ID: ""}
assert.Nil(t, FirstOrCreatePlace(p))
})
t.Run("LocLabel empty", func(t *testing.T) {
p := &Place{ID: "abcde44", LocLabel: ""}
t.Run("GeoLabel empty", func(t *testing.T) {
p := &Place{ID: "abcde44", GeoLabel: ""}
assert.Nil(t, FirstOrCreatePlace(p))
})
}
func TestPlace_LongCity(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Place{LocCity: "veryveryveryverylongcity"}
p := Place{GeoCity: "veryveryveryverylongcity"}
assert.True(t, p.LongCity())
})
t.Run("false", func(t *testing.T) {
p := Place{LocCity: "short"}
p := Place{GeoCity: "short"}
assert.False(t, p.LongCity())
})
}
func TestPlace_NoCity(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Place{LocCity: ""}
p := Place{GeoCity: ""}
assert.True(t, p.NoCity())
})
t.Run("false", func(t *testing.T) {
p := Place{LocCity: "short"}
p := Place{GeoCity: "short"}
assert.False(t, p.NoCity())
})
}
func TestPlace_CityContains(t *testing.T) {
t.Run("true", func(t *testing.T) {
p := Place{LocCity: "Munich"}
p := Place{GeoCity: "Munich"}
assert.True(t, p.CityContains("Munich"))
})
t.Run("false", func(t *testing.T) {
p := Place{LocCity: "short"}
p := Place{GeoCity: "short"}
assert.False(t, p.CityContains("ich"))
})
}

View file

@ -35,9 +35,9 @@ type Photo struct {
PhotoPrivate bool `json:"Private"`
PhotoReview bool `json:"Review"`
PhotoScan bool `json:"Scan"`
LocationID string `json:"LocationID"`
LocationSrc string `json:"LocationSrc"`
GPSAccuracy int `json:"GPSAccuracy"`
GeoID string `json:"GeoID"`
GeoSrc string `json:"GeoSrc"`
GeoAccuracy int `json:"GeoAccuracy"`
PhotoAltitude int `json:"Altitude"`
PhotoLat float32 `json:"Lat"`
PhotoLng float32 `json:"Lng"`

View file

@ -38,7 +38,7 @@ type PhotoSearch struct {
Diff uint32 `form:"diff"`
Mono bool `form:"mono"`
Portrait bool `form:"portrait"`
Location bool `form:"location"`
Geo bool `form:"geo"`
Album string `form:"album"`
Label string `form:"label"`
Category string `form:"category"` // Moments

View file

@ -29,8 +29,8 @@ func TestNewPhoto(t *testing.T) {
CameraID: uint(3),
CameraSrc: "meta",
LensID: uint(6),
LocationID: "1234",
LocationSrc: "geo",
GeoID: "1234",
GeoSrc: "geo",
PlaceID: "765",
PhotoCountry: "de"}
@ -60,8 +60,8 @@ func TestNewPhoto(t *testing.T) {
assert.Equal(t, uint(3), r.CameraID)
assert.Equal(t, "meta", r.CameraSrc)
assert.Equal(t, uint(6), r.LensID)
assert.Equal(t, "1234", r.LocationID)
assert.Equal(t, "geo", r.LocationSrc)
assert.Equal(t, "1234", r.GeoID)
assert.Equal(t, "geo", r.GeoSrc)
assert.Equal(t, "765", r.PlaceID)
assert.Equal(t, "de", r.PhotoCountry)
})

View file

@ -9,20 +9,6 @@ import (
"github.com/photoprism/photoprism/pkg/s2"
)
/* TODO
(SELECT pl.loc_label as album_name, pl.loc_country, YEAR(ph.taken_at) as taken_year, round(count(ph.id)) as photo_count FROM photos ph
JOIN places pl ON ph.place_id = pl.id AND pl.id <> 1
GROUP BY album_name, taken_year HAVING photo_count > 5) UNION (
SELECT c.country_name AS album_name, pl.loc_country, YEAR(ph.taken_at) as taken_year, round(count(ph.id)) as photo_count FROM photos ph
JOIN places pl ON ph.place_id = pl.id AND pl.id <> 1
JOIN countries c ON c.id = pl.loc_country
GROUP BY album_name, taken_year
HAVING photo_count > 10)
ORDER BY loc_country, album_name, taken_year;
*/
// Photo location
type Location struct {
ID string

View file

@ -195,7 +195,7 @@ func TestLocation_Assign(t *testing.T) {
assert.Equal(t, "", l.LocCategory)
assert.Equal(t, "Unknown", l.LocCity)
// TODO: Should be zz for international waters, fixed in places server
// assert.Equal(t, "", l.LocCountry)
// assert.Equal(t, "", l.GeoCountry)
})
}

View file

@ -12,8 +12,8 @@ func (ind *Index) estimateLocation(photo *entity.Photo) {
if result := ind.db.Unscoped().Order(gorm.Expr("ABS(DATEDIFF(taken_at, ?)) ASC", photo.TakenAt)).Preload("Place").First(&recentPhoto); result.Error == nil {
if recentPhoto.HasPlace() {
photo.Place = recentPhoto.Place
photo.PhotoCountry = photo.Place.LocCountry
photo.LocationSrc = entity.SrcAuto
photo.PhotoCountry = photo.Place.GeoCountry
photo.GeoSrc = entity.SrcAuto
log.Debugf("index: approximate location is %s", txt.Quote(recentPhoto.Place.Label()))
}
}

View file

@ -479,8 +479,8 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
}
if photo.UnknownLocation() {
photo.Location = &entity.UnknownLocation
photo.LocationID = entity.UnknownLocation.ID
photo.Geo = &entity.UnknownGeo
photo.GeoID = entity.UnknownGeo.ID
}
if photo.UnknownPlace() {

View file

@ -6,8 +6,8 @@ import (
"github.com/photoprism/photoprism/internal/entity"
)
// Location returns the Location of a MediaFile.
func (m *MediaFile) Location() (*entity.Location, error) {
// Geo returns the Geo of a MediaFile.
func (m *MediaFile) Location() (*entity.Geo, error) {
if m.location != nil {
return m.location, nil
}
@ -18,7 +18,7 @@ func (m *MediaFile) Location() (*entity.Location, error) {
return nil, errors.New("mediafile: no latitude and longitude in metadata")
}
m.location = entity.NewLocation(data.Lat, data.Lng)
m.location = entity.NewGeo(data.Lat, data.Lng)
return m.location, nil
}

View file

@ -36,7 +36,7 @@ type MediaFile struct {
height int
metaData meta.Data
metaDataOnce sync.Once
location *entity.Location
location *entity.Geo
}
// NewMediaFile returns a new media file.

View file

@ -165,10 +165,10 @@ func Geo(f form.GeoSearch) (results GeoResults, err error) {
if f.S2 != "" {
s2Min, s2Max := s2.PrefixedRange(f.S2, 7)
s = s.Where("photos.location_id BETWEEN ? AND ?", s2Min, s2Max)
s = s.Where("photos.geo_id BETWEEN ? AND ?", s2Min, s2Max)
} else if f.Olc != "" {
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), 7)
s = s.Where("photos.location_id BETWEEN ? AND ?", s2Min, s2Max)
s = s.Where("photos.geo_id BETWEEN ? AND ?", s2Min, s2Max)
} else {
// Inaccurate distance search, but probably 'good enough' for now
if f.Lat > 0 {

View file

@ -119,10 +119,10 @@ func MomentsCountries(threshold int) (results Moments, err error) {
// MomentsStates returns the most popular states and countries by year.
func MomentsStates(threshold int) (results Moments, err error) {
db := UnscopedDb().Table("photos").
Select("p.loc_country AS country, p.loc_state AS state, COUNT(*) AS photo_count").
Select("p.geo_country AS country, p.geo_state AS state, COUNT(*) AS photo_count").
Joins("JOIN places p ON p.id = photos.place_id").
Where("photos.photo_quality >= 3 AND photos.deleted_at IS NULL AND photo_private = 0 AND p.loc_state <> '' AND p.loc_country <> 'zz'").
Group("p.loc_country, p.loc_state").
Where("photos.photo_quality >= 3 AND photos.deleted_at IS NULL AND photo_private = 0 AND p.geo_state <> '' AND p.geo_country <> 'zz'").
Group("p.geo_country, p.geo_state").
Having("photo_count >= ?", threshold)
if err := db.Scan(&results).Error; err != nil {

View file

@ -18,8 +18,8 @@ func PhotoByID(photoID uint64) (photo entity.Photo, err error) {
Preload("Lens").
Preload("Details").
Preload("Place").
Preload("Location").
Preload("Location.Place").
Preload("Geo").
Preload("Geo.Place").
First(&photo).Error; err != nil {
return photo, err
}
@ -38,8 +38,8 @@ func PhotoByUID(photoUID string) (photo entity.Photo, err error) {
Preload("Lens").
Preload("Details").
Preload("Place").
Preload("Location").
Preload("Location.Place").
Preload("Geo").
Preload("Geo.Place").
First(&photo).Error; err != nil {
return photo, err
}
@ -58,8 +58,8 @@ func PhotoPreloadByUID(photoUID string) (photo entity.Photo, err error) {
Preload("Lens").
Preload("Details").
Preload("Place").
Preload("Location").
Preload("Location.Place").
Preload("Geo").
Preload("Geo.Place").
First(&photo).Error; err != nil {
return photo, err
}
@ -101,8 +101,8 @@ func PhotosCheck(limit int, offset int) (entities entity.Photos, err error) {
Preload("Lens").
Preload("Details").
Preload("Place").
Preload("Location").
Preload("Location.Place").
Preload("Geo").
Preload("Geo.Place").
Where("checked_at IS NULL OR checked_at < ?", time.Now().Add(-1*time.Hour*24*3)).
Where("updated_at < ?", time.Now().Add(-1*time.Minute*10)).
Limit(limit).Offset(offset).Find(&entities).Error

View file

@ -48,16 +48,16 @@ type PhotoResult struct {
LensModel string `json:"LensModel"`
LensMake string `json:"LensMake"`
PlaceID string `json:"PlaceID"`
LocationID string `json:"LocationID"` // Location
LocationSrc string `json:"LocationSrc"`
GPSAccuracy int `json:"GPSAccuracy,omitempty"`
GeoID string `json:"GeoID"` // Geo
GeoSrc string `json:"GeoSrc"`
GeoAccuracy int `json:"GeoAccuracy,omitempty"`
PhotoAltitude int `json:"Altitude,omitempty"`
PhotoLat float32 `json:"Lat"`
PhotoLng float32 `json:"Lng"`
LocLabel string `json:"LocLabel"`
LocCity string `json:"LocCity"`
LocState string `json:"LocState"`
LocCountry string `json:"LocCountry"`
GeoLabel string `json:"GeoLabel"`
GeoCity string `json:"GeoCity"`
GeoState string `json:"GeoState"`
GeoCountry string `json:"GeoCountry"`
FileID uint `json:"-"` // File
FileUID string `json:"FileUID"`
FileRoot string `json:"FileRoot"`

View file

@ -41,12 +41,12 @@ func TestPhotosResults_Merged(t *testing.T) {
LensID: 0,
LensModel: "",
LensMake: "",
LocationID: "",
GeoID: "",
PlaceID: "",
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
GeoLabel: "",
GeoCity: "",
GeoState: "",
GeoCountry: "",
FileID: 0,
FileUID: "",
FilePrimary: false,
@ -100,12 +100,12 @@ func TestPhotosResults_Merged(t *testing.T) {
LensID: 0,
LensModel: "",
LensMake: "",
LocationID: "",
GeoID: "",
PlaceID: "",
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
GeoLabel: "",
GeoCity: "",
GeoState: "",
GeoCountry: "",
FileID: 0,
FileUID: "",
FilePrimary: false,
@ -172,12 +172,12 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
LensID: 0,
LensModel: "",
LensMake: "",
LocationID: "",
GeoID: "",
PlaceID: "",
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
GeoLabel: "",
GeoCity: "",
GeoState: "",
GeoCountry: "",
FileID: 0,
FileUID: "",
FilePrimary: false,
@ -235,12 +235,12 @@ func TestPhotosResult_ShareFileName(t *testing.T) {
LensID: 0,
LensModel: "",
LensMake: "",
LocationID: "",
GeoID: "",
PlaceID: "",
LocLabel: "",
LocCity: "",
LocState: "",
LocCountry: "",
GeoLabel: "",
GeoCity: "",
GeoState: "",
GeoCountry: "",
FileID: 0,
FileUID: "",
FilePrimary: false,

View file

@ -33,7 +33,7 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
files.file_diff, files.file_video, files.file_duration, files.file_size,
cameras.camera_make, cameras.camera_model,
lenses.lens_make, lenses.lens_model,
places.loc_label, places.loc_city, places.loc_state, places.loc_country`).
places.geo_label, places.geo_city, places.geo_state, places.geo_country`).
Joins("JOIN files ON photos.id = files.photo_id AND files.file_missing = 0 AND files.deleted_at IS NULL").
Joins("JOIN cameras ON photos.camera_id = cameras.id").
Joins("JOIN lenses ON photos.lens_id = lenses.id").
@ -100,8 +100,8 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
}
// Filter by location.
if f.Location == true {
s = s.Where("location_id <> ''")
if f.Geo == true {
s = s.Where("geo_id <> ''")
if likeAny := LikeAny("k.keyword", f.Query); likeAny != "" {
s = s.Where("photos.id IN (SELECT pk.photo_id FROM keywords k JOIN photos_keywords pk ON k.id = pk.keyword_id WHERE (?))", gorm.Expr(likeAny))
@ -199,12 +199,12 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
}
if f.State != "" {
s = s.Where("places.loc_state IN (?)", strings.Split(f.State, ","))
s = s.Where("places.geo_state IN (?)", strings.Split(f.State, ","))
}
if f.Category != "" {
s = s.Joins("JOIN locations ON photos.location_id = locations.id").
Where("locations.loc_category IN (?)", strings.Split(strings.ToLower(f.Category), ","))
s = s.Joins("JOIN geo ON photos.geo_id = geo.id").
Where("geo.geo_category IN (?)", strings.Split(strings.ToLower(f.Category), ","))
}
// Filter by media type.
@ -335,7 +335,7 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
s = s.Order("photos.id DESC, files.file_primary DESC")
case entity.SortOrderSimilar:
s = s.Where("files.file_diff > 0")
s = s.Order("files.file_main_color, photos.location_id, files.file_diff, taken_at DESC, files.file_primary DESC")
s = s.Order("files.file_main_color, photos.geo_id, files.file_diff, taken_at DESC, files.file_primary DESC")
case entity.SortOrderName:
s = s.Order("photos.photo_path, photos.photo_name, files.file_primary DESC")
default:

View file

@ -110,7 +110,7 @@ func TestPhotoSearch(t *testing.T) {
f.Query = ""
f.Count = 10
f.Offset = 0
f.Location = true
f.Geo = true
photos, _, err := PhotoSearch(f)
@ -125,7 +125,7 @@ func TestPhotoSearch(t *testing.T) {
f.Query = "bridge"
f.Count = 10
f.Offset = 0
f.Location = true
f.Geo = true
f.Error = false
photos, _, err := PhotoSearch(f)
@ -141,7 +141,7 @@ func TestPhotoSearch(t *testing.T) {
f.Query = "a"
f.Count = 5000
f.Offset = 0
f.Location = false
f.Geo = false
photos, _, err := PhotoSearch(f)