Repository: blevesearch/bleve
Pull request #1673 (2022-04-07 06:08:24)

Updating the geo readme.


Patch:
diff --git a/geo/README.md b/geo/README.md
index 43bcd98f..6112ff5d 100644
--- a/geo/README.md
+++ b/geo/README.md
@@ -1,5 +1,273 @@
 # geo support in bleve
 
+Latest bleve spatial capabilities are powered by spatial hierarchical tokens generated from s2geometry.
+You can find more details about the [s2geometry basics here](http://s2geometry.io/), and explore the 
+extended functionality of our forked golang port of [s2geometry lib here](https://github.com/blevesearch/geo).
+
+Users can continue to index and query `geopoint` field type and the existing queries like,
+
+- Point Distance
+- Bounded Rectangle
+- Bounded Polygon
+
+as before.
+
+## New Spatial Field Type - geoshape
+
+We have introduced a field type (`geoshape`) for representing the new spatial types. 
+
+Using the new `geoshape` field type, users can unblock the spatial capabilities  
+for the [geojson](https://datatracker.ietf.org/doc/html/rfc7946) shapes like,
+
+- Point
+- LineString
+- Polygon
+- MultiPoint
+- MultiLineString
+- MultiPolygon
+- GeometryCollection
+
+In addition to these shapes, bleve will also support additional shapes like,
+
+- Circle
+- Envelope (Bounded box)
+
+To specify GeoJSON data, use a nested field with:
+
+- a field named type that specifies the GeoJSON object type and the type value will be case-insensitive.
+- a field named coordinates that specifies the object's coordinates.
+
+```
+         "fieldName": { 
+              "type": "GeoJSON Type", 
+              "coordinates": <coordinates> 
+           }
+```
+
+- If specifying latitude and longitude coordinates, list the longitude first and then latitude.
+- Valid longitude values are between -180 and 180, both inclusive.
+- Valid latitude values are between -90 and 90, both inclusive.
+- Shapes would be internally represented as geodesics.
+- The GeoJSON specification strongly suggests splitting geometries so that neither of their parts crosses the antimeridian.
+
+
+Examples for the various geojson shapes representations are as below.
+
+## Point
+
+The following specifies a [Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) field in a document:
+
+```
+    { 
+    "type": "point", 
+    "coordinates": [75.05687713623047,22.53539059204079]
+    }
+```
+
+## Linestring
+
+The following specifies a [Linestring](https://tools.ietf.org/html/rfc7946#section-3.1.4) field in a document:
+
+
+```
+{ 
+    "type": "linestring", 
+    "coordinates": [ 
+    [ 77.01416015625, 23.0797317624497], 
+    [ 78.134765625, 20.385825381874263] 
+    ] 
+}
+```
+
+
+## Polygon
+
+The following specifies a [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) field in a document:
+
+```
+{
+ "type": "polygon",
+ "coordinates": [ [ [ 85.605, 57.207], 
+                    [ 86.396, 55.998], 
+                    [ 87.033, 56.716], 
+                    [ 85.605, 57.207] 
+                ] ]
+}
+```
+
+
+The first and last coordinates must match in order to close the polygon. 
+And the exterior coordinates have to be in Counter Clockwise Order in a polygon. (CCW)
+
+
+## MultiPoint
+
+The following specifies a [Multipoint](https://tools.ietf.org/html/rfc7946#section-3.1.3) field in a document:
+
+```
+{
+ "type": "multipoint",
+ "coordinates": [
+    [ -115.8343505859375, 38.45789034424927],
+    [ -115.81237792968749, 38.19502155795575],
+    [ -120.80017089843749, 36.54053616262899],
+    [ -120.67932128906249, 36.33725319397006]
+ ]
+}
+```
+
+## MultiLineString
+
+The following specifies a [MultiLineString](https://tools.ietf.org/html/rfc7946#section-3.1.5) field in a document:
+
+```
+{
+ "type": "multilinestring",
+ "coordinates": [
+    [ [ -118.31726074, 35.250105158],[ -117.509765624, 35.3756141] ],
+    [ [ -118.6962890, 34.624167789],[ -118.317260742, 35.03899204] ],
+    [ [ -117.9492187, 35.146862906], [ -117.6745605, 34.41144164] ]
+]
+}
+```
+
+## MultiPolygon
+
+The following specifies a [MultiPolygon](https://tools.ietf.org/html/rfc7946#section-3.1.7) field in a document:
+
+```
+{
+ "type": "multipolygon",
+ "coordinates": [
+    [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], 
+        [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ], 
+        [ -73.958, 40.8003 ] ] ],
+
+
+    [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], 
+        [ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ]
+ ]
+}
+```
+
+
+## GeometryCollection
+
+The following specifies a [GeometryCollection](https://tools.ietf.org/html/rfc7946#section-3.1.8) field in a document:
+
+```
+{
+ "type": "geometrycollection",
+ "geometries": [
+    {
+      "type": "multipoint",
+      "coordinates": [
+         [ -73.9580, 40.8003 ],
+         [ -73.9498, 40.7968 ],
+         [ -73.9737, 40.7648 ],
+         [ -73.9814, 40.7681 ]
+      ]
+    },
+    {
+      "type": "multilinestring",
+      "coordinates": [
+         [ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ],
+         [ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ],
+         [ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ],
+         [ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ]
+      ]
+    },
+    {
+      "type" : "polygon",
+      "coordinates" : [
+    [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0 ] ],
+    [ [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 2 ] , [ 2 , 2 ] ]
+    ]
+   }
+]
+}
+```
+
+
+## Circle
+
+If the user wishes to cover a circular region over the earth’s surface, then they could use this shape.
+A  sample circular shape is as below.
+
+```
+{ 
+ "type": "circle", 
+ "coordinates": [75.05687713623047,22.53539059204079],
+ "radius": "1000m"
+}
+```
+
+ 
+Circle is specified over the center point coordinates along with the radius.
+Example formats supported for radius are: 
+"5in" , "5inch" , "7yd" , "7yards",  "9ft" , "9feet", "11km", "11kilometers", "3nm" 
+"3nauticalmiles", "13mm" , "13millimeters",  "15cm", "15centimeters", "17mi", "17miles" "19m" or "19meters".
+
+If the unit cannot be determined, the entire string is parsed and the unit of meters is assumed.
+
+
+## Envelope
+
+Envelope type, which consists of coordinates for upper left and lower right points of the shape 
+to represent a bounding rectangle in the format  [[minLon, maxLat], [maxLon, minLat]].
+
+```
+{
+    "type": "envelope",
+    "coordinates": [
+      [72.83, 18.979],
+      [78.508,17.4555]
+    ]
+}
+```
+
+
+## GeoShape Query
+
+Geoshape query support three types/filters of spatial querying capability across those 
+heterogeneous types of documents indexed.
+
+### Query Structure:
+
+```
+{
+  "query": {
+    "geometry": {
+      "shape": {
+        "type": "<shapeType>", 
+        "coordinates": [[[ ]]]
+      },
+      "relation": "<<filterName>>"
+    }
+  }
+}
+```
+
+
+*shapeType* => can be any of the aforementioned types like Point, LineString, Polygon, MultiPoint,
+Geometrycollection, MultiLineString, MultiPolygon, Circle and Envelope.
+
+*filterName* => can be any of the 3 types like *intersects*, *contains* and *within*.
+
+### Relation
+
+| 	FilterName	 |  Description	                                                            | 
+| 	:-----------:| 	:-----------------------------------------------------------------:     | 
+| 	`intersects` | 	Return all documents whose shape field intersects the query  geometry.	| 
+| 	`contains`	 | 	Return all documents whose shape field contains the query geometry   	| 
+| 	`within`     | 	Return all documents whose shape field is within the query geometry.	| 
+
+------------------------------------------------------------------------------------------------------------------------
+
+
+
+### Older Implementation
+
 First, all of this geo code is a Go adaptation of the [Lucene 5.3.2 sandbox geo support](https://lucene.apache.org/core/5_3_2/sandbox/org/apache/lucene/util/package-summary.html).
 
 ## Notes
diff --git a/search/query/geo_shape.go b/search/query/geo_shape.go
index 47c8a1d6..1fa7b13b 100644
--- a/search/query/geo_shape.go
+++ b/search/query/geo_shape.go
@@ -35,13 +35,54 @@ type GeoShapeQuery struct {
 	BoostVal *Boost   `json:"boost,omitempty"`
 }
 
-func NewGeoShapeQuery(coordinates [][][][]float64, typ, relation string) *GeoShapeQuery {
+// NewGeoShapeQuery creates a geoshape query for the
+// given shape type. This method can be used for
+// creating geoshape queries for shape types like: point,
+// linestring, polygon, multipoint, multilinestring,
+// multipolygon and envelope.
+func NewGeoShapeQuery(coordinates [][][][]float64, typ,
+	relation string) (*GeoShapeQuery, error) {
 	s, _, err := geo.NewGeoJsonShape(coordinates, typ)
 	if err != nil {
-		return nil
+		return nil, err
 	}
 
-	return &GeoShapeQuery{Geometry: Geometry{Shape: s, Relation: relation}}
+	return &GeoShapeQuery{Geometry: Geometry{Shape: s,
+		Relation: relation}}, nil
+}
+
+// NewGeoShapeCircleQuery creates a geoshape query for the
+// given center point and the radius. Radius formats supported:
+// "5in" "5inch" "7yd" "7yards" "9ft" "9feet" "11km" "11kilometers"
+// "3nm" "3nauticalmiles" "13mm" "13millimeters" "15cm" "15centimeters"
+// "17mi" "17miles" "19m" "19meters" If the unit cannot be determined,
+// the entire string is parsed and the unit of meters is assumed.
+func NewGeoShapeCircleQuery(coordinates []float64, radius,
+	relation string) (*GeoShapeQuery, error) {
+	radiusInMeters, err := geo.ParseDistance(radius)
+	if err != nil {
+		return nil, err
+	}
+	s, _, err := geo.NewGeoCircleShape(coordinates, radiusInMeters)
+	if err != nil {
+		return nil, err
+	}
+
+	return &GeoShapeQuery{Geometry: Geometry{Shape: s,
+		Relation: relation}}, nil
+}
+
+// NewGeometryCollectionQuery creates a geoshape query for the
+// given geometrycollection coordinates and types.
+func NewGeometryCollectionQuery(coordinates [][][][][]float64, types []string,
+	relation string) (*GeoShapeQuery, error) {
+	s, _, err := geo.NewGeometryCollection(coordinates, types)
+	if err != nil {
+		return nil, err
+	}
+
+	return &GeoShapeQuery{Geometry: Geometry{Shape: s,
+		Relation: relation}}, nil
 }
 
 func (q *GeoShapeQuery) SetBoost(b float64) {
@@ -68,7 +109,8 @@ func (q *GeoShapeQuery) Searcher(i index.IndexReader,
 		field = m.DefaultSearchField()
 	}
 
-	return searcher.NewGeoShapeSearcher(i, q.Geometry.Shape, q.Geometry.Relation, field, q.BoostVal.Value(), options)
+	return searcher.NewGeoShapeSearcher(i, q.Geometry.Shape, q.Geometry.Relation, field,
+		q.BoostVal.Value(), options)
 }
 
 func (q *GeoShapeQuery) Validate() error {


Test patch:
diff --git a/search/query/query_test.go b/search/query/query_test.go
index ffa9c5b8..1794f16d 100644
--- a/search/query/query_test.go
+++ b/search/query/query_test.go
@@ -450,42 +450,41 @@ func TestGeoShapeQuery(t *testing.T) {
 		err    bool
 	}{
 		{
-			input: []byte(`{      
+			input: []byte(`{
 				"field" : "region",
 				 "geometry": {
 					 "shape": {
 						 "type": "polygon",
-						"coordinates": [
-							[                 
-							[                   
+						"coordinates": [[
+							[
 								74.1357421875,
 								30.600093873550072
-							],              
-							[                  
+							],
+							[
 								67.0166015625,
 								21.57571893245848
-							],              
-							[                  
+							],
+							[
 								68.8623046875,
 								9.145486056167277
-							],              
-							[                  
+							],
+							[
 								83.1884765625,
 								4.083452772038619
-							],              
-							[                  
+							],
+							[
 								88.9892578125,
 								22.67484735118852
-							],              
-							[                   
+							],
+							[
 								74.1357421875,
 								30.600093873550072
-							]]]                       
+							]]]
 						},
 					  "relation": "intersects"
 			  }}`),
 			output: func() Query {
-				q := NewGeoShapeQuery([][][][]float64{{{{74.1357421875, 30.600093873550072},
+				q, _ := NewGeoShapeQuery([][][][]float64{{{{74.1357421875, 30.600093873550072},
 					{67.0166015625, 21.57571893245848}, {68.8623046875, 9.145486056167277},
 					{83.1884765625, 4.083452772038619}, {88.9892578125, 22.67484735118852},
 					{74.1357421875, 30.600093873550072}}}}, geo.PolygonType, "intersects")
@@ -493,6 +492,256 @@ func TestGeoShapeQuery(t *testing.T) {
 				return q
 			}(),
 		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "multipolygon",
+						 "coordinates": [
+							 [[
+								[
+									77.58268117904663,
+									12.980513152175025
+								],
+								[
+									77.58147954940794,
+									12.977983107483992
+								],
+								[
+									77.58708000183104,
+									12.97886130773254
+								],
+								[
+									77.58268117904663,
+									12.980513152175025
+								]
+							]],
+							[[
+								[
+									77.5864577293396,
+									12.97762764459667
+								],
+								[
+									77.58879661560059,
+									12.975076660730531
+								],
+								[
+									77.59115695953369,
+									12.979216768855913
+								],
+								[
+									77.5864577293396,
+									12.97762764459667
+								]
+							]]
+						]
+					},
+					"relation": "contains"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{
+					{{{77.58268117904663, 12.980513152175025},
+						{77.58147954940794, 12.977983107483992}, {77.58708000183104, 12.97886130773254},
+						{77.58268117904663, 12.980513152175025}}},
+					{{{77.5864577293396, 12.97762764459667}, {77.58879661560059, 12.975076660730531},
+						{77.59115695953369, 12.979216768855913}, {77.5864577293396, 12.97762764459667}}}},
+					geo.MultiPolygonType, "contains")
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "point",
+						 "coordinates": [77.58268117904663, 12.980513152175025]
+					},
+					"relation": "contains"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{
+					{{{77.58268117904663, 12.980513152175025}}}},
+					geo.PointType, "contains")
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "multipoint",
+						 "coordinates": [[77.58268117904663, 12.980513152175025],
+						 [77.5864577293396, 12.97762764459667]]
+					},
+					"relation": "intersects"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{
+					{{{77.58268117904663, 12.980513152175025},
+						{77.5864577293396, 12.97762764459667}}}},
+					geo.MultiPointType, "intersects")
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "linestring",
+						 "coordinates": [[77.58268117904663, 12.980513152175025],
+						 [77.5864577293396, 12.97762764459667]]
+					},
+					"relation": "intersects"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{
+					{{{77.58268117904663, 12.980513152175025},
+						{77.5864577293396, 12.97762764459667}}}},
+					geo.LineStringType, "intersects")
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "multilinestring",
+						 "coordinates": [
+						[[77.58268117904663, 12.980513152175025],
+						 [77.5864577293396, 12.97762764459667]],
+						 [[77.5864577293396,12.97762764459667],
+						 [77.58879661560059, 12.975076660730531]]]
+					},
+					"relation": "intersects"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{{
+					{{77.58268117904663, 12.980513152175025},
+						{77.5864577293396, 12.97762764459667}},
+					{{77.5864577293396, 12.97762764459667},
+						{77.58879661560059, 12.975076660730531}}}},
+					geo.MultiLineStringType, "intersects")
+
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "envelope",
+						 "coordinates": [[77.58268117904663, 12.980513152175025],
+						 [77.5864577293396, 12.97762764459667]]
+					},
+					"relation": "within"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeQuery([][][][]float64{{
+					{{77.58268117904663, 12.980513152175025},
+						{77.5864577293396, 12.97762764459667}}}},
+					geo.EnvelopeType, "within")
+
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "circle",
+						 "coordinates": [77.58268117904663, 12.980513152175025],
+						 "radius": "100m"
+					},
+					"relation": "within"
+			  }}`),
+			output: func() Query {
+				q, _ := NewGeoShapeCircleQuery([]float64{
+					77.58268117904663, 12.980513152175025},
+					"100m", "within")
+
+				q.SetField("region")
+				return q
+			}(),
+		},
+		{
+			input: []byte(`{
+				"field" : "region",
+				"geometry": {
+					"shape": {
+					  "type": "geometrycollection",
+					  "geometries": [
+						{
+						  "type": "point",
+						  "coordinates": [
+							77.59158611297607,
+							12.972002899506203
+						  ]
+						},
+						{
+						  "type": "linestring",
+						  "coordinates": [
+							[
+							  77.58851766586304,
+							  12.973152950670608
+							],
+							[
+							  77.58937597274779,
+							  12.972212000113458
+							]
+						  ]
+						},
+						{
+						  "type": "polygon",
+						  "coordinates": [
+							[
+							  [
+								77.59055614471436,
+								12.974721193688106
+							  ],
+							  [
+								77.58954763412476,
+								12.97350841995465
+							  ],
+							  [
+								77.59141445159912,
+								12.973382960265356
+							  ],
+							  [
+								77.59055614471436,
+								12.974721193688106
+							  ]
+							]
+						  ]
+						}
+					  ]
+					},
+					"relation": "contains"
+				  }}`),
+			output: func() Query {
+				q, _ := NewGeometryCollectionQuery([][][][][]float64{
+					{{{{77.59158611297607, 12.972002899506203}}}},
+					{{{{77.58851766586304, 12.973152950670608}, {77.58937597274779, 12.972212000113458}}}},
+					{{{{77.59055614471436, 12.974721193688106}, {77.58954763412476, 12.97350841995465},
+						{77.59141445159912, 12.973382960265356}, {77.59055614471436, 12.974721193688106}}}},
+				},
+					[]string{"point", "linestring", "polygon"}, "contains")
+				q.SetField("region")
+				return q
+			}(),
+		},
 	}
 
 	for i, test := range tests {