Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
ca20be57ce
commit
06d8816a7f
14 changed files with 348 additions and 231 deletions
|
@ -43,12 +43,13 @@ type SearchPhotos struct {
|
|||
Private bool `form:"private" notes:"Finds private pictures"`
|
||||
Favorite string `form:"favorite" example:"favorite:yes" notes:"Finds favorites only"`
|
||||
Unsorted bool `form:"unsorted" notes:"Finds pictures not in an album"`
|
||||
Lat float32 `form:"lat" notes:"GPS Position (Latitude)"`
|
||||
Lng float32 `form:"lng" notes:"GPS Position (Longitude)"`
|
||||
Dist uint `form:"dist" example:"dist:5" notes:"Distance to GPS Position (km)"`
|
||||
Near string `form:"near" example:"near:pqbcf5j446s0futy" notes:"Finds nearby pictures (UID)"`
|
||||
S2 string `form:"s2" example:"s2:4799e370ca54c8b9" notes:"S2 Position (Cell ID)"`
|
||||
Olc string `form:"olc" example:"olc:8FWCHX7W+" notes:"OLC Position (Open Location Code)"`
|
||||
Lat float32 `form:"lat" example:"lat:41.894043" notes:"GPS Position (Latitude)"`
|
||||
Lng float32 `form:"lng" example:"lng:-87.62448" notes:"GPS Position (Longitude)"`
|
||||
Dist uint `form:"dist" example:"dist:50" notes:"Distance to Position (km)"`
|
||||
Latlng string `form:"latlng" notes:"GPS Bounding Box (Lat N, Lng E, Lat S, Lng W)"`
|
||||
S2 string `form:"s2" notes:"S2 Position (Cell ID)"`
|
||||
Olc string `form:"olc" notes:"Open Location Code (OLC)"`
|
||||
Fmin float32 `form:"fmin" notes:"F-number (min)"`
|
||||
Fmax float32 `form:"fmax" notes:"F-number (max)"`
|
||||
Chroma int16 `form:"chroma" example:"chroma:70" notes:"Chroma (0-100)"`
|
||||
|
|
|
@ -13,7 +13,6 @@ type SearchPhotosGeo struct {
|
|||
Filter string `form:"filter" serialize:"-" notes:"-"`
|
||||
ID string `form:"id" example:"id:123e4567-e89b-..." notes:"Finds pictures by Exif UID, XMP Document ID or Instance ID"`
|
||||
UID string `form:"uid" example:"uid:pqbcf5j446s0futy" notes:"Limits results to the specified internal unique IDs"`
|
||||
Near string `form:"near"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
Folder string `form:"folder"` // Alias for Path
|
||||
|
@ -42,12 +41,13 @@ type SearchPhotosGeo struct {
|
|||
Face string `form:"face" notes:"Face ID, yes, no, new, or kind"`
|
||||
Faces string `form:"faces"` // Find or exclude faces if detected.
|
||||
Subject string `form:"subject"`
|
||||
Lat float32 `form:"lat" notes:"GPS Position (Latitude)"`
|
||||
Lng float32 `form:"lng" notes:"GPS Position (Longitude)"`
|
||||
Dist uint `form:"dist" example:"dist:5" notes:"Distance to GPS Position (km)"`
|
||||
Near string `form:"near" example:"near:pqbcf5j446s0futy" notes:"Finds nearby pictures (UID)"`
|
||||
S2 string `form:"s2" example:"s2:4799e370ca54c8b9" notes:"S2 Position (Cell ID)"`
|
||||
Olc string `form:"olc" example:"olc:8FWCHX7W+" notes:"OLC Position (Open Location Code)"`
|
||||
Lat float32 `form:"lat" example:"lat:41.894043" notes:"GPS Position (Latitude)"`
|
||||
Lng float32 `form:"lng" example:"lng:-87.62448" notes:"GPS Position (Longitude)"`
|
||||
Dist uint `form:"dist" example:"dist:50" notes:"Distance to Position (km)"`
|
||||
Latlng string `form:"latlng" notes:"GPS Bounding Box (Lat N, Lng E, Lat S, Lng W)"`
|
||||
S2 string `form:"s2" notes:"S2 Position (Cell ID)"`
|
||||
Olc string `form:"olc" notes:"Open Location Code (OLC)"`
|
||||
Person string `form:"person"` // Alias for Subject
|
||||
Subjects string `form:"subjects"` // Text
|
||||
People string `form:"people"` // Alias for Subjects
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
var (
|
||||
ErrForbidden = i18n.Error(i18n.ErrForbidden)
|
||||
ErrBadRequest = i18n.Error(i18n.ErrBadRequest)
|
||||
ErrNotFound = i18n.Error(i18n.ErrNotFound)
|
||||
ErrBadSortOrder = fmt.Errorf("invalid sort order")
|
||||
ErrBadFilter = fmt.Errorf("invalid search filter")
|
||||
ErrInvalidId = fmt.Errorf("invalid ID specified")
|
||||
|
|
|
@ -58,8 +58,31 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
return PhotoResults{}, 0, ErrBadRequest
|
||||
}
|
||||
|
||||
// Size of S2 Cells.
|
||||
S2Levels := 7
|
||||
// Find photos near another?
|
||||
if txt.NotEmpty(f.Near) {
|
||||
photo := Photo{}
|
||||
|
||||
// Find a nearby picture using the UID or return an empty result otherwise.
|
||||
if err = Db().First(&photo, "photo_uid = ?", f.Near).Error; err != nil {
|
||||
log.Debugf("search: %s (find nearby)", err)
|
||||
return PhotoResults{}, 0, ErrNotFound
|
||||
}
|
||||
|
||||
// Set the S2 Cell ID to search for.
|
||||
f.S2 = photo.CellID
|
||||
|
||||
// Set the search distance if unspecified.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = 2
|
||||
}
|
||||
}
|
||||
|
||||
// Set default search distance.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = 50
|
||||
} else if f.Dist > 5000 {
|
||||
f.Dist = 5000
|
||||
}
|
||||
|
||||
// Specify table names and joins.
|
||||
s := UnscopedDb().Table(entity.File{}.TableName()).Select(resultCols).
|
||||
|
@ -94,6 +117,11 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
f.Filter = a.AlbumFilter
|
||||
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", a.AlbumUID)
|
||||
}
|
||||
|
||||
// Limit search distance.
|
||||
if f.Dist <= 0 || f.Dist > 50 {
|
||||
f.Dist = 50
|
||||
}
|
||||
} else {
|
||||
f.Scope = ""
|
||||
}
|
||||
|
@ -629,13 +657,13 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
}
|
||||
|
||||
// Filter by location code.
|
||||
if f.S2 != "" {
|
||||
if txt.NotEmpty(f.S2) {
|
||||
// S2 Cell ID.
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, S2Levels)
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, s2.Level(f.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
} else if f.Olc != "" {
|
||||
} else if txt.NotEmpty(f.Olc) {
|
||||
// Open Location Code (OLC).
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), S2Levels)
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), s2.Level(f.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
}
|
||||
|
||||
|
@ -646,22 +674,18 @@ func searchPhotos(f form.SearchPhotos, sess *entity.Session, resultCols string)
|
|||
}
|
||||
|
||||
// Filter by approx distance to coordinates.
|
||||
if f.Dist == 0 {
|
||||
f.Dist = 20
|
||||
} else if f.Dist > 5000 {
|
||||
f.Dist = 5000
|
||||
if f.Lat != 0 && f.Lat >= -90 && f.Lat <= 90 {
|
||||
// Latitude (from +90 to -90 degrees).
|
||||
latNorth := f.Lat + Radius*float32(f.Dist)
|
||||
latSouth := f.Lat - Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latSouth, latNorth)
|
||||
}
|
||||
|
||||
if f.Lat != 0 {
|
||||
latNorth := f.Lat - Radius*float32(f.Dist)
|
||||
latSouth := f.Lat + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latNorth, latSouth)
|
||||
}
|
||||
|
||||
if f.Lng != 0 {
|
||||
lngEast := f.Lng - Radius*float32(f.Dist)
|
||||
lngWest := f.Lng + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngEast, lngWest)
|
||||
if f.Lng != 0 && f.Lng >= -180 && f.Lng <= 180 {
|
||||
// Longitude (from -180 to +180 degrees).
|
||||
lngWest := f.Lng - Radius*float32(f.Dist)
|
||||
lngEast := f.Lng + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngWest, lngEast)
|
||||
}
|
||||
|
||||
// Find photos taken before date.
|
||||
|
|
|
@ -39,21 +39,30 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
return GeoResults{}, ErrBadRequest
|
||||
}
|
||||
|
||||
// Size of S2 Cells.
|
||||
S2Levels := 7
|
||||
|
||||
// Search for nearby photos.
|
||||
if f.Near != "" {
|
||||
// Find photos near another?
|
||||
if txt.NotEmpty(f.Near) {
|
||||
photo := Photo{}
|
||||
|
||||
// Find photo to get location.
|
||||
// Find a nearby picture using the UID or return an empty result otherwise.
|
||||
if err = Db().First(&photo, "photo_uid = ?", f.Near).Error; err != nil {
|
||||
return GeoResults{}, err
|
||||
log.Debugf("search: %s (find nearby)", err)
|
||||
return GeoResults{}, ErrNotFound
|
||||
}
|
||||
|
||||
// Set the S2 Cell ID to search for.
|
||||
f.S2 = photo.CellID
|
||||
|
||||
S2Levels = 12
|
||||
// Set the search distance if unspecified.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = 2
|
||||
}
|
||||
}
|
||||
|
||||
// Set default search distance.
|
||||
if f.Dist <= 0 {
|
||||
f.Dist = 50
|
||||
} else if f.Dist > 5000 {
|
||||
f.Dist = 5000
|
||||
}
|
||||
|
||||
// Specify table names and joins.
|
||||
|
@ -90,7 +99,10 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
s = s.Where("files.photo_uid NOT IN (SELECT photo_uid FROM photos_albums pa WHERE pa.hidden = 1 AND pa.album_uid = ?)", a.AlbumUID)
|
||||
}
|
||||
|
||||
S2Levels = 18
|
||||
// Limit search distance.
|
||||
if f.Dist <= 0 || f.Dist > 50 {
|
||||
f.Dist = 50
|
||||
}
|
||||
} else {
|
||||
f.Scope = ""
|
||||
}
|
||||
|
@ -510,13 +522,13 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
}
|
||||
|
||||
// Filter by location code.
|
||||
if f.S2 != "" {
|
||||
if txt.NotEmpty(f.S2) {
|
||||
// S2 Cell ID.
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, S2Levels)
|
||||
s2Min, s2Max := s2.PrefixedRange(f.S2, s2.Level(f.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
} else if f.Olc != "" {
|
||||
} else if txt.NotEmpty(f.Olc) {
|
||||
// Open Location Code (OLC).
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), S2Levels)
|
||||
s2Min, s2Max := s2.PrefixedRange(pluscode.S2(f.Olc), s2.Level(f.Dist))
|
||||
s = s.Where("photos.cell_id BETWEEN ? AND ?", s2Min, s2Max)
|
||||
}
|
||||
|
||||
|
@ -527,22 +539,18 @@ func UserPhotosGeo(f form.SearchPhotosGeo, sess *entity.Session) (results GeoRes
|
|||
}
|
||||
|
||||
// Filter by approx distance to coordinates.
|
||||
if f.Dist == 0 {
|
||||
f.Dist = 20
|
||||
} else if f.Dist > 5000 {
|
||||
f.Dist = 5000
|
||||
if f.Lat != 0 && f.Lat >= -90 && f.Lat <= 90 {
|
||||
// Latitude (from +90 to -90 degrees).
|
||||
latNorth := f.Lat + Radius*float32(f.Dist)
|
||||
latSouth := f.Lat - Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latSouth, latNorth)
|
||||
}
|
||||
|
||||
if f.Lat != 0 {
|
||||
latNorth := f.Lat - Radius*float32(f.Dist)
|
||||
latSouth := f.Lat + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lat BETWEEN ? AND ?", latNorth, latSouth)
|
||||
}
|
||||
|
||||
if f.Lng != 0 {
|
||||
lngEast := f.Lng - Radius*float32(f.Dist)
|
||||
lngWest := f.Lng + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngEast, lngWest)
|
||||
if f.Lng != 0 && f.Lng >= -180 && f.Lng <= 180 {
|
||||
// Longitude (from -180 to +180 degrees).
|
||||
lngWest := f.Lng - Radius*float32(f.Dist)
|
||||
lngEast := f.Lng + Radius*float32(f.Dist)
|
||||
s = s.Where("photos.photo_lng BETWEEN ? AND ?", lngWest, lngEast)
|
||||
}
|
||||
|
||||
// Find photos taken before date.
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestPhotosGeoFilterNear(t *testing.T) {
|
|||
f.Near = "%gold"
|
||||
_, err := PhotosGeo(f)
|
||||
|
||||
assert.Equal(t, err.Error(), "record not found")
|
||||
assert.Equal(t, err.Error(), "Not found")
|
||||
})
|
||||
t.Run("CenterPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -47,7 +47,7 @@ func TestPhotosGeoFilterNear(t *testing.T) {
|
|||
f.Near = "I love % dog"
|
||||
_, err := PhotosGeo(f)
|
||||
|
||||
assert.Equal(t, err.Error(), "record not found")
|
||||
assert.Equal(t, err.Error(), "Not found")
|
||||
})
|
||||
//TODO error
|
||||
/*t.Run("EndsWithPercent", func(t *testing.T) {
|
||||
|
|
|
@ -13,6 +13,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
var f form.SearchPhotosGeo
|
||||
|
||||
f.S2 = "1ef744d1e283"
|
||||
f.Dist = 2
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
||||
|
@ -25,6 +26,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
var f form.SearchPhotosGeo
|
||||
|
||||
f.S2 = "85d1ea7d382c"
|
||||
f.Dist = 2
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
||||
|
@ -37,25 +39,27 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
var f form.SearchPhotosGeo
|
||||
|
||||
f.S2 = "%gold"
|
||||
f.Dist = 2
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
||||
f.S2 = "I love % dog"
|
||||
f.Dist = 2
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -67,7 +71,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -79,7 +83,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -91,7 +95,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -103,7 +107,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -115,7 +119,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -128,7 +132,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -140,7 +144,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -152,7 +156,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -164,7 +168,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -176,7 +180,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -188,7 +192,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -201,7 +205,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -214,7 +218,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -226,7 +230,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -238,7 +242,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -250,7 +254,7 @@ func TestPhotosGeoFilterS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -259,13 +263,14 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
var f form.SearchPhotosGeo
|
||||
|
||||
f.Query = "s2:1ef744d1e283"
|
||||
f.Dist = 2
|
||||
|
||||
photos, err := PhotosGeo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 3)
|
||||
assert.Equal(t, 3, len(photos))
|
||||
})
|
||||
t.Run("s2:85d1ea7d382c", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -277,7 +282,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 8)
|
||||
assert.Equal(t, 8, len(photos))
|
||||
})
|
||||
t.Run("85d1ea7d382c pipe 1ef744d1e283", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -289,7 +294,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -301,7 +306,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -313,7 +318,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithPercent", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -325,7 +330,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -337,7 +342,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -349,7 +354,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithAmpersand", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -361,7 +366,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -373,7 +378,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -386,7 +391,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithSingleQuote", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -398,7 +403,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -410,7 +415,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -422,7 +427,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithAsterisk", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -434,7 +439,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -446,7 +451,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -459,7 +464,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithPipe", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -471,7 +476,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("StartsWithNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -483,7 +488,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("CenterNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -495,7 +500,7 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
t.Run("EndsWithNumber", func(t *testing.T) {
|
||||
var f form.SearchPhotosGeo
|
||||
|
@ -507,6 +512,6 @@ func TestPhotosGeoQueryS2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(photos), 0)
|
||||
assert.Equal(t, 0, len(photos))
|
||||
})
|
||||
}
|
||||
|
|
41
pkg/s2/level.go
Normal file
41
pkg/s2/level.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package s2
|
||||
|
||||
// DefaultLevel specifies the default S2 cell size.
|
||||
var DefaultLevel = 21
|
||||
|
||||
// Level returns the S2 cell level based on the approximate cell size in km.
|
||||
// see https://s2geometry.io/resources/s2cell_statistics.html
|
||||
func Level(km uint) (level int) {
|
||||
switch {
|
||||
case km >= 7842:
|
||||
return 0
|
||||
case km >= 3921:
|
||||
return 1
|
||||
case km >= 1825:
|
||||
return 2
|
||||
case km >= 1130:
|
||||
return 3
|
||||
case km >= 579:
|
||||
return 4
|
||||
case km >= 287:
|
||||
return 5
|
||||
case km >= 143:
|
||||
return 6
|
||||
case km >= 72:
|
||||
return 7
|
||||
case km >= 36:
|
||||
return 8
|
||||
case km >= 18:
|
||||
return 9
|
||||
case km >= 9:
|
||||
return 10
|
||||
case km >= 4:
|
||||
return 11
|
||||
case km >= 2:
|
||||
return 12
|
||||
case km >= 1:
|
||||
return 13
|
||||
default:
|
||||
return 14
|
||||
}
|
||||
}
|
35
pkg/s2/range.go
Normal file
35
pkg/s2/range.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package s2
|
||||
|
||||
import gs2 "github.com/golang/geo/s2"
|
||||
|
||||
// Range returns a token range to find nearby cells within the specified S2 level.
|
||||
func Range(token string, level int) (start, end string) {
|
||||
token = NormalizeToken(token)
|
||||
|
||||
cell := gs2.CellIDFromToken(token)
|
||||
|
||||
if !cell.IsValid() {
|
||||
return start, end
|
||||
}
|
||||
|
||||
// See https://s2geometry.io/resources/s2cell_statistics.html
|
||||
cellLevel := cell.Level()
|
||||
|
||||
// Range level must not be greater than the cell level.
|
||||
if level > cellLevel {
|
||||
level = cellLevel
|
||||
}
|
||||
|
||||
// Get parent cell ID for the given level.
|
||||
parentCell := cell.Parent(level)
|
||||
|
||||
// Return computed S2 cell token range.
|
||||
return parentCell.Prev().ChildBeginAtLevel(cellLevel).ToToken(), parentCell.Next().ChildBeginAtLevel(cellLevel).ToToken()
|
||||
}
|
||||
|
||||
// PrefixedRange returns a prefixed token range to find nearby cells within the specified S2 level.
|
||||
func PrefixedRange(token string, level int) (start, end string) {
|
||||
start, end = Range(token, level)
|
||||
|
||||
return Prefix(start), Prefix(end)
|
||||
}
|
73
pkg/s2/s2.go
73
pkg/s2/s2.go
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Package s2 encapsulates Google's S2 library.
|
||||
Package s2 provides a geolocation abstraction based on Google's S2 library.
|
||||
|
||||
See https://s2geometry.io/
|
||||
|
||||
|
@ -25,74 +25,3 @@ Additional information can be found in our Developer Guide:
|
|||
<https://docs.photoprism.app/developer-guide/>
|
||||
*/
|
||||
package s2
|
||||
|
||||
import (
|
||||
gs2 "github.com/golang/geo/s2"
|
||||
)
|
||||
|
||||
// DefaultLevel see https://s2geometry.io/resources/s2cell_statistics.html.
|
||||
var DefaultLevel = 21
|
||||
|
||||
// Token returns the S2 cell token for coordinates using the default level.
|
||||
func Token(lat, lng float64) string {
|
||||
return TokenLevel(lat, lng, DefaultLevel)
|
||||
}
|
||||
|
||||
// TokenLevel returns the S2 cell token for coordinates.
|
||||
func TokenLevel(lat, lng float64, level int) string {
|
||||
if lat == 0.0 && lng == 0.0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lat < -90 || lat > 90 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lng < -180 || lng > 180 {
|
||||
return ""
|
||||
}
|
||||
|
||||
l := gs2.LatLngFromDegrees(lat, lng)
|
||||
return gs2.CellIDFromLatLng(l).Parent(level).ToToken()
|
||||
}
|
||||
|
||||
// LatLng returns the coordinates for a S2 cell token.
|
||||
func LatLng(token string) (lat, lng float64) {
|
||||
token = NormalizeToken(token)
|
||||
|
||||
if len(token) < 3 {
|
||||
return 0.0, 0.0
|
||||
}
|
||||
|
||||
c := gs2.CellIDFromToken(token)
|
||||
|
||||
if !c.IsValid() {
|
||||
return 0.0, 0.0
|
||||
}
|
||||
|
||||
l := c.LatLng()
|
||||
return l.Lat.Degrees(), l.Lng.Degrees()
|
||||
}
|
||||
|
||||
// IsZero returns true if the coordinates are both empty.
|
||||
func IsZero(lat, lng float64) bool {
|
||||
return lat == 0.0 && lng == 0.0
|
||||
}
|
||||
|
||||
// Range returns a token range for finding nearby locations.
|
||||
func Range(token string, levelUp int) (min, max string) {
|
||||
token = NormalizeToken(token)
|
||||
|
||||
c := gs2.CellIDFromToken(token)
|
||||
|
||||
if !c.IsValid() {
|
||||
return min, max
|
||||
}
|
||||
|
||||
// See https://s2geometry.io/resources/s2cell_statistics.html
|
||||
lvl := c.Level()
|
||||
|
||||
parent := c.Parent(lvl - levelUp)
|
||||
|
||||
return parent.Prev().ChildBeginAtLevel(lvl).ToToken(), parent.Next().ChildBeginAtLevel(lvl).ToToken()
|
||||
}
|
||||
|
|
|
@ -102,19 +102,32 @@ func TestTokenLevel(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestLevel(t *testing.T) {
|
||||
t.Run("8000", func(t *testing.T) {
|
||||
assert.Equal(t, 0, Level(8000))
|
||||
})
|
||||
|
||||
t.Run("150", func(t *testing.T) {
|
||||
assert.Equal(t, 6, Level(150))
|
||||
})
|
||||
t.Run("0", func(t *testing.T) {
|
||||
assert.Equal(t, 14, Level(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLatLng(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
lat, lng := LatLng("4799e370ca54c8b9")
|
||||
assert.Equal(t, 48.56344835921243, lat)
|
||||
assert.Equal(t, 8.996878323369781, lng)
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
lat, lng := LatLng("4799e370ca5q")
|
||||
assert.Equal(t, 0.0, lat)
|
||||
assert.Equal(t, 0.0, lng)
|
||||
})
|
||||
t.Run("empty", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
lat, lng := LatLng("")
|
||||
assert.Equal(t, 0.0, lat)
|
||||
assert.Equal(t, 0.0, lng)
|
||||
|
@ -122,45 +135,60 @@ func TestLatLng(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIsZero(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
t.Run("Valid", func(t *testing.T) {
|
||||
lat, lng := LatLng("4799e370ca54c8b9")
|
||||
assert.False(t, IsZero(lat, lng))
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
lat, lng := LatLng("4799e370ca5q")
|
||||
assert.True(t, IsZero(lat, lng))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRange(t *testing.T) {
|
||||
t.Run("valid_1", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca54c8b9", 1)
|
||||
assert.Equal(t, "4799e370ca54c8b1", min)
|
||||
assert.Equal(t, "4799e370ca54c8c1", max)
|
||||
t.Run("Level1", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 1)
|
||||
assert.Equal(t, "3800000000000001", start)
|
||||
assert.Equal(t, "4800000000000001", end)
|
||||
})
|
||||
t.Run("valid_2", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca54c8b9", 2)
|
||||
assert.Equal(t, "4799e370ca54c881", min)
|
||||
assert.Equal(t, "4799e370ca54c8c1", max)
|
||||
t.Run("Level2", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 2)
|
||||
assert.Equal(t, "4400000000000001", start)
|
||||
assert.Equal(t, "4800000000000001", end)
|
||||
})
|
||||
t.Run("valid_3", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca54c8b9", 3)
|
||||
assert.Equal(t, "4799e370ca54c801", min)
|
||||
assert.Equal(t, "4799e370ca54c901", max)
|
||||
t.Run("Level5", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 5)
|
||||
assert.Equal(t, "4790000000000001", start)
|
||||
assert.Equal(t, "47a0000000000001", end)
|
||||
})
|
||||
t.Run("valid_4", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca54c8b9", 4)
|
||||
assert.Equal(t, "4799e370ca54c601", min)
|
||||
assert.Equal(t, "4799e370ca54ca01", max)
|
||||
t.Run("Level7", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 7)
|
||||
assert.Equal(t, "4799000000000001", start)
|
||||
assert.Equal(t, "479a000000000001", end)
|
||||
})
|
||||
t.Run("valid_5", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca54c8b9", 5)
|
||||
assert.Equal(t, "4799e370ca54c001", min)
|
||||
assert.Equal(t, "4799e370ca54d001", max)
|
||||
t.Run("Level10", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 10)
|
||||
assert.Equal(t, "4799e00000000001", start)
|
||||
assert.Equal(t, "4799e40000000001", end)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
min, max := Range("4799e370ca5q", 1)
|
||||
assert.Equal(t, "", min)
|
||||
assert.Equal(t, "", max)
|
||||
t.Run("Level14", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 14)
|
||||
assert.Equal(t, "4799e36e00000001", start)
|
||||
assert.Equal(t, "4799e37200000001", end)
|
||||
})
|
||||
t.Run("Level21", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 21)
|
||||
assert.Equal(t, "4799e370ca480001", start)
|
||||
assert.Equal(t, "4799e370ca580001", end)
|
||||
})
|
||||
t.Run("Level23", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca54c8b9", 23)
|
||||
assert.Equal(t, "4799e370ca540001", start)
|
||||
assert.Equal(t, "4799e370ca550001", end)
|
||||
})
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
start, end := Range("4799e370ca5q", 1)
|
||||
assert.Equal(t, "", start)
|
||||
assert.Equal(t, "", end)
|
||||
})
|
||||
}
|
||||
|
|
52
pkg/s2/token.go
Normal file
52
pkg/s2/token.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package s2
|
||||
|
||||
import (
|
||||
gs2 "github.com/golang/geo/s2"
|
||||
)
|
||||
|
||||
// IsZero returns true if the coordinates are both empty.
|
||||
func IsZero(lat, lng float64) bool {
|
||||
return lat == 0.0 && lng == 0.0
|
||||
}
|
||||
|
||||
// Token returns the S2 cell token for coordinates using the default level.
|
||||
func Token(lat, lng float64) string {
|
||||
return TokenLevel(lat, lng, DefaultLevel)
|
||||
}
|
||||
|
||||
// TokenLevel returns the S2 cell token for coordinates.
|
||||
func TokenLevel(lat, lng float64, level int) string {
|
||||
if lat == 0.0 && lng == 0.0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lat < -90 || lat > 90 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lng < -180 || lng > 180 {
|
||||
return ""
|
||||
}
|
||||
|
||||
l := gs2.LatLngFromDegrees(lat, lng)
|
||||
return gs2.CellIDFromLatLng(l).Parent(level).ToToken()
|
||||
}
|
||||
|
||||
// LatLng returns the coordinates for a S2 cell token.
|
||||
func LatLng(token string) (lat, lng float64) {
|
||||
token = NormalizeToken(token)
|
||||
|
||||
if len(token) < 3 {
|
||||
return 0.0, 0.0
|
||||
}
|
||||
|
||||
cell := gs2.CellIDFromToken(token)
|
||||
|
||||
if !cell.IsValid() {
|
||||
return 0.0, 0.0
|
||||
}
|
||||
|
||||
l := cell.LatLng()
|
||||
|
||||
return l.Lat.Degrees(), l.Lng.Degrees()
|
||||
}
|
|
@ -35,10 +35,3 @@ func Prefix(token string) string {
|
|||
func PrefixedToken(lat, lng float64) string {
|
||||
return Prefix(Token(lat, lng))
|
||||
}
|
||||
|
||||
// PrefixedRange returns a token range for finding nearby locations.
|
||||
func PrefixedRange(token string, levelUp int) (min, max string) {
|
||||
min, max = Range(token, levelUp)
|
||||
|
||||
return Prefix(min), Prefix(max)
|
||||
}
|
|
@ -78,34 +78,34 @@ func TestPrefixedToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPrefixedRange(t *testing.T) {
|
||||
t.Run("valid_1", func(t *testing.T) {
|
||||
min, max := PrefixedRange("4799e370ca54c8b9", 1)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c8b1", min)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c8c1", max)
|
||||
t.Run("Level1", func(t *testing.T) {
|
||||
start, end := PrefixedRange("4799e370ca54c8b9", 1)
|
||||
assert.Equal(t, TokenPrefix+"3800000000000001", start)
|
||||
assert.Equal(t, TokenPrefix+"4800000000000001", end)
|
||||
})
|
||||
t.Run("valid_2", func(t *testing.T) {
|
||||
min, max := PrefixedRange(TokenPrefix+"4799e370ca54c8b9", 2)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c881", min)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c8c1", max)
|
||||
t.Run("Level2", func(t *testing.T) {
|
||||
start, end := PrefixedRange(TokenPrefix+"4799e370ca54c8b9", 2)
|
||||
assert.Equal(t, TokenPrefix+"4400000000000001", start)
|
||||
assert.Equal(t, TokenPrefix+"4800000000000001", end)
|
||||
})
|
||||
t.Run("valid_3", func(t *testing.T) {
|
||||
min, max := PrefixedRange("4799e370ca54c8b9", 3)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c801", min)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c901", max)
|
||||
t.Run("Level3", func(t *testing.T) {
|
||||
start, end := PrefixedRange("4799e370ca54c8b9", 3)
|
||||
assert.Equal(t, TokenPrefix+"4700000000000001", start)
|
||||
assert.Equal(t, TokenPrefix+"4800000000000001", end)
|
||||
})
|
||||
t.Run("valid_4", func(t *testing.T) {
|
||||
min, max := PrefixedRange(TokenPrefix+"4799e370ca54c8b9", 4)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c601", min)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54ca01", max)
|
||||
t.Run("Level4", func(t *testing.T) {
|
||||
start, end := PrefixedRange(TokenPrefix+"4799e370ca54c8b9", 4)
|
||||
assert.Equal(t, TokenPrefix+"4760000000000001", start)
|
||||
assert.Equal(t, TokenPrefix+"47a0000000000001", end)
|
||||
})
|
||||
t.Run("valid_5", func(t *testing.T) {
|
||||
min, max := PrefixedRange("4799e370ca54c8b9", 5)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54c001", min)
|
||||
assert.Equal(t, TokenPrefix+"4799e370ca54d001", max)
|
||||
t.Run("Level5", func(t *testing.T) {
|
||||
start, end := PrefixedRange("4799e370ca54c8b9", 5)
|
||||
assert.Equal(t, TokenPrefix+"4790000000000001", start)
|
||||
assert.Equal(t, TokenPrefix+"47a0000000000001", end)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
min, max := PrefixedRange("4799e370ca5q", 1)
|
||||
assert.Equal(t, "", min)
|
||||
assert.Equal(t, "", max)
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
start, end := PrefixedRange("4799e370ca5q", 1)
|
||||
assert.Equal(t, "", start)
|
||||
assert.Equal(t, "", end)
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue