diff --git a/geo/parse.go b/geo/parse.go index 7725da0a0..87ac091bc 100644 --- a/geo/parse.go +++ b/geo/parse.go @@ -229,7 +229,10 @@ func extract2DCoordinates(thing interface{}) [][]float64 { for j := 0; j < thingVal.Len(); j++ { edges := thingVal.Index(j).Interface() if es, ok := edges.([]interface{}); ok { - rv = append(rv, extractCoordinates(es)) + v := extractCoordinates(es) + if len(v) == 2 { + rv = append(rv, v) + } } } @@ -272,29 +275,38 @@ func extract4DCoordinates(thing interface{}) (rv [][][][]float64) { return rv } -func extractGeoShape(thing interface{}) ([][][][]float64, string, bool) { +func ParseGeoShapeField(thing interface{}) (interface{}, string, error) { thingVal := reflect.ValueOf(thing) if !thingVal.IsValid() { - return nil, "", false + return nil, "", nil } + var shape string var coordValue interface{} - var typ string + if thingVal.Kind() == reflect.Map { iter := thingVal.MapRange() for iter.Next() { if iter.Key().String() == "type" { - typ = iter.Value().Interface().(string) - // make the shape type case insensitive. - typ = strings.ToLower(typ) + shape = iter.Value().Interface().(string) continue } + if iter.Key().String() == "coordinates" { coordValue = iter.Value().Interface() } } } + return coordValue, strings.ToLower(shape), nil +} + +func extractGeoShape(thing interface{}) ([][][][]float64, string, bool) { + coordValue, typ, err := ParseGeoShapeField(thing) + if err != nil { + return nil, "", false + } + return ExtractGeoShapeCoordinates(coordValue, typ) } @@ -383,23 +395,68 @@ func ExtractGeoShapeCoordinates(coordValue interface{}, typ string) ([][][][]float64, string, bool) { var rv [][][][]float64 if typ == PointType { - rv = [][][][]float64{{{extractCoordinates(coordValue)}}} + point := extractCoordinates(coordValue) + + // ignore the contents with invalid entry. + if len(point) < 2 { + return nil, typ, false + } + + rv = [][][][]float64{{{point}}} return rv, typ, true } if typ == MultiPointType || typ == LineStringType || typ == EnvelopeType { - rv = [][][][]float64{{extract2DCoordinates(coordValue)}} + coords := extract2DCoordinates(coordValue) + + // ignore the contents with invalid entry. + if len(coords) == 0 { + return nil, typ, false + } + + if typ == EnvelopeType && len(coords) != 2 { + return nil, typ, false + } + + if typ == LineStringType && len(coords) < 2 { + return nil, typ, false + } + + rv = [][][][]float64{{coords}} return rv, typ, true } if typ == PolygonType || typ == MultiLineStringType { - rv = [][][][]float64{extract3DCoordinates(coordValue)} + coords := extract3DCoordinates(coordValue) + + // ignore the contents with invalid entry. + if len(coords) == 0 { + return nil, typ, false + } + + if typ == PolygonType && len(coords[0]) < 3 || + typ == MultiLineStringType && len(coords[0]) < 2 { + return nil, typ, false + } + + rv = [][][][]float64{coords} return rv, typ, true } if typ == MultiPolygonType { rv = extract4DCoordinates(coordValue) + + // ignore the contents with invalid entry. + if len(rv) == 0 || len(rv[0]) == 0 { + return nil, typ, false + + } + + if len(rv[0][0]) < 3 { + return nil, typ, false + } + return rv, typ, true } diff --git a/geo/parse_test.go b/geo/parse_test.go index 1b46d8c81..56145564c 100644 --- a/geo/parse_test.go +++ b/geo/parse_test.go @@ -14,7 +14,10 @@ package geo -import "testing" +import ( + "reflect" + "testing" +) func TestExtractGeoPoint(t *testing.T) { @@ -199,3 +202,154 @@ func (s *s12) Lng() float64 { func (s *s12) Lat() float64 { return s.lat } + +func TestExtractGeoShape(t *testing.T) { + tests := []struct { + in interface{} + resTyp string + result [][][][]float64 + success bool + }{ + // valid point slice + { + in: map[string]interface{}{ + "coordinates": []interface{}{3.4, 5.9}, + "type": "Point", + }, + resTyp: "point", + result: [][][][]float64{{{{3.4, 5.9}}}}, + success: true, + }, + // invalid point slice + { + in: map[string]interface{}{ + "coordinates": []interface{}{3.4}, + "type": "point"}, + + resTyp: "point", + result: nil, + success: false, + }, + // valid multipoint slice containing single point + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 5.9}}, + "type": "multipoint"}, + resTyp: "multipoint", + result: [][][][]float64{{{{3.4, 5.9}}}}, + success: true, + }, + // valid multipoint slice + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 5.9}, {6.7, 9.8}}, + "type": "multipoint"}, + resTyp: "multipoint", + result: [][][][]float64{{{{3.4, 5.9}, {6.7, 9.8}}}}, + success: true, + }, + // valid multipoint slice containing one invalid entry + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 5.9}, {6.7}}, + "type": "multipoint"}, + resTyp: "multipoint", + result: [][][][]float64{{{{3.4, 5.9}}}}, + success: true, + }, + // invalid multipoint slice + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4}}, + "type": "multipoint"}, + resTyp: "multipoint", + result: nil, + success: false, + }, + // valid linestring slice + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}}, + "type": "linestring"}, + resTyp: "linestring", + result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}}}}, + success: true, + }, + // valid linestring slice + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}, {10.1, 12.3}}, + "type": "linestring"}, + resTyp: "linestring", + result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}, {10.1, 12.3}}}}, + success: true, + }, + // invalid linestring slice with single entry + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}}, + "type": "linestring"}, + resTyp: "linestring", + result: nil, + success: false, + }, + // invalid linestring slice with wrong paranthesis + { + in: map[string]interface{}{ + "coordinates": [][][]interface{}{{{3.4, 4.4}, {8.4, 9.4}}}, + "type": "linestring"}, + resTyp: "linestring", + result: nil, + success: false, + }, + // valid envelope + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}, {8.4, 9.4}}, + "type": "envelope"}, + resTyp: "envelope", + result: [][][][]float64{{{{3.4, 4.4}, {8.4, 9.4}}}}, + success: true, + }, + // invalid envelope + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}}, + "type": "envelope"}, + resTyp: "envelope", + result: nil, + success: false, + }, + // invalid envelope + { + in: map[string]interface{}{ + "coordinates": [][][]interface{}{{{3.4, 4.4}, {8.4, 9.4}}}, + "type": "envelope"}, + resTyp: "envelope", + result: nil, + success: false, + }, + // invalid envelope with >2 vertices + { + in: map[string]interface{}{ + "coordinates": [][]interface{}{{3.4, 4.4}, {5.6, 6.4}, {7.4, 7.4}}, + "type": "envelope"}, + resTyp: "envelope", + result: nil, + success: false, + }, + } + + for _, test := range tests { + result, shapeType, success := extractGeoShape(test.in) + if success != test.success { + t.Errorf("expected extract geo point: %t, got: %t for: %v", test.success, success, test.in) + } + if shapeType != test.resTyp { + t.Errorf("expected shape type: %v, got: %v for input: %v", test.resTyp, shapeType, test.in) + } + if !reflect.DeepEqual(test.result, result) { + t.Errorf("expected result %+v, got %+v for %v", test.result, result, test.in) + } + } +} diff --git a/mapping/field.go b/mapping/field.go index 962696ebd..7a7fc82f5 100644 --- a/mapping/field.go +++ b/mapping/field.go @@ -18,8 +18,6 @@ import ( "encoding/json" "fmt" "net" - "reflect" - "strings" "time" "github.com/blevesearch/bleve/v2/analysis/analyzer/keyword" @@ -315,36 +313,9 @@ func (fm *FieldMapping) processIP(ip net.IP, pathString string, path []string, i } } -func parseGeoShapeField(thing interface{}) (interface{}, string, error) { - thingVal := reflect.ValueOf(thing) - if !thingVal.IsValid() { - return nil, "", nil - } - - var shape string - var coordValue interface{} - - if thingVal.Kind() == reflect.Map { - iter := thingVal.MapRange() - for iter.Next() { - if iter.Key().String() == "type" { - shape = iter.Value().Interface().(string) - continue - - } - - if iter.Key().String() == "coordinates" { - coordValue = iter.Value().Interface() - } - } - } - - return coordValue, strings.ToLower(shape), nil -} - func (fm *FieldMapping) processGeoShape(propertyMightBeGeoShape interface{}, pathString string, path []string, indexes []uint64, context *walkContext) { - coordValue, shape, err := parseGeoShapeField(propertyMightBeGeoShape) + coordValue, shape, err := geo.ParseGeoShapeField(propertyMightBeGeoShape) if err != nil { return }