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:"-"` PhotoUID string `json:"UID"` PhotoType string `json:"Type,omitempty"` PhotoLat float32 `json:"Lat"` PhotoLng float32 `json:"Lng"` PhotoTitle string `json:"Title"` PhotoDescription string `json:"Description,omitempty"` PhotoFavorite bool `json:"Favorite,omitempty"` FileHash string `json:"Hash"` FileWidth int `json:"Width"` FileHeight int `json:"Height"` TakenAt time.Time `json:"TakenAt"` TakenAtLocal time.Time `json:"TakenAtLocal"` } // 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) } // 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.TypeImage && p.PhotoType != entity.TypeDefault { 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() }