2021-11-26 13:59:10 +01:00
|
|
|
package search
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
geojson "github.com/paulmach/go.geojson"
|
2021-11-26 21:10:52 +01:00
|
|
|
|
2021-11-26 13:59:10 +01:00
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GeoResult represents a photo geo search result.
|
|
|
|
type GeoResult struct {
|
2022-03-30 20:36:25 +02:00
|
|
|
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"`
|
2021-11-26 13:59:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2021-11-26 21:10:52 +01:00
|
|
|
// GeoJSON returns results as specified on https://geojson.org/.
|
|
|
|
func (photos GeoResults) GeoJSON() ([]byte, error) {
|
2021-11-26 13:59:10 +01:00
|
|
|
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,
|
2021-11-27 09:30:29 +01:00
|
|
|
"TakenAt": p.TakenAt,
|
2021-11-26 13:59:10 +01:00
|
|
|
"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()
|
|
|
|
}
|