package search import ( "time" "github.com/gin-gonic/gin" geojson "github.com/paulmach/go.geojson" "github.com/photoprism/photoprism/internal/entity" ) // GeoResult represents a photo geo search result. type GeoResult struct { ID string `json:"-" select:"photos.id"` PhotoUID string `json:"UID" select:"photos.photo_uid"` PhotoType string `json:"Type,omitempty" select:"photos.photo_type"` PhotoLat float32 `json:"Lat" select:"photos.photo_lat"` PhotoLng float32 `json:"Lng" select:"photos.photo_lng"` PhotoTitle string `json:"Title" select:"photos.photo_title"` PhotoDescription string `json:"Description,omitempty" select:"photos.photo_description"` PhotoFavorite bool `json:"Favorite,omitempty" select:"photos.photo_favorite"` FileHash string `json:"Hash" select:"files.file_hash"` FileWidth int `json:"Width" select:"files.file_width"` FileHeight int `json:"Height" select:"files.file_height"` TakenAt time.Time `json:"TakenAt" select:"photos.taken_at"` TakenAtLocal time.Time `json:"TakenAtLocal" select:"photos.taken_at_local"` } // Lat returns the position latitude. func (photo GeoResult) Lat() float64 { return float64(photo.PhotoLat) } // Lng returns the position longitude. func (photo GeoResult) Lng() float64 { return float64(photo.PhotoLng) } // IsPlayable returns true if the photo has a related video/animation that is playable. func (photo GeoResult) IsPlayable() bool { switch photo.PhotoType { case entity.MediaVideo, entity.MediaLive, entity.MediaAnimated: return true default: return false } } // GeoResults represents a list of geo search results. type GeoResults []GeoResult // GeoJSON returns results as specified on https://geojson.org/. func (photos GeoResults) GeoJSON() ([]byte, error) { fc := geojson.NewFeatureCollection() bbox := make([]float64, 4) bboxMin := func(pos int, val float64) { if bbox[pos] == 0.0 || bbox[pos] > val { bbox[pos] = val } } bboxMax := func(pos int, val float64) { if bbox[pos] == 0.0 || bbox[pos] < val { bbox[pos] = val } } for _, p := range photos { bboxMin(0, p.Lng()) bboxMin(1, p.Lat()) bboxMax(2, p.Lng()) bboxMax(3, p.Lat()) props := gin.H{ "UID": p.PhotoUID, "Hash": p.FileHash, "TakenAt": p.TakenAt, "Title": p.PhotoTitle, } if p.PhotoType != entity.MediaImage && p.PhotoType != entity.MediaUnknown { props["Type"] = p.PhotoType } if p.PhotoFavorite { props["Favorite"] = true } feat := geojson.NewPointFeature([]float64{p.Lng(), p.Lat()}) feat.ID = p.ID feat.Properties = props fc.AddFeature(feat) } fc.BoundingBox = bbox return fc.MarshalJSON() }