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:
parent
8fd381860a
commit
e1c45c4d5f
43 changed files with 452 additions and 476 deletions
|
@ -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},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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"};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -19,7 +19,7 @@ func CreateTestFixtures() {
|
|||
CreateKeywordFixtures()
|
||||
CreatePhotoKeywordFixtures()
|
||||
CreateCategoryFixtures()
|
||||
CreateLocationFixtures()
|
||||
CreateGeoFixtures()
|
||||
CreatePlaceFixtures()
|
||||
CreateFileShareFixtures()
|
||||
CreateFileSyncFixtures()
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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")
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue