mirror of https://github.com/perkeep/perkeep.git
search: GPS location search
Like loc:hawaii or loc:USA or loc:94128, etc. Change-Id: I11f47bf464a812f0b62e7799752811144bb7454e
This commit is contained in:
parent
d759e12b86
commit
7238bd1652
|
@ -18,10 +18,14 @@ package search
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"camlistore.org/pkg/context"
|
||||
"camlistore.org/pkg/geocode"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,7 +41,7 @@ var (
|
|||
// near:portland") and returns a SearchQuery for that search text. The
|
||||
// Constraint field will always be set. The Limit and Sort may also be
|
||||
// set.
|
||||
func parseExpression(exp string) (*SearchQuery, error) {
|
||||
func parseExpression(ctx *context.Context, exp string) (*SearchQuery, error) {
|
||||
base := &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
SkipHidden: true,
|
||||
|
@ -62,13 +66,25 @@ func parseExpression(exp string) (*SearchQuery, error) {
|
|||
},
|
||||
}
|
||||
}
|
||||
andFile := func(fc *FileConstraint) {
|
||||
and(&Constraint{
|
||||
permOfFile := func(fc *FileConstraint) *Constraint {
|
||||
return &Constraint{
|
||||
Permanode: &PermanodeConstraint{
|
||||
Attr: "camliContent",
|
||||
ValueInSet: &Constraint{File: fc},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
orConst := func(a, b *Constraint) *Constraint {
|
||||
return &Constraint{
|
||||
Logical: &LogicalConstraint{
|
||||
Op: "or",
|
||||
A: a,
|
||||
B: b,
|
||||
},
|
||||
}
|
||||
}
|
||||
andFile := func(fc *FileConstraint) {
|
||||
and(permOfFile(fc))
|
||||
}
|
||||
andWHRatio := func(fc *FloatConstraint) {
|
||||
andFile(&FileConstraint{
|
||||
|
@ -134,6 +150,36 @@ func parseExpression(exp string) (*SearchQuery, error) {
|
|||
Height: whIntConstraint(m[1], m[2]),
|
||||
})
|
||||
}
|
||||
if strings.HasPrefix(word, "loc:") {
|
||||
where := strings.TrimPrefix(word, "loc:")
|
||||
rects, err := geocode.Lookup(ctx, where)
|
||||
log.Printf("Geocode lookup for %q: %#v", where, rects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rects) == 0 {
|
||||
return nil, fmt.Errorf("No location found for %q", where)
|
||||
}
|
||||
var locConstraint *Constraint
|
||||
for i, rect := range rects {
|
||||
rectConstraint := permOfFile(&FileConstraint{
|
||||
IsImage: true,
|
||||
Location: &LocationConstraint{
|
||||
Left: rect.SouthWest.Long,
|
||||
Right: rect.NorthEast.Long,
|
||||
Top: rect.NorthEast.Lat,
|
||||
Bottom: rect.SouthWest.Lat,
|
||||
},
|
||||
})
|
||||
if i == 0 {
|
||||
locConstraint = rectConstraint
|
||||
} else {
|
||||
locConstraint = orConst(locConstraint, rectConstraint)
|
||||
}
|
||||
}
|
||||
and(locConstraint)
|
||||
continue
|
||||
}
|
||||
log.Printf("Unknown search expression word %q", word)
|
||||
// TODO: finish. better tokenization. non-operator tokens
|
||||
// are text searches, etc.
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"camlistore.org/pkg/context"
|
||||
)
|
||||
|
||||
var parseExprTests = []struct {
|
||||
|
@ -147,7 +149,7 @@ func TestParseExpression(t *testing.T) {
|
|||
ins = []string{tt.in}
|
||||
}
|
||||
for _, in := range ins {
|
||||
got, err := parseExpression(in)
|
||||
got, err := parseExpression(context.TODO(), in)
|
||||
if err != nil {
|
||||
if tt.errContains != "" && strings.Contains(err.Error(), tt.errContains) {
|
||||
continue
|
||||
|
|
|
@ -177,7 +177,7 @@ func (q *SearchQuery) addContinueConstraint() error {
|
|||
return errors.New("token not valid for query type")
|
||||
}
|
||||
|
||||
func (q *SearchQuery) checkValid() (sq *SearchQuery, err error) {
|
||||
func (q *SearchQuery) checkValid(ctx *context.Context) (sq *SearchQuery, err error) {
|
||||
if q.Limit < 0 {
|
||||
return nil, errors.New("negative limit")
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ func (q *SearchQuery) checkValid() (sq *SearchQuery, err error) {
|
|||
}
|
||||
if q.Constraint == nil {
|
||||
if expr := q.Expression; expr != "" {
|
||||
sq, err := parseExpression(expr)
|
||||
sq, err := parseExpression(ctx, expr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing search expression %q: %v", expr, err)
|
||||
}
|
||||
|
@ -294,11 +294,12 @@ type FileConstraint struct {
|
|||
ModTime *TimeConstraint
|
||||
|
||||
// For images:
|
||||
IsImage bool `json:"isImage,omitempty"`
|
||||
EXIF *EXIFConstraint `json:"exif,omitempty"`
|
||||
Width *IntConstraint `json:"width,omitempty"`
|
||||
Height *IntConstraint `json:"height,omitempty"`
|
||||
WHRatio *FloatConstraint `json:"widthHeightRation,omitempty"`
|
||||
IsImage bool `json:"isImage,omitempty"`
|
||||
EXIF *EXIFConstraint `json:"exif,omitempty"` // TODO: implement
|
||||
Width *IntConstraint `json:"width,omitempty"`
|
||||
Height *IntConstraint `json:"height,omitempty"`
|
||||
WHRatio *FloatConstraint `json:"widthHeightRation,omitempty"`
|
||||
Location *LocationConstraint `json:"location,omitempty"`
|
||||
}
|
||||
|
||||
type DirConstraint struct {
|
||||
|
@ -403,6 +404,17 @@ type EXIFConstraint struct {
|
|||
// ISO, Aperature, Camera Make/Model, etc.
|
||||
}
|
||||
|
||||
type LocationConstraint struct {
|
||||
Top float64
|
||||
Left float64
|
||||
Right float64
|
||||
Bottom float64
|
||||
}
|
||||
|
||||
func (c *LocationConstraint) matchesLatLong(lat, long float64) bool {
|
||||
return c.Left <= long && long <= c.Right && c.Bottom <= lat && lat <= c.Top
|
||||
}
|
||||
|
||||
// A StringConstraint specifies constraints on a string.
|
||||
// All non-zero must match.
|
||||
type StringConstraint struct {
|
||||
|
@ -585,7 +597,8 @@ func optimizePlan(c *Constraint) *Constraint {
|
|||
}
|
||||
|
||||
func (h *Handler) Query(rawq *SearchQuery) (*SearchResult, error) {
|
||||
exprResult, err := rawq.checkValid()
|
||||
ctx := context.TODO() // TODO: set from rawq
|
||||
exprResult, err := rawq.checkValid(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid SearchQuery: %v", err)
|
||||
}
|
||||
|
@ -1084,9 +1097,9 @@ func (c *FileConstraint) blobMatches(s *search, br blob.Ref, bm camtypes.BlobMet
|
|||
return false, nil
|
||||
}
|
||||
}
|
||||
corpus := s.h.corpus
|
||||
var width, height int64
|
||||
if c.Width != nil || c.Height != nil || c.WHRatio != nil {
|
||||
corpus := s.h.corpus
|
||||
if corpus == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -1106,6 +1119,15 @@ func (c *FileConstraint) blobMatches(s *search, br blob.Ref, bm camtypes.BlobMet
|
|||
if c.WHRatio != nil && !c.WHRatio.floatMatches(float64(width)/float64(height)) {
|
||||
return false, nil
|
||||
}
|
||||
if c.Location != nil {
|
||||
if corpus == nil {
|
||||
return false, nil
|
||||
}
|
||||
lat, long, ok := corpus.FileLatLongLocked(br)
|
||||
if !ok || !c.Location.matchesLatLong(lat, long) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
// TOOD: EXIF timeconstraint
|
||||
return true, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue