Repository: blevesearch/bleve
Pull request #1669 (2022-03-25 12:52:46)

Adding GeoJSON spatial capability
This change introduces the capability to index and
query various spatial shapes specified by the
GeoJSON standard.
ref - https://datatracker.ietf.org/doc/html/rfc7946

For convenience, it also supports two additional shapes
like circle and envelope(bounded rectangle).

Changes included here are,

-A field type "geoshape" for specifying any of the
 geojson shapes/circle/envelope.
-A new geoshape query for performing geo queries
 across any of the geoJSON shapes.
-Three main geo query operations between shapes are,

"intersects" - finding all the documents that
 intersect with the query shape.

"contains" - finding all the documents that
 contain the query shape.

"within" - finding all the documents that reside
within the query shape.


Patch:
diff --git a/document/field_geoshape.go b/document/field_geoshape.go
new file mode 100644
index 00000000..061ff3f9
--- /dev/null
+++ b/document/field_geoshape.go
@@ -0,0 +1,183 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package document
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/blevesearch/bleve/v2/analysis"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/size"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+var reflectStaticSizeGeoShapeField int
+
+func init() {
+	var f GeoShapeField
+	reflectStaticSizeGeoShapeField = int(reflect.TypeOf(f).Size())
+}
+
+const DefaultGeoShapeIndexingOptions = index.IndexField | index.DocValues
+
+type GeoShapeField struct {
+	name              string
+	shape             index.GeoJSON
+	arrayPositions    []uint64
+	options           index.FieldIndexingOptions
+	numPlainTextBytes uint64
+	length            int
+	value             []byte
+
+	frequencies index.TokenFrequencies
+}
+
+func (n *GeoShapeField) Size() int {
+	return reflectStaticSizeGeoShapeField + size.SizeOfPtr +
+		len(n.name) +
+		len(n.arrayPositions)*size.SizeOfUint64
+}
+
+func (n *GeoShapeField) Name() string {
+	return n.name
+}
+
+func (n *GeoShapeField) ArrayPositions() []uint64 {
+	return n.arrayPositions
+}
+
+func (n *GeoShapeField) Options() index.FieldIndexingOptions {
+	return n.options
+}
+
+func (n *GeoShapeField) EncodedFieldType() byte {
+	return 's'
+}
+
+func (n *GeoShapeField) AnalyzedLength() int {
+	return n.length
+}
+
+func (n *GeoShapeField) AnalyzedTokenFrequencies() index.TokenFrequencies {
+	return n.frequencies
+}
+
+func (n *GeoShapeField) Analyze() {
+	// compute the bytes representation for the coordinates
+	tokens := make(analysis.TokenStream, 0)
+	tokens = append(tokens, &analysis.Token{
+		Start:    0,
+		End:      len(n.value),
+		Term:     n.value,
+		Position: 1,
+		Type:     analysis.AlphaNumeric,
+	})
+
+	rti := geo.GetSpatialAnalyzerPlugin("s2")
+	terms := rti.GetIndexTokens(n.shape)
+
+	for _, term := range terms {
+		token := analysis.Token{
+			Start:    0,
+			End:      len(term),
+			Term:     []byte(term),
+			Position: 1,
+			Type:     analysis.AlphaNumeric,
+		}
+		tokens = append(tokens, &token)
+	}
+
+	n.length = len(tokens)
+	n.frequencies = analysis.TokenFrequency(tokens, n.arrayPositions, n.options)
+}
+
+func (n *GeoShapeField) Value() []byte {
+	return n.value
+}
+
+func (n *GeoShapeField) GoString() string {
+	return fmt.Sprintf("&document.GeoShapeField{Name:%s, Options: %s, Value: %s}", n.name, n.options, n.value)
+}
+
+func (n *GeoShapeField) NumPlainTextBytes() uint64 {
+	return n.numPlainTextBytes
+}
+
+func NewGeoShapeField(name string, arrayPositions []uint64,
+	coordinates [][][][]float64, typ string) *GeoShapeField {
+	return NewGeoShapeFieldWithIndexingOptions(name, arrayPositions,
+		coordinates, typ, DefaultGeoShapeIndexingOptions)
+}
+
+func NewGeoShapeFieldWithIndexingOptions(name string, arrayPositions []uint64,
+	coordinates [][][][]float64, typ string,
+	options index.FieldIndexingOptions) *GeoShapeField {
+	shape, value, err := geo.NewGeoJsonShape(coordinates, typ)
+	if err != nil {
+		return nil
+	}
+
+	options = options | DefaultGeoShapeIndexingOptions
+
+	return &GeoShapeField{
+		shape:             shape,
+		name:              name,
+		arrayPositions:    arrayPositions,
+		options:           options,
+		value:             value,
+		numPlainTextBytes: uint64(len(value)),
+	}
+}
+
+func NewGeometryCollectionFieldWithIndexingOptions(name string,
+	arrayPositions []uint64, coordinates [][][][][]float64, types []string,
+	options index.FieldIndexingOptions) *GeoShapeField {
+	shape, value, err := geo.NewGeometryCollection(coordinates, types)
+	if err != nil {
+		return nil
+	}
+
+	options = options | DefaultGeoShapeIndexingOptions
+
+	return &GeoShapeField{
+		shape:             shape,
+		name:              name,
+		arrayPositions:    arrayPositions,
+		options:           options,
+		value:             value,
+		numPlainTextBytes: uint64(len(value)),
+	}
+}
+
+func NewGeoCircleFieldWithIndexingOptions(name string, arrayPositions []uint64,
+	centerPoint []float64, radius float64,
+	options index.FieldIndexingOptions) *GeoShapeField {
+	shape, value, err := geo.NewGeoCircleShape(centerPoint, radius)
+	if err != nil {
+		return nil
+	}
+
+	options = options | DefaultGeoShapeIndexingOptions
+
+	return &GeoShapeField{
+		shape:             shape,
+		name:              name,
+		arrayPositions:    arrayPositions,
+		options:           options,
+		value:             value,
+		numPlainTextBytes: uint64(len(value)),
+	}
+}
diff --git a/geo/geo_s2_util.go b/geo/geo_s2_util.go
new file mode 100644
index 00000000..8c149df1
--- /dev/null
+++ b/geo/geo_s2_util.go
@@ -0,0 +1,382 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package geo
+
+import (
+	"strings"
+
+	index "github.com/blevesearch/bleve_index_api"
+	"github.com/blevesearch/geo/s2"
+	"github.com/golang/geo/s1"
+)
+
+// ------------------------------------------------------------------------
+
+func polylineIntersectsPoint(pls []*s2.Polyline,
+	point []float64) bool {
+	s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(
+		point[1], point[0]))
+
+	for _, pl := range pls {
+		if pl.IntersectsCell(s2cell) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func polylineIntersectsPolygons(pls []*s2.Polyline,
+	coordinates [][][][]float64) bool {
+	for _, pl := range pls {
+		for _, vertices := range coordinates {
+			s2pgn := s2PolygonFromCoordinates(vertices)
+			for i := 0; i < pl.NumEdges(); i++ {
+				edge := pl.Edge(i)
+				a := []float64{edge.V0.X, edge.V0.Y}
+				b := []float64{edge.V1.X, edge.V1.Y}
+
+				for i := 0; i < s2pgn.NumEdges(); i++ {
+					edgeB := s2pgn.Edge(i)
+
+					c := []float64{edgeB.V0.X, edgeB.V0.Y}
+					d := []float64{edgeB.V1.X, edgeB.V1.Y}
+
+					if doIntersect(a, b, c, d) {
+						return true
+					}
+				}
+			}
+		}
+	}
+
+	return false
+}
+
+func polylineIntersectsPolylines(pls []*s2.Polyline,
+	coordinates [][][]float64) bool {
+	var plsInDoc []*s2.Polyline
+	for _, lines := range coordinates {
+		latlngs := make([]s2.LatLng, 0, len(lines))
+		for _, line := range lines {
+			latlngs = append(latlngs, s2.LatLngFromDegrees(line[1], line[0]))
+		}
+		plsInDoc = append(plsInDoc, s2.PolylineFromLatLngs(latlngs))
+	}
+
+	for _, pl := range pls {
+		for _, pl2 := range plsInDoc {
+			if pl.Intersects(pl2) {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func geometryCollectionIntersectsShape(gc *geometryCollection,
+	shapeIn index.GeoJSON) bool {
+	for _, shape := range gc.Members() {
+		intersects, err := shapeIn.Intersects(shape)
+		if err == nil && intersects {
+			return true
+		}
+	}
+	return false
+}
+
+func polylinesContainPoints(lineVertices [][][]float64,
+	coordinates [][]float64) bool {
+	// check the intersection for every point in the array.
+	lookup := make(map[int]struct{})
+NextPoint:
+	for pos, points := range coordinates {
+		s2point := s2.PointFromLatLng(
+			s2.LatLngFromDegrees(points[1], points[0]))
+
+		for _, lines := range lineVertices {
+			for _, point := range lines {
+
+				linePoint := s2.PointFromLatLng(
+					s2.LatLngFromDegrees(point[1], point[0]))
+
+				if linePoint.ContainsPoint(s2point) {
+					lookup[pos] = struct{}{}
+					continue NextPoint
+				}
+			}
+		}
+	}
+
+	return len(coordinates) == len(lookup)
+}
+
+func polygonsIntersectsLinestrings(s2pgn *s2.Polygon,
+	lineVertices [][][]float64) bool {
+	for _, vertices := range lineVertices {
+		t1 := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			vertices[0][1], vertices[0][0]))
+		t2 := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			vertices[1][1], vertices[1][0]))
+		a := []float64{t1.X, t1.Y}
+		b := []float64{t2.X, t2.Y}
+
+		for i := 0; i < s2pgn.NumEdges(); i++ {
+			edgeB := s2pgn.Edge(i)
+
+			c := []float64{edgeB.V0.X, edgeB.V0.Y}
+			d := []float64{edgeB.V1.X, edgeB.V1.Y}
+
+			if doIntersect(a, b, c, d) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func polygonsContainsLineStrings(s2pgns []*s2.Polygon,
+	lineVertices [][][]float64) bool {
+	linesWithIn := make(map[int]struct{})
+nextLine:
+	for lineIndex, points := range lineVertices {
+		start := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			points[0][1], points[0][0]))
+		end := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			points[1][1], points[1][0]))
+
+		// check whether both the end vertices are inside the polygon.
+		for _, s2pgn := range s2pgns {
+			if s2pgn.ContainsPoint(start) && s2pgn.ContainsPoint(end) {
+				// if both endpoints lie within the polygon then check
+				// for any edge intersections to confirm the containment.
+				for i := 0; i < s2pgn.NumEdges(); i++ {
+					edgeA := s2pgn.Edge(i)
+					a := []float64{edgeA.V0.X, edgeA.V0.Y}
+					b := []float64{edgeA.V1.X, edgeA.V1.Y}
+					c := []float64{start.X, start.Y}
+					d := []float64{end.X, end.Y}
+					if doIntersect(a, b, c, d) {
+						continue nextLine
+					}
+				}
+				linesWithIn[lineIndex] = struct{}{}
+				continue nextLine
+			}
+		}
+	}
+
+	return len(lineVertices) == len(linesWithIn)
+}
+
+func rectangleIntersectsWithPolygons(s2rect *s2.Rect,
+	coordinates [][][][]float64) bool {
+	s2pgnFromRect := s2PolygonFromS2Rectangle(s2rect)
+	for _, pgnVertices := range coordinates {
+		s2pgn := s2PolygonFromCoordinates(pgnVertices)
+		if s2pgn.Intersects(s2pgnFromRect) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func rectangleIntersectsWithLineStrings(s2rect *s2.Rect,
+	coordinates [][][]float64) bool {
+
+	var polylines []*s2.Polyline
+
+	for _, lines := range coordinates {
+		var latlngs []s2.LatLng
+		for _, line := range lines {
+			v := s2.LatLngFromDegrees(line[1], line[0])
+			latlngs = append(latlngs, v)
+		}
+		pl := s2.PolylineFromLatLngs(latlngs)
+		polylines = append(polylines, pl)
+	}
+
+	for _, pl := range polylines {
+		for i := 0; i < pl.NumEdges(); i++ {
+			edgeA := pl.Edge(i)
+			a := []float64{edgeA.V0.X, edgeA.V0.Y}
+			b := []float64{edgeA.V1.X, edgeA.V1.Y}
+
+			for j := 0; j < 4; j++ {
+				v1 := s2.PointFromLatLng(s2rect.Vertex(j))
+				v2 := s2.PointFromLatLng(s2rect.Vertex((j + 1) % 4))
+
+				c := []float64{v1.X, v1.Y}
+				d := []float64{v2.X, v2.Y}
+
+				if doIntersect(a, b, c, d) {
+					return true
+				}
+			}
+		}
+	}
+
+	return false
+}
+
+func s2PolygonFromCoordinates(coordinates [][][]float64) *s2.Polygon {
+	loops := make([]*s2.Loop, 0, len(coordinates))
+	for _, loop := range coordinates {
+		var points []s2.Point
+		for _, point := range loop {
+			p := s2.PointFromLatLng(s2.LatLngFromDegrees(point[1], point[0]))
+			points = append(points, p)
+		}
+		loops = append(loops, s2.LoopFromPoints(points))
+	}
+
+	return s2.PolygonFromOrientedLoops(loops)
+}
+
+func s2PolygonFromS2Rectangle(s2rect *s2.Rect) *s2.Polygon {
+	loops := make([]*s2.Loop, 0, 1)
+	var points []s2.Point
+	for j := 0; j <= 4; j++ {
+		points = append(points, s2.PointFromLatLng(s2rect.Vertex(j%4)))
+	}
+
+	loops = append(loops, s2.LoopFromPoints(points))
+	return s2.PolygonFromLoops(loops)
+}
+
+func deduplicateTerms(terms []string) []string {
+	var rv []string
+	hash := make(map[string]struct{}, len(terms))
+	for _, term := range terms {
+		if _, exists := hash[term]; !exists {
+			rv = append(rv, term)
+			hash[term] = struct{}{}
+		}
+	}
+
+	return rv
+}
+
+//----------------------------------------------------------------------
+
+var earthRadiusInMeter = 6378137.0
+
+func radiusInMetersToS1Angle(radius float64) s1.Angle {
+	return s1.Angle(radius / earthRadiusInMeter)
+}
+
+func s2PolylinesFromCoordinates(coordinates [][][]float64) []*s2.Polyline {
+	var polylines []*s2.Polyline
+	for _, lines := range coordinates {
+		var latlngs []s2.LatLng
+		for _, line := range lines {
+			v := s2.LatLngFromDegrees(line[1], line[0])
+			latlngs = append(latlngs, v)
+		}
+		polylines = append(polylines, s2.PolylineFromLatLngs(latlngs))
+	}
+	return polylines
+}
+
+func s2RectFromBounds(topLeft, bottomRight []float64) *s2.Rect {
+	rect := s2.EmptyRect()
+	rect = rect.AddPoint(s2.LatLngFromDegrees(topLeft[1], topLeft[0]))
+	rect = rect.AddPoint(s2.LatLngFromDegrees(bottomRight[1], bottomRight[0]))
+	return &rect
+}
+
+func s2Cap(vertices []float64, radiusInMeter float64) *s2.Cap {
+	cp := s2.PointFromLatLng(s2.LatLngFromDegrees(vertices[1], vertices[0]))
+	angle := radiusInMetersToS1Angle(float64(radiusInMeter))
+	cap := s2.CapFromCenterAngle(cp, angle)
+	return &cap
+}
+
+func max(a, b float64) float64 {
+	if a >= b {
+		return a
+	}
+	return b
+}
+
+func min(a, b float64) float64 {
+	if a >= b {
+		return b
+	}
+	return a
+}
+
+func onsegment(p, q, r []float64) bool {
+	if q[0] <= max(p[0], r[0]) && q[0] >= min(p[0], r[0]) &&
+		q[1] <= max(p[1], r[1]) && q[1] >= min(p[1], r[1]) {
+		return true
+	}
+
+	return false
+}
+
+func doIntersect(p1, q1, p2, q2 []float64) bool {
+	o1 := orientation(p1, q1, p2)
+	o2 := orientation(p1, q1, q2)
+	o3 := orientation(p2, q2, p1)
+	o4 := orientation(p2, q2, q1)
+
+	if o1 != o2 && o3 != o4 {
+		return true
+	}
+
+	if o1 == 0 && onsegment(p1, p2, q1) {
+		return true
+	}
+
+	if o2 == 0 && onsegment(p1, q2, q1) {
+		return true
+	}
+
+	if o3 == 0 && onsegment(p2, p1, q2) {
+		return true
+	}
+
+	if o4 == 0 && onsegment(p2, q1, q2) {
+		return true
+	}
+
+	return false
+}
+
+func orientation(p, q, r []float64) int {
+	val := (q[1]-p[1])*(r[0]-q[0]) - (q[0]-p[0])*(r[1]-q[1])
+	if val == 0 {
+		return 0
+	}
+	if val > 0 {
+		return 1
+	}
+	return 2
+}
+
+func stripCoveringTerms(terms []string) []string {
+	rv := make([]string, 0, len(terms))
+	for _, term := range terms {
+		if strings.HasPrefix(term, "$") {
+			rv = append(rv, term[1:])
+			continue
+		}
+		rv = append(rv, term)
+	}
+	return deduplicateTerms(rv)
+}
diff --git a/geo/geo_s2_utils.go b/geo/geo_s2_utils.go
deleted file mode 100644
index 9bc6941c..00000000
--- a/geo/geo_s2_utils.go
+++ /dev/null
@@ -1,194 +0,0 @@
-//  Copyright (c) 2022 Couchbase, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// 		http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package geo
-
-import (
-	"sync"
-
-	index "github.com/blevesearch/bleve_index_api"
-	"github.com/blevesearch/geo/s2"
-)
-
-// spatialPluginsMap is spatial plugin cache.
-var (
-	spatialPluginsMap = make(map[string]index.SpatialAnalyzerPlugin)
-	pluginsMapLock    = sync.RWMutex{}
-)
-
-// RegisterSpatialAnalyzerPlugin registers the given plugin implementation.
-func RegisterSpatialAnalyzerPlugin(plugin index.SpatialAnalyzerPlugin) {
-	pluginsMapLock.Lock()
-	spatialPluginsMap[plugin.Type()] = plugin
-	pluginsMapLock.Unlock()
-}
-
-// GetSpatialAnalyzerPlugin retrieves the given implementation type.
-func GetSpatialAnalyzerPlugin(typ string) index.SpatialAnalyzerPlugin {
-	pluginsMapLock.RLock()
-	rv := spatialPluginsMap[typ]
-	pluginsMapLock.RUnlock()
-	return rv
-}
-
-func init() {
-	registerS2RegionTermIndexer()
-}
-
-func registerS2RegionTermIndexer() {
-	// refer for detailed commentary on s2 options here
-	// https://github.com/sreekanth-cb/geo/blob/806f1c56fffb418d53d2ea6ce6aabaa376355d67/s2/region_term_indexer.go#L92
-	options := &s2.Options{}
-
-	// maxLevel control the maximum size of the
-	// S2Cells used to approximate regions.
-	options.SetMaxLevel(16)
-
-	// minLevel control the minimum size of the
-	// S2Cells used to approximate regions.
-	options.SetMinLevel(4)
-
-	// levelMod value greater than 1 increases the effective branching
-	// factor of the S2Cell hierarchy by skipping some levels.
-	options.SetLevelMod(2)
-
-	// maxCells controls the maximum number of cells
-	// when approximating each s2 region.
-	options.SetMaxCells(8)
-
-	// If the index will only contain points (rather than regions), be sure
-	// to set this flag.  This will generate smaller and faster queries that
-	// are specialized for the points-only case.
-	options.SetPointsOnly(true)
-
-	spatialPlugin := S2SpatialAnalyzerPlugin{
-		s2Indexer: s2.NewRegionTermIndexerWithOptions(*options)}
-
-	RegisterSpatialAnalyzerPlugin(&spatialPlugin)
-}
-
-// S2SpatialAnalyzerPlugin is an implementation of
-// the index.SpatialAnalyzerPlugin interface.
-type S2SpatialAnalyzerPlugin struct {
-	s2Indexer *s2.RegionTermIndexer
-}
-
-func (s *S2SpatialAnalyzerPlugin) Type() string {
-	return "s2"
-}
-
-func (s *S2SpatialAnalyzerPlugin) GetIndexTokens(shape index.GeoJSON) []string {
-	if shape.Type() == "point" {
-		if point, ok := shape.(*Point); ok {
-			// generate the tokens for indexing.
-			return s.s2Indexer.GetIndexTermsForPoint(s2.PointFromLatLng(
-				s2.LatLngFromDegrees(point.Lat, point.Lon)), "")
-		}
-	}
-	return nil
-}
-
-func (s *S2SpatialAnalyzerPlugin) GetQueryTokens(shape index.GeoJSON) []string {
-	if pd, ok := shape.(*pointDistance); ok {
-		// obtain the covering query region from the given points.
-		queryRegion := s2.CapFromCenterAndRadius(pd.centerLat, pd.centerLon, pd.dist)
-
-		// obtain the query terms for the query region.
-		terms := s.s2Indexer.GetQueryTermsForRegion(queryRegion, "")
-
-		// since we index only one dimensional points, let's filter out
-		// or prune our search time terms. This needs to be removed once
-		// we start indexing 2 or higher dimensional shapes.
-		return s2.FilterOutCoveringTerms(terms)
-	}
-
-	if br, ok := shape.(*boundedRectangle); ok {
-		rect := s2.RectFromDegrees(br.minLat, br.minLon, br.maxLat, br.maxLon)
-
-		// obtain the terms to be searched for the given bounding box.
-		terms := s.s2Indexer.GetQueryTermsForRegion(rect, "")
-
-		// since we index only one dimensional points, let's filter out
-		// or prune our search time terms. This needs to be removed once
-		// we start indexing 2 or higher dimensional shapes.
-		return s2.FilterOutCoveringTerms(terms)
-	}
-
-	if bg, ok := shape.(*boundedPolygon); ok {
-		coordinates := bg.coordinates
-		vertices := make([]s2.Point, len(coordinates))
-		for i, point := range coordinates {
-			vertices[i] = s2.PointFromLatLng(s2.LatLngFromDegrees(point.Lat, point.Lon))
-		}
-		polygon := s2.PolygonFromLoops([]*s2.Loop{s2.LoopFromPoints(vertices)})
-
-		// obtain the terms to be searched for the given polygon.
-		terms := s.s2Indexer.GetQueryTermsForRegion(
-			polygon.CapBound(), "")
-
-		// since we index only one dimensional points, let's filter out
-		// or prune our search time terms. This needs to be removed once
-		// we start indexing 2 or higher dimensional shapes.
-		return s2.FilterOutCoveringTerms(terms)
-	}
-
-	return nil
-}
-
-type boundedRectangle struct {
-	minLat float64
-	maxLat float64
-	minLon float64
-	maxLon float64
-}
-
-func (br *boundedRectangle) Type() string {
-	return "boundedRectangle"
-}
-
-func NewBoundedRectangle(minLat, minLon, maxLat, maxLon float64) *boundedRectangle {
-	return &boundedRectangle{minLat: minLat, maxLat: maxLat,
-		minLon: minLon, maxLon: maxLon}
-}
-
-type boundedPolygon struct {
-	coordinates []Point
-}
-
-func (bp *boundedPolygon) Type() string {
-	return "boundedPolygon"
-}
-
-func NewBoundedPolygon(coordinates []Point) *boundedPolygon {
-	return &boundedPolygon{coordinates: coordinates}
-}
-
-type pointDistance struct {
-	dist      float64
-	centerLat float64
-	centerLon float64
-}
-
-func (p *pointDistance) Type() string {
-	return "pointDistance"
-}
-
-func NewPointDistance(centerLat, centerLon, dist float64) *pointDistance {
-	return &pointDistance{centerLat: centerLat,
-		centerLon: centerLon, dist: dist}
-}
-
-func (p *Point) Type() string {
-	return "point"
-}
diff --git a/geo/geo_s2plugin_impl.go b/geo/geo_s2plugin_impl.go
new file mode 100644
index 00000000..ed570bdd
--- /dev/null
+++ b/geo/geo_s2plugin_impl.go
@@ -0,0 +1,343 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package geo
+
+import (
+	"sync"
+
+	index "github.com/blevesearch/bleve_index_api"
+
+	"github.com/blevesearch/geo/s2"
+)
+
+// spatialPluginsMap is spatial plugin cache.
+var (
+	spatialPluginsMap = make(map[string]index.SpatialAnalyzerPlugin)
+	pluginsMapLock    = sync.RWMutex{}
+)
+
+// RegisterSpatialAnalyzerPlugin registers the given plugin implementation.
+func RegisterSpatialAnalyzerPlugin(plugin index.SpatialAnalyzerPlugin) {
+	pluginsMapLock.Lock()
+	spatialPluginsMap[plugin.Type()] = plugin
+	pluginsMapLock.Unlock()
+}
+
+// GetSpatialAnalyzerPlugin retrieves the given implementation type.
+func GetSpatialAnalyzerPlugin(typ string) index.SpatialAnalyzerPlugin {
+	pluginsMapLock.RLock()
+	rv := spatialPluginsMap[typ]
+	pluginsMapLock.RUnlock()
+	return rv
+}
+
+func init() {
+	registerS2RegionTermIndexer()
+}
+
+func registerS2RegionTermIndexer() {
+	// refer for detailed commentary on s2 options here
+	// https://github.com/blevesearch/geo/blob/5734244d948a0ccf9a6b00e02d1a05f94cbc8849/s2/region_term_indexer.go#L92
+	options := &s2.Options{}
+
+	// maxLevel control the maximum size of the
+	// S2Cells used to approximate regions.
+	options.SetMaxLevel(16)
+
+	// minLevel control the minimum size of the
+	// S2Cells used to approximate regions.
+	options.SetMinLevel(4)
+
+	// levelMod value greater than 1 increases the effective branching
+	// factor of the S2Cell hierarchy by skipping some levels.
+	options.SetLevelMod(2)
+
+	// maxCells controls the maximum number of cells
+	// when approximating each s2 region.
+	options.SetMaxCells(8)
+
+	spatialPlugin := S2SpatialAnalyzerPlugin{
+		s2Indexer: s2.NewRegionTermIndexerWithOptions(*options)}
+
+	RegisterSpatialAnalyzerPlugin(&spatialPlugin)
+}
+
+// S2SpatialAnalyzerPlugin is an implementation of
+// the index.SpatialAnalyzerPlugin interface.
+type S2SpatialAnalyzerPlugin struct {
+	s2Indexer *s2.RegionTermIndexer
+}
+
+func (s *S2SpatialAnalyzerPlugin) Type() string {
+	return "s2"
+}
+
+func (s *S2SpatialAnalyzerPlugin) GetIndexTokens(queryShape index.GeoJSON) []string {
+	var rv []string
+	shapes := []index.GeoJSON{queryShape}
+	if gc, ok := queryShape.(*geometryCollection); ok {
+		shapes = gc.Shapes
+	}
+
+	for _, shape := range shapes {
+		if s2t, ok := shape.(s2Tokenizable); ok {
+			rv = append(rv, s2t.IndexTokens(s)...)
+		}
+	}
+
+	return stripCoveringTerms(rv)
+}
+
+func (s *S2SpatialAnalyzerPlugin) GetQueryTokens(queryShape index.GeoJSON) []string {
+	var rv []string
+	shapes := []index.GeoJSON{queryShape}
+	if gc, ok := queryShape.(*geometryCollection); ok {
+		shapes = gc.Shapes
+	}
+
+	for _, shape := range shapes {
+		if s2t, ok := shape.(s2Tokenizable); ok {
+			rv = append(rv, s2t.QueryTokens(s)...)
+		}
+	}
+
+	return stripCoveringTerms(rv)
+}
+
+// ------------------------------------------------------------------------
+// s2Tokenizable is an optional interface for shapes that support
+// the generation of s2 based tokens that can be used for both
+// indexing and querying.
+
+type s2Tokenizable interface {
+	// IndexTokens returns the tokens for indexing.
+	IndexTokens(*S2SpatialAnalyzerPlugin) []string
+
+	// QueryTokens returns the tokens for searching.
+	QueryTokens(*S2SpatialAnalyzerPlugin) []string
+}
+
+// ------------------------------------------------------------------------
+
+func (p *point) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	ll := s2.LatLngFromDegrees(p.Vertices[1], p.Vertices[0])
+	point := s2.PointFromLatLng(ll)
+	return s.s2Indexer.GetIndexTermsForPoint(point, "")
+}
+
+func (p *point) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	ll := s2.LatLngFromDegrees(p.Vertices[1], p.Vertices[0])
+	point := s2.PointFromLatLng(ll)
+	return s.s2Indexer.GetQueryTermsForPoint(point, "")
+}
+
+// ------------------------------------------------------------------------
+
+func (mp *multipoint) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	for _, point := range mp.Vertices {
+		terms := s.s2Indexer.GetIndexTermsForPoint(s2.PointFromLatLng(
+			s2.LatLngFromDegrees(point[1], point[0])), "")
+		rv = append(rv, terms...)
+	}
+	return deduplicateTerms(rv)
+}
+
+func (mp *multipoint) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	for _, point := range mp.Vertices {
+		terms := s.s2Indexer.GetQueryTermsForPoint(s2.PointFromLatLng(
+			s2.LatLngFromDegrees(point[1], point[0])), "")
+		rv = append(rv, terms...)
+	}
+
+	return deduplicateTerms(rv)
+}
+
+// ------------------------------------------------------------------------
+
+func (ls *linestring) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	pls := s2PolylinesFromCoordinates([][][]float64{ls.Vertices})
+	if len(pls) == 1 {
+		return s.s2Indexer.GetIndexTermsForRegion(pls[0].CapBound(), "")
+	}
+	return nil
+}
+
+func (ls *linestring) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	pls := s2PolylinesFromCoordinates([][][]float64{ls.Vertices})
+	if len(pls) == 1 {
+		return s.s2Indexer.GetQueryTermsForRegion(pls[0].CapBound(), "")
+	}
+	return nil
+}
+
+// ------------------------------------------------------------------------
+
+func (mls *multilinestring) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	polylines := s2PolylinesFromCoordinates(mls.Vertices)
+
+	for _, pline := range polylines {
+		terms := s.s2Indexer.GetIndexTermsForRegion(pline.CapBound(), "")
+		rv = append(rv, terms...)
+	}
+
+	return deduplicateTerms(rv)
+}
+
+func (mls *multilinestring) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	polylines := s2PolylinesFromCoordinates(mls.Vertices)
+
+	for _, pline := range polylines {
+		terms := s.s2Indexer.GetQueryTermsForRegion(pline.CapBound(), "")
+		rv = append(rv, terms...)
+	}
+
+	return deduplicateTerms(rv)
+}
+
+// ------------------------------------------------------------------------
+
+func (mp *multipolygon) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	for _, loops := range mp.Coordinates() {
+		s2polygon := s2PolygonFromCoordinates(loops)
+		terms := s.s2Indexer.GetIndexTermsForRegion(s2polygon.CapBound(), "")
+		rv = append(rv, terms...)
+	}
+
+	return rv
+}
+
+func (mp *multipolygon) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	var rv []string
+	for _, coords := range mp.Coordinates() {
+		s2polygon := s2PolygonFromCoordinates(coords)
+		terms := s.s2Indexer.GetQueryTermsForRegion(s2polygon.CapBound(), "")
+		rv = append(rv, terms...)
+	}
+
+	return rv
+}
+
+// ------------------------------------------------------------------------
+
+func (pgn *polygon) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	s2polygon := s2PolygonFromCoordinates(pgn.Coordinates())
+	return s.s2Indexer.GetIndexTermsForRegion(
+		s2polygon.CapBound(), "")
+}
+
+func (pgn *polygon) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	s2polygon := s2PolygonFromCoordinates(pgn.Coordinates())
+	return s.s2Indexer.GetQueryTermsForRegion(
+		s2polygon.CapBound(), "")
+}
+
+// ------------------------------------------------------------------------
+
+func (c *circle) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	cp := s2.PointFromLatLng(s2.LatLngFromDegrees(c.Vertices[1], c.Vertices[0]))
+	angle := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+	cap := s2.CapFromCenterAngle(cp, angle)
+
+	return s.s2Indexer.GetIndexTermsForRegion(cap.CapBound(), "")
+}
+
+func (c *circle) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	cp := s2.PointFromLatLng(s2.LatLngFromDegrees(c.Vertices[1], c.Vertices[0]))
+	angle := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+	cap := s2.CapFromCenterAngle(cp, angle)
+
+	return s.s2Indexer.GetQueryTermsForRegion(cap.CapBound(), "")
+}
+
+// ------------------------------------------------------------------------
+
+func (e *envelope) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	s2rect := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+	return s.s2Indexer.GetIndexTermsForRegion(s2rect.CapBound(), "")
+}
+
+func (e *envelope) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	s2rect := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+	return s.s2Indexer.GetQueryTermsForRegion(s2rect.CapBound(), "")
+}
+
+// ------------------------------------------------------------------------
+
+func (p *Point) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	return s.s2Indexer.GetIndexTermsForPoint(s2.PointFromLatLng(
+		s2.LatLngFromDegrees(p.Lat, p.Lon)), "")
+}
+
+func (p *Point) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	return nil
+}
+
+// ------------------------------------------------------------------------
+
+func (pd *pointDistance) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	return nil
+}
+
+func (pd *pointDistance) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	// obtain the covering query region from the given points.
+	queryRegion := s2.CapFromCenterAndRadius(pd.centerLat,
+		pd.centerLon, pd.dist)
+
+	// obtain the query terms for the query region.
+	terms := s.s2Indexer.GetQueryTermsForRegion(queryRegion, "")
+
+	return s2.FilterOutCoveringTerms(terms)
+}
+
+// ------------------------------------------------------------------------
+
+func (bp *boundedPolygon) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	return nil
+}
+
+func (bp *boundedPolygon) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	vertices := make([]s2.Point, len(bp.coordinates))
+	for i, point := range bp.coordinates {
+		vertices[i] = s2.PointFromLatLng(
+			s2.LatLngFromDegrees(point.Lat, point.Lon))
+	}
+	s2polygon := s2.PolygonFromOrientedLoops([]*s2.Loop{s2.LoopFromPoints(vertices)})
+
+	// obtain the terms to be searched for the given polygon.
+	terms := s.s2Indexer.GetQueryTermsForRegion(
+		s2polygon.CapBound(), "")
+
+	return s2.FilterOutCoveringTerms(terms)
+}
+
+// ------------------------------------------------------------------------
+
+func (br *boundedRectangle) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
+	return nil
+}
+
+func (br *boundedRectangle) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
+	rect := s2.RectFromDegrees(br.minLat, br.minLon, br.maxLat, br.maxLon)
+
+	// obtain the terms to be searched for the given bounding box.
+	terms := s.s2Indexer.GetQueryTermsForRegion(rect, "")
+
+	return s2.FilterOutCoveringTerms(terms)
+}
diff --git a/geo/geojson_shapes_impl.go b/geo/geojson_shapes_impl.go
new file mode 100644
index 00000000..b4c7f581
--- /dev/null
+++ b/geo/geojson_shapes_impl.go
@@ -0,0 +1,1746 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package geo
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	index "github.com/blevesearch/bleve_index_api"
+
+	"github.com/blevesearch/geo/s2"
+)
+
+const (
+	PointType              = "point"
+	MultiPointType         = "multipoint"
+	LineStringType         = "linestring"
+	MultiLineStringType    = "multilinestring"
+	PolygonType            = "polygon"
+	MultiPolygonType       = "multipolygon"
+	GeometryCollectionType = "geometrycollection"
+	CircleType             = "circle"
+	EnvelopeType           = "envelope"
+)
+
+// compositeShape is an optional interface for the
+// composite geoJSON shapes which is composed of
+// multiple spatial shapes within it. Composite shapes
+// like multipoint, multilinestring, multipolygon and
+// geometrycollection shapes are supposed to implement
+// this interface.
+type compositeShape interface {
+	// Members implementation returns the
+	// geoJSON shapes composed within the shape.
+	Members() []index.GeoJSON
+}
+
+//--------------------------------------------------------
+// point represents the geoJSON point type and it
+// implements the index.GeoJSON interface.
+type point struct {
+	Typ      string    `json:"type"`
+	Vertices []float64 `json:"coordinates"`
+	s2cell   *s2.Cell
+}
+
+func (p *point) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func NewGeoJsonPoint(v []float64) index.GeoJSON {
+	return &point{Typ: PointType, Vertices: v}
+}
+
+func (p *point) Intersects(other index.GeoJSON) (bool, error) {
+	if p.s2cell == nil {
+		s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			p.Vertices[1], p.Vertices[0]))
+		cell := s2.CellFromPoint(s2point)
+		p.s2cell = &cell
+	}
+
+	return checkCellIntersectsShape(p.s2cell, p, other)
+}
+
+func (p *point) Contains(other index.GeoJSON) (bool, error) {
+	if p.s2cell == nil {
+		s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			p.Vertices[1], p.Vertices[0]))
+		cell := s2.CellFromPoint(s2point)
+		p.s2cell = &cell
+	}
+
+	return checkCellContainsShape([]*s2.Cell{p.s2cell}, other)
+}
+
+func (p *point) Coordinates() []float64 {
+	return p.Vertices
+}
+
+//--------------------------------------------------------
+// multipoint represents the geoJSON multipoint type and it
+// implements the index.GeoJSON interface as well as the
+// compositeShap interface.
+type multipoint struct {
+	Typ      string      `json:"type"`
+	Vertices [][]float64 `json:"coordinates"`
+	s2cells  []*s2.Cell
+}
+
+func NewGeoJsonMultiPoint(v [][]float64) index.GeoJSON {
+	return &multipoint{Typ: MultiPointType, Vertices: v}
+}
+
+func (p *multipoint) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func (p *multipoint) Intersects(other index.GeoJSON) (bool, error) {
+	if p.s2cells == nil {
+		p.s2cells = make([]*s2.Cell, len(p.Vertices))
+
+		for i, point := range p.Vertices {
+			s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				point[1], point[0]))
+			cell := s2.CellFromPoint(s2point)
+			p.s2cells[i] = &cell
+		}
+	}
+
+	for _, cell := range p.s2cells {
+		rv, err := checkCellIntersectsShape(cell, p, other)
+		if rv && err == nil {
+			return rv, nil
+		}
+	}
+
+	return false, nil
+}
+
+func (p *multipoint) Contains(other index.GeoJSON) (bool, error) {
+	if p.s2cells == nil {
+		p.s2cells = make([]*s2.Cell, len(p.Vertices))
+
+		for i, point := range p.Vertices {
+			s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				point[1], point[0]))
+			cell := s2.CellFromPoint(s2point)
+			p.s2cells[i] = &cell
+		}
+	}
+
+	return checkCellContainsShape(p.s2cells, other)
+}
+
+func (p *multipoint) Coordinates() [][]float64 {
+	return p.Vertices
+}
+
+func (p *multipoint) Members() []index.GeoJSON {
+	points := make([]index.GeoJSON, len(p.Vertices))
+	for pos, vertices := range p.Vertices {
+		points[pos] = NewGeoJsonPoint(vertices)
+	}
+	return points
+}
+
+//--------------------------------------------------------
+// linestring represents the geoJSON linestring type and it
+// implements the index.GeoJSON interface.
+type linestring struct {
+	Typ      string      `json:"type"`
+	Vertices [][]float64 `json:"coordinates"`
+	pl       *s2.Polyline
+}
+
+func NewGeoJsonLinestring(points [][]float64) index.GeoJSON {
+	return &linestring{Typ: LineStringType, Vertices: points}
+}
+
+func (p *linestring) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func (p *linestring) Intersects(other index.GeoJSON) (bool, error) {
+	if p.pl == nil {
+		latlngs := make([]s2.LatLng, 2)
+		latlngs[0] = s2.LatLngFromDegrees(p.Vertices[0][1], p.Vertices[0][0])
+		latlngs[1] = s2.LatLngFromDegrees(p.Vertices[1][1], p.Vertices[1][0])
+
+		p.pl = s2.PolylineFromLatLngs(latlngs)
+	}
+
+	return checkLineStringsIntersectsShape([]*s2.Polyline{p.pl}, p, other)
+}
+
+func (p *linestring) Contains(other index.GeoJSON) (bool, error) {
+	return checkLineStringsContainsShape([][][]float64{p.Vertices}, other)
+}
+
+func (p *linestring) Coordinates() [][]float64 {
+	return p.Vertices
+}
+
+//--------------------------------------------------------
+// multilinestring represents the geoJSON multilinestring type
+// and it implements the index.GeoJSON interface as well as the
+// compositeShap interface.
+type multilinestring struct {
+	Typ      string        `json:"type"`
+	Vertices [][][]float64 `json:"coordinates"`
+	pls      []*s2.Polyline
+}
+
+func NewGeoJsonMultilinestring(points [][][]float64) index.GeoJSON {
+	return &multilinestring{Typ: MultiLineStringType, Vertices: points}
+}
+
+func (p *multilinestring) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func (p *multilinestring) Intersects(other index.GeoJSON) (bool, error) {
+	if p.pls == nil {
+		p.pls = s2PolylinesFromCoordinates(p.Vertices)
+	}
+
+	return checkLineStringsIntersectsShape(p.pls, p, other)
+}
+
+func (p *multilinestring) Contains(other index.GeoJSON) (bool, error) {
+	return checkLineStringsContainsShape(p.Vertices, other)
+}
+
+func (p *multilinestring) Coordinates() [][][]float64 {
+	return p.Vertices
+}
+
+func (p *multilinestring) Members() []index.GeoJSON {
+	lines := make([]index.GeoJSON, len(p.Vertices))
+	for pos, vertices := range p.Vertices {
+		lines[pos] = NewGeoJsonLinestring(vertices)
+	}
+	return lines
+}
+
+//--------------------------------------------------------
+// polygon represents the geoJSON polygon type
+// and it implements the index.GeoJSON interface.
+type polygon struct {
+	Typ      string        `json:"type"`
+	Vertices [][][]float64 `json:"coordinates"`
+	s2pgn    *s2.Polygon
+}
+
+func NewGeoJsonPolygon(points [][][]float64) index.GeoJSON {
+	return &polygon{Typ: PolygonType, Vertices: points}
+}
+
+func (p *polygon) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func (p *polygon) Intersects(other index.GeoJSON) (bool, error) {
+	// make an s2polygon for reuse.
+	if p.s2pgn == nil {
+		p.s2pgn = s2PolygonFromCoordinates(p.Vertices)
+	}
+
+	return checkPolygonIntersectsShape(p.s2pgn, p.Vertices, p, other)
+}
+
+func (p *polygon) Contains(other index.GeoJSON) (bool, error) {
+	// make an s2polygon for reuse.
+	if p.s2pgn == nil {
+		p.s2pgn = s2PolygonFromCoordinates(p.Vertices)
+	}
+
+	return checkMultiPolygonContainsShape([]*s2.Polygon{p.s2pgn}, p, other)
+}
+
+func (p *polygon) Coordinates() [][][]float64 {
+	return p.Vertices
+}
+
+//--------------------------------------------------------
+// multipolygon represents the geoJSON multipolygon type
+// and it implements the index.GeoJSON interface as well as the
+// compositeShap interface.
+type multipolygon struct {
+	Typ      string          `json:"type"`
+	Vertices [][][][]float64 `json:"coordinates"`
+	s2pgns   []*s2.Polygon
+}
+
+func NewGeoJsonMultiPolygon(points [][][][]float64) index.GeoJSON {
+	return &multipolygon{Typ: MultiPolygonType, Vertices: points}
+}
+
+func (p *multipolygon) Type() string {
+	return strings.ToLower(p.Typ)
+}
+
+func (p *multipolygon) Intersects(other index.GeoJSON) (bool, error) {
+	if p.s2pgns == nil {
+		p.s2pgns = make([]*s2.Polygon, len(p.Vertices))
+		for i, vertices := range p.Vertices {
+			pgn := s2PolygonFromCoordinates(vertices)
+			p.s2pgns[i] = pgn
+		}
+	}
+
+	for i, pgn := range p.s2pgns {
+		rv, err := checkPolygonIntersectsShape(pgn, p.Vertices[i], p, other)
+		if rv && err == nil {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+func (p *multipolygon) Contains(other index.GeoJSON) (bool, error) {
+	if p.s2pgns == nil {
+		p.s2pgns = make([]*s2.Polygon, len(p.Vertices))
+		for i, vertices := range p.Vertices {
+			pgn := s2PolygonFromCoordinates(vertices)
+			p.s2pgns[i] = pgn
+		}
+	}
+
+	return checkMultiPolygonContainsShape(p.s2pgns, p, other)
+}
+
+func (p *multipolygon) Coordinates() [][][][]float64 {
+	return p.Vertices
+}
+
+func (p *multipolygon) Members() []index.GeoJSON {
+	polygons := make([]index.GeoJSON, len(p.Vertices))
+	for pos, vertices := range p.Vertices {
+		polygons[pos] = NewGeoJsonPolygon(vertices)
+	}
+	return polygons
+}
+
+//--------------------------------------------------------
+// geometryCollection represents the geoJSON geometryCollection type
+// and it implements the index.GeoJSON interface as well as the
+// compositeShap interface.
+type geometryCollection struct {
+	Typ    string          `json:"type"`
+	Shapes []index.GeoJSON `json:"geometries"`
+}
+
+func (gc *geometryCollection) Type() string {
+	return strings.ToLower(gc.Typ)
+}
+
+func (gc *geometryCollection) Members() []index.GeoJSON {
+	shapes := make([]index.GeoJSON, 0, len(gc.Shapes))
+	for _, shape := range gc.Shapes {
+		if cs, ok := shape.(compositeShape); ok {
+			shapes = append(shapes, cs.Members()...)
+		} else {
+			shapes = append(shapes, shape)
+		}
+	}
+	return shapes
+}
+
+func (gc *geometryCollection) Intersects(other index.GeoJSON) (bool, error) {
+	for _, shape := range gc.Members() {
+
+		intersects, err := shape.Intersects(other)
+		if intersects && err == nil {
+			return true, nil
+		}
+	}
+	return false, nil
+}
+
+func (gc *geometryCollection) Contains(other index.GeoJSON) (bool, error) {
+	// handle composite target shapes explicitly
+	if cs, ok := other.(compositeShape); ok {
+		otherShapes := cs.Members()
+		shapesFoundWithIn := make(map[int]struct{})
+
+	nextShape:
+		for pos, shapeInDoc := range otherShapes {
+			for _, shape := range gc.Members() {
+				within, err := shape.Contains(shapeInDoc)
+				if within && err == nil {
+					shapesFoundWithIn[pos] = struct{}{}
+					continue nextShape
+				}
+			}
+		}
+
+		return len(shapesFoundWithIn) == len(otherShapes), nil
+	}
+
+	for _, shape := range gc.Members() {
+		within, err := shape.Contains(other)
+		if within && err == nil {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+func (gc *geometryCollection) UnmarshalJSON(data []byte) error {
+	tmp := struct {
+		Typ    string            `json:"type"`
+		Shapes []json.RawMessage `json:"geometries"`
+	}{}
+
+	err := json.Unmarshal(data, &tmp)
+	if err != nil {
+		return err
+	}
+	gc.Typ = tmp.Typ
+
+	for _, shape := range tmp.Shapes {
+		var t map[string]interface{}
+		err := json.Unmarshal(shape, &t)
+		if err != nil {
+			return err
+		}
+
+		var typ string
+
+		if val, ok := t["type"]; ok {
+			typ = strings.ToLower(val.(string))
+		} else {
+			continue
+		}
+
+		switch typ {
+		case PointType:
+			var p point
+			err := json.Unmarshal(shape, &p)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &p)
+
+		case MultiPointType:
+			var mp multipoint
+			err := json.Unmarshal(shape, &mp)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &mp)
+
+		case LineStringType:
+			var ls linestring
+			err := json.Unmarshal(shape, &ls)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &ls)
+
+		case MultiLineStringType:
+			var mls multilinestring
+			err := json.Unmarshal(shape, &mls)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &mls)
+
+		case PolygonType:
+			var pgn polygon
+			err := json.Unmarshal(shape, &pgn)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &pgn)
+
+		case MultiPolygonType:
+			var pgn multipolygon
+			err := json.Unmarshal(shape, &pgn)
+			if err != nil {
+				return err
+			}
+			gc.Shapes = append(gc.Shapes, &pgn)
+		}
+	}
+
+	return nil
+}
+
+//--------------------------------------------------------
+// circle represents a custom circle type and it
+// implements the index.GeoJSON interface.
+type circle struct {
+	Typ            string    `json:"type"`
+	Vertices       []float64 `json:"coordinates"`
+	RadiusInMeters float64   `json:"radiusInMeters"`
+	s2cap          *s2.Cap
+}
+
+func NewGeoCircle(points []float64,
+	radiusInMeter float64) index.GeoJSON {
+	return &circle{Typ: CircleType,
+		Vertices:       points,
+		RadiusInMeters: radiusInMeter}
+}
+
+func (c *circle) Type() string {
+	return strings.ToLower(c.Typ)
+}
+
+func (c *circle) Intersects(other index.GeoJSON) (bool, error) {
+	if c.s2cap == nil {
+		c.s2cap = s2Cap(c.Vertices, c.RadiusInMeters)
+	}
+
+	return checkCircleIntersectsShape(c.s2cap, c, other)
+}
+
+func (c *circle) Contains(other index.GeoJSON) (bool, error) {
+	if c.s2cap == nil {
+		c.s2cap = s2Cap(c.Vertices, c.RadiusInMeters)
+	}
+
+	return checkCircleContainsShape(c.s2cap, c, other)
+}
+
+func (c *circle) UnmarshalJSON(data []byte) error {
+	tmp := struct {
+		Typ            string    `json:"type"`
+		Vertices       []float64 `json:"coordinates"`
+		Radius         string    `json:"radius"`
+		RadiusInMeters float64   `json:"radiusInMeters"`
+	}{}
+
+	err := json.Unmarshal(data, &tmp)
+	if err != nil {
+		return err
+	}
+	c.Typ = tmp.Typ
+	c.Vertices = tmp.Vertices
+	c.RadiusInMeters = tmp.RadiusInMeters
+	if tmp.Radius != "" {
+		c.RadiusInMeters, err = ParseDistance(tmp.Radius)
+	}
+
+	return err
+}
+
+//--------------------------------------------------------
+// envelope represents the  envelope/bounding box type and it
+// implements the index.GeoJSON interface.
+type envelope struct {
+	Typ      string      `json:"type"`
+	Vertices [][]float64 `json:"coordinates"`
+	r        *s2.Rect
+}
+
+func NewGeoEnvelope(points [][]float64) index.GeoJSON {
+	return &envelope{Vertices: points, Typ: EnvelopeType}
+}
+
+func (e *envelope) Type() string {
+	return strings.ToLower(e.Typ)
+}
+
+func (e *envelope) Intersects(other index.GeoJSON) (bool, error) {
+	if e.r == nil {
+		e.r = s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+	}
+
+	return checkEnvelopeIntersectsShape(e.r, e, other)
+}
+
+func (e *envelope) Contains(other index.GeoJSON) (bool, error) {
+	if e.r == nil {
+		e.r = s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+	}
+
+	return checkEnvelopeContainsShape(e.r, e, other)
+}
+
+//--------------------------------------------------------
+
+type boundedRectangle struct {
+	minLat float64
+	maxLat float64
+	minLon float64
+	maxLon float64
+}
+
+func NewBoundedRectangle(minLat, minLon, maxLat,
+	maxLon float64) *boundedRectangle {
+	return &boundedRectangle{minLat: minLat,
+		maxLat: maxLat, minLon: minLon, maxLon: maxLon}
+}
+
+func (br *boundedRectangle) Type() string {
+	// placeholder implementation
+	return "boundedRectangle"
+}
+
+func (p *boundedRectangle) Intersects(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+func (p *boundedRectangle) Contains(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+//--------------------------------------------------------
+
+type boundedPolygon struct {
+	coordinates []Point
+}
+
+func NewBoundedPolygon(coordinates []Point) *boundedPolygon {
+	return &boundedPolygon{coordinates: coordinates}
+}
+
+func (bp *boundedPolygon) Type() string {
+	// placeholder implementation
+	return "boundedPolygon"
+}
+
+func (p *boundedPolygon) Intersects(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+func (p *boundedPolygon) Contains(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+//--------------------------------------------------------
+
+type pointDistance struct {
+	dist      float64
+	centerLat float64
+	centerLon float64
+}
+
+func (p *pointDistance) Type() string {
+	// placeholder implementation
+	return "pointDistance"
+}
+
+func NewPointDistance(centerLat, centerLon,
+	dist float64) *pointDistance {
+	return &pointDistance{centerLat: centerLat,
+		centerLon: centerLon, dist: dist}
+}
+
+func (p *pointDistance) Intersects(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+func (p *pointDistance) Contains(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+//--------------------------------------------------------
+
+func (p *Point) Type() string {
+	return PointType
+}
+
+func (p *Point) Intersects(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+func (p *Point) Contains(s index.GeoJSON) (bool, error) {
+	// placeholder implementation
+	return false, nil
+}
+
+//--------------------------------------------------------
+
+// checkCellIntersectsShape checks for intersection between
+// the s2cell and the shape in the document.
+func checkCellIntersectsShape(cell *s2.Cell, shapeIn,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(
+			p2.Vertices[1], p2.Vertices[0]))
+
+		if cell.IntersectsCell(s2cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the array.
+		for _, point := range p2.Vertices {
+			s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(
+				point[1], point[0]))
+
+			if cell.IntersectsCell(s2cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		s2pgn2 := s2PolygonFromCoordinates(p2.Coordinates())
+
+		if s2pgn2.IntersectsCell(*cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		for _, coordinates := range p2.Vertices {
+			s2pgn2 := s2PolygonFromCoordinates(coordinates)
+
+			if s2pgn2.IntersectsCell(*cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a linestring.
+	if p2, ok := other.(*linestring); ok {
+		for _, point := range p2.Vertices {
+			s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(
+				point[1], point[0]))
+			if cell.IntersectsCell(s2cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if p2, ok := other.(*multilinestring); ok {
+		// check the intersection for any linestring in the array.
+		for _, linestrings := range p2.Vertices {
+			for _, point := range linestrings {
+				s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(
+					point[1], point[0]))
+				if cell.IntersectsCell(s2cell) {
+					return true, nil
+				}
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a geometrycollection.
+	if gc, ok := other.(*geometryCollection); ok {
+		// check for intersection across every member shape.
+		if geometryCollectionIntersectsShape(gc, shapeIn) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		s2cap := s2Cap(c.Vertices, c.RadiusInMeters)
+		if s2cap.IntersectsCell(*cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is an envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0],
+			e.Vertices[1])
+
+		if s2rectInDoc.IntersectsCell(*cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s "+
+		" found in document", other.Type())
+}
+
+// checkCellContainsShape checks whether the given shape in
+// in the document is contained with the s2cell.
+func checkCellContainsShape(cells []*s2.Cell,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		for _, cell := range cells {
+			s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				p2.Vertices[1], p2.Vertices[0]))
+
+			if cell.ContainsPoint(s2point) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint, if so containment is
+	// checked for every point in the multipoint with every given cells.
+	if p2, ok := other.(*multipoint); ok {
+		// check the containment for every point in the collection.
+		lookup := make(map[int]struct{})
+		for _, cell := range cells {
+			for pos, point := range p2.Vertices {
+				if _, done := lookup[pos]; done {
+					continue
+				}
+				// already processed all the points in the multipoint.
+				if len(lookup) == len(p2.Vertices) {
+					return true, nil
+				}
+
+				s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(point[1],
+					point[0]))
+
+				if cell.ContainsPoint(s2point) {
+					lookup[pos] = struct{}{}
+				}
+			}
+		}
+
+		return len(lookup) == len(p2.Vertices), nil
+	}
+
+	// as point is a non closed shape, containment isn't feasible
+	// for other higher dimensions.
+	return false, nil
+}
+
+// ------------------------------------------------------------------------
+
+// checkLineStringsIntersectsShape checks whether the given linestrings
+// intersects with the shape in the document.
+func checkLineStringsIntersectsShape(pls []*s2.Polyline, shapeIn,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+
+		if polylineIntersectsPoint(pls, p2.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the collection.
+		for _, point := range p2.Vertices {
+
+			if polylineIntersectsPoint(pls, point) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+
+		if polylineIntersectsPolygons(pls,
+			[][][][]float64{p2.Coordinates()}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		if polylineIntersectsPolygons(pls, p2.Coordinates()) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a linestring.
+	if ls, ok := other.(*linestring); ok {
+
+		if polylineIntersectsPolylines(pls, [][][]float64{ls.Vertices}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if mls, ok := other.(*multilinestring); ok {
+
+		if polylineIntersectsPolylines(pls, mls.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		// check whether the linestring intersects with any of the
+		// shapes Contains a geometrycollection.
+		if geometryCollectionIntersectsShape(gc, shapeIn) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		centre := s2.PointFromLatLng(
+			s2.LatLngFromDegrees(c.Vertices[1], c.Vertices[0]))
+
+		for _, pl := range pls {
+			edge := pl.Edge(0)
+			distance := s2.DistanceFromSegment(centre, edge.V0, edge.V1)
+			r := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+			return distance <= r, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+
+		for _, pl := range pls {
+			edge := pl.Edge(0)
+			latlng1 := s2.LatLngFromPoint(edge.V0)
+			latlng2 := s2.LatLngFromPoint(edge.V1)
+			a := []float64{latlng1.Lng.Degrees(), latlng1.Lat.Degrees()}
+			b := []float64{latlng2.Lng.Degrees(), latlng2.Lat.Degrees()}
+			for j := 0; j < 4; j++ {
+				v1 := s2rectInDoc.Vertex(j)
+				v2 := s2rectInDoc.Vertex((j + 1) % 4)
+				c := []float64{v1.Lng.Degrees(), v1.Lat.Degrees()}
+				d := []float64{v2.Lng.Degrees(), v2.Lat.Degrees()}
+				if doIntersect(a, b, c, d) {
+					return true, nil
+				}
+			}
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s "+
+		"found in document", other.Type())
+}
+
+// checkLineStringsContainsShape checks the containment for
+// points and multipoints for the linestring vertices.
+func checkLineStringsContainsShape(vertices [][][]float64,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p, ok := other.(*point); ok {
+
+		if polylinesContainPoints(vertices, [][]float64{p.Vertices}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if mp, ok := other.(*multipoint); ok {
+
+		if polylinesContainPoints(vertices, mp.Vertices) {
+			return true, nil
+		}
+	}
+
+	return false, nil
+}
+
+// ------------------------------------------------------------------------
+
+// checkPolygonIntersectsShape checks the intersection between the
+// s2 polygon and the other shapes in the documents.
+func checkPolygonIntersectsShape(s2pgn *s2.Polygon, vertices [][][]float64,
+	shapeIn, other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(p2.Vertices[1],
+			p2.Vertices[0]))
+
+		if s2pgn.IntersectsCell(s2cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the collection.
+		for _, point := range p2.Vertices {
+			s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(point[1],
+				point[0]))
+
+			if s2pgn.IntersectsCell(s2cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		s2p2 := s2PolygonFromCoordinates(p2.Coordinates())
+
+		if s2pgn.Intersects(s2p2) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		for _, coordinates := range p2.Vertices {
+			s2p2 := s2PolygonFromCoordinates(coordinates)
+
+			if s2pgn.Intersects(s2p2) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a linestring.
+	if ls, ok := other.(*linestring); ok {
+
+		if polygonsIntersectsLinestrings(s2pgn,
+			[][][]float64{ls.Vertices}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if mls, ok := other.(*multilinestring); ok {
+
+		if polygonsIntersectsLinestrings(s2pgn, mls.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		// check whether the polygon intersects with any of the
+		// member shapes of the geometry collection.
+		if geometryCollectionIntersectsShape(gc, shapeIn) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+
+		cp := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			c.Vertices[1], c.Vertices[0]))
+		radius := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+		projected := s2pgn.Project(&cp)
+		distance := projected.Distance(cp)
+
+		return distance <= radius, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+		s2pgnInDoc := s2PolygonFromS2Rectangle(s2rectInDoc)
+		if s2pgn.Intersects(s2pgnInDoc) {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s "+
+		" found in document", other.Type())
+}
+
+// checkMultiPolygonContainsShape checks whether the given polygons
+// collectively contains the shape in the document.
+func checkMultiPolygonContainsShape(s2pgns []*s2.Polygon,
+	shapeIn, other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2point := s2.PointFromLatLng(
+			s2.LatLngFromDegrees(p2.Vertices[1], p2.Vertices[0]))
+
+		for _, s2pgn := range s2pgns {
+			if s2pgn.ContainsPoint(s2point) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the containment for every point in the collection.
+		pointsWithIn := make(map[int]struct{})
+	nextPoint:
+		for pointIndex, point := range p2.Vertices {
+			s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(point[1],
+				point[0]))
+
+			for _, s2pgn := range s2pgns {
+				if s2pgn.ContainsPoint(s2point) {
+					pointsWithIn[pointIndex] = struct{}{}
+					continue nextPoint
+				}
+			}
+		}
+
+		return len(p2.Vertices) == len(pointsWithIn), nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		s2p2 := s2PolygonFromCoordinates(p2.Coordinates())
+
+		for _, s2pgn := range s2pgns {
+			if s2pgn.Contains(s2p2) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for every polygon in the collection.
+		polygonsWithIn := make(map[int]struct{})
+	nextPolygon:
+		for pgnIndex, coordinates := range p2.Vertices {
+			s2p2 := s2PolygonFromCoordinates(coordinates)
+
+			for _, s2pgn := range s2pgns {
+				if s2pgn.Contains(s2p2) {
+					polygonsWithIn[pgnIndex] = struct{}{}
+					continue nextPolygon
+				}
+			}
+		}
+
+		return len(p2.Vertices) == len(polygonsWithIn), nil
+	}
+
+	// check if the other shape is a linestring.
+	if ls, ok := other.(*linestring); ok {
+
+		if polygonsContainsLineStrings(s2pgns,
+			[][][]float64{ls.Vertices}) {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if mls, ok := other.(*multilinestring); ok {
+		// check whether any of the linestring is inside the polygon.
+		if polygonsContainsLineStrings(s2pgns, mls.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		shapesWithIn := make(map[int]struct{})
+	nextShape:
+		for pos, shape := range gc.Members() {
+			for _, s2pgn := range s2pgns {
+				contains, err := checkMultiPolygonContainsShape(
+					[]*s2.Polygon{s2pgn}, shapeIn, shape)
+				if err == nil && contains {
+					shapesWithIn[pos] = struct{}{}
+					continue nextShape
+				}
+			}
+		}
+		return len(shapesWithIn) == len(gc.Members()), nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		cp := s2.PointFromLatLng(
+			s2.LatLngFromDegrees(c.Vertices[1], c.Vertices[0]))
+		radius := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+
+		for _, s2pgn := range s2pgns {
+
+			if s2pgn.ContainsPoint(cp) {
+				projected := s2pgn.ProjectToBoundary(&cp)
+				distance := projected.Distance(cp)
+				if distance >= radius {
+					return true, nil
+				}
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+		// create a polygon from the rectangle and checks the containment.
+		s2pgnInDoc := s2PolygonFromS2Rectangle(s2rectInDoc)
+		for _, s2pgn := range s2pgns {
+			if s2pgn.Contains(s2pgnInDoc) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s"+
+		" found in document", other.Type())
+}
+
+// ------------------------------------------------------------------------
+
+// checkCircleIntersectsShape checks for intersection of the
+// shape in the document with the circle.
+func checkCircleIntersectsShape(s2cap *s2.Cap, shapeIn,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(p2.Vertices[1],
+			p2.Vertices[0]))
+
+		if s2cap.IntersectsCell(s2cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the collection.
+		for _, point := range p2.Vertices {
+			s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(point[1],
+				point[0]))
+
+			if s2cap.IntersectsCell(s2cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		s2pgn := s2PolygonFromCoordinates(p2.Coordinates())
+		centerPoint := s2cap.Center()
+		projected := s2pgn.Project(&centerPoint)
+		distance := projected.Distance(centerPoint)
+		return distance <= s2cap.Radius(), nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		for _, coordinates := range p2.Vertices {
+			s2pgn := s2PolygonFromCoordinates(coordinates)
+			centerPoint := s2cap.Center()
+			projected := s2pgn.Project(&centerPoint)
+			distance := projected.Distance(centerPoint)
+			return distance <= s2cap.Radius(), nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a linestring.
+	if p2, ok := other.(*linestring); ok {
+		start := s2.LatLngFromDegrees(p2.Vertices[0][1], p2.Vertices[0][0])
+		end := s2.LatLngFromDegrees(p2.Vertices[1][1], p2.Vertices[1][0])
+		pl := s2.PolylineFromLatLngs([]s2.LatLng{start, end})
+		projected, _ := pl.Project(s2cap.Center())
+		distance := projected.Distance(s2cap.Center())
+		return distance <= s2cap.Radius(), nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if p2, ok := other.(*multilinestring); ok {
+		polylines := s2PolylinesFromCoordinates(p2.Vertices)
+		for _, pl := range polylines {
+			projected, _ := pl.Project(s2cap.Center())
+			distance := projected.Distance(s2cap.Center())
+			if distance <= s2cap.Radius() {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		// check whether the circle intersects with any of the
+		// member shapes Contains the geometrycollection.
+		if geometryCollectionIntersectsShape(gc, shapeIn) {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+
+		s2capInDoc := s2Cap(c.Vertices, c.RadiusInMeters)
+
+		if s2cap.Intersects(*s2capInDoc) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+
+		if s2rectInDoc.ContainsPoint(s2cap.Center()) {
+			return true, nil
+		}
+
+		latlngs := []s2.LatLng{s2rectInDoc.Vertex(0), s2rectInDoc.Vertex(1),
+			s2rectInDoc.Vertex(2), s2rectInDoc.Vertex(3), s2rectInDoc.Vertex(0)}
+		pl := s2.PolylineFromLatLngs(latlngs)
+		projected, _ := pl.Project(s2cap.Center())
+		distance := projected.Distance(s2cap.Center())
+		if distance <= s2cap.Radius() {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s"+
+		" found in document", other.Type())
+}
+
+// checkCircleContainsShape checks for containment of the
+// shape in the document with the circle.
+func checkCircleContainsShape(s2cap *s2.Cap,
+	shapeIn, other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			p2.Vertices[1], p2.Vertices[0]))
+
+		if s2cap.ContainsPoint(s2point) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for every point in the collection.
+		for _, point := range p2.Vertices {
+			s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				point[1], point[0]))
+
+			if !s2cap.ContainsPoint(s2point) {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		for _, vertex := range p2.Vertices {
+			for _, v := range vertex {
+				if !s2cap.ContainsPoint(s2.PointFromLatLng(
+					s2.LatLngFromDegrees(v[1], v[0]))) {
+					return false, nil
+				}
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the containment for every polygon in the collection.
+		for _, coordinates := range p2.Vertices {
+			for _, vertex := range coordinates {
+				for _, v := range vertex {
+					if !s2cap.ContainsPoint(s2.PointFromLatLng(
+						s2.LatLngFromDegrees(v[1], v[0]))) {
+						return false, nil
+					}
+				}
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a linestring.
+	if p2, ok := other.(*linestring); ok {
+		start := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			p2.Vertices[0][1], p2.Vertices[0][0]))
+		end := s2.PointFromLatLng(s2.LatLngFromDegrees(
+			p2.Vertices[1][1], p2.Vertices[1][0]))
+		// check whether both the end vertices are inside the circle.
+		if s2cap.ContainsPoint(start) && s2cap.ContainsPoint(end) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if p2, ok := other.(*multilinestring); ok {
+		for _, lines := range p2.Vertices {
+			start := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				lines[0][1], lines[0][0]))
+			end := s2.PointFromLatLng(s2.LatLngFromDegrees(
+				lines[1][1], lines[1][0]))
+			// check whether both the end vertices are inside the circle.
+			if !(s2cap.ContainsPoint(start) && s2cap.ContainsPoint(end)) {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		for _, shape := range gc.Members() {
+			contains, err := shapeIn.Contains(shape)
+			if err == nil && !contains {
+				return false, nil
+			}
+		}
+		return true, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		s2capInDoc := s2Cap(c.Vertices, c.RadiusInMeters)
+
+		if s2cap.Contains(*s2capInDoc) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+
+		for i := 0; i < 4; i++ {
+			if !s2cap.ContainsPoint(
+				s2.PointFromLatLng(s2rectInDoc.Vertex(i))) {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s"+
+		" found in document", other.Type())
+}
+
+// ------------------------------------------------------------------------
+
+// checkEnvelopeIntersectsShape checks whether the given shape in
+// the document is intersecting Contains the envelope/rectangle.
+func checkEnvelopeIntersectsShape(s2rect *s2.Rect, shapeIn,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(p2.Vertices[1],
+			p2.Vertices[0]))
+
+		if s2rect.IntersectsCell(s2cell) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the collection.
+		for _, point := range p2.Vertices {
+			s2cell := s2.CellFromLatLng(s2.LatLngFromDegrees(point[1],
+				point[0]))
+
+			if s2rect.IntersectsCell(s2cell) {
+				return true, nil
+			}
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a polygon.
+	if pgn, ok := other.(*polygon); ok {
+
+		if rectangleIntersectsWithPolygons(s2rect,
+			[][][][]float64{pgn.Vertices}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if mpgn, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		if rectangleIntersectsWithPolygons(s2rect, mpgn.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a linestring.
+	if ls, ok := other.(*linestring); ok {
+
+		if rectangleIntersectsWithLineStrings(s2rect,
+			[][][]float64{ls.Vertices}) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if mls, ok := other.(*multilinestring); ok {
+
+		if rectangleIntersectsWithLineStrings(s2rect, mls.Vertices) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		// check for the intersection of every member shape
+		// within the geometrycollection.
+		if geometryCollectionIntersectsShape(gc, shapeIn) {
+			return true, nil
+		}
+		return false, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		s2capInDoc := s2Cap(c.Vertices, c.RadiusInMeters)
+		s2pgn := s2PolygonFromS2Rectangle(s2rect)
+		cp := s2capInDoc.Center()
+		projected := s2pgn.Project(&cp)
+		distance := projected.Distance(s2capInDoc.Center())
+		return distance <= s2capInDoc.Radius(), nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+
+		if s2rect.Intersects(*s2rectInDoc) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s"+
+		" found in document", other.Type())
+}
+
+// checkEnvelopeContainsShape checks whether the given shape in
+// the document is contained Contains the envelope/rectangle.
+func checkEnvelopeContainsShape(s2rect *s2.Rect, shapeIn,
+	other index.GeoJSON) (bool, error) {
+	// check if the other shape is a point.
+	if p2, ok := other.(*point); ok {
+		s2LatLng := s2.LatLngFromDegrees(p2.Vertices[1], p2.Vertices[0])
+
+		if s2rect.ContainsLatLng(s2LatLng) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a multipoint.
+	if p2, ok := other.(*multipoint); ok {
+		// check the intersection for any point in the collection.
+		for _, point := range p2.Vertices {
+			s2LatLng := s2.LatLngFromDegrees(point[1], point[0])
+
+			if !s2rect.ContainsLatLng(s2LatLng) {
+				return false, nil
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a polygon.
+	if p2, ok := other.(*polygon); ok {
+		for _, points := range p2.Vertices {
+			for _, point := range points {
+				if !s2rect.ContainsLatLng(s2.LatLngFromDegrees(point[1],
+					point[0])) {
+					return false, nil
+				}
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a multipolygon.
+	if p2, ok := other.(*multipolygon); ok {
+		// check the intersection for any polygon in the collection.
+		for _, coordinates := range p2.Vertices {
+			for _, points := range coordinates {
+				for _, point := range points {
+					if !s2rect.ContainsLatLng(s2.LatLngFromDegrees(point[1],
+						point[0])) {
+						return false, nil
+					}
+				}
+			}
+		}
+
+		return true, nil
+	}
+
+	// check if the other shape is a linestring.
+	if p2, ok := other.(*linestring); ok {
+		for _, point := range p2.Vertices {
+			s2LatLng := s2.LatLngFromDegrees(point[1], point[0])
+			if !s2rect.ContainsLatLng(s2LatLng) {
+				return false, nil
+			}
+		}
+		return true, nil
+	}
+
+	// check if the other shape is a multilinestring.
+	if p2, ok := other.(*multilinestring); ok {
+		for _, points := range p2.Vertices {
+			for _, point := range points {
+				s2LatLng := s2.LatLngFromDegrees(point[1], point[0])
+				if !s2rect.ContainsLatLng(s2LatLng) {
+					return false, nil
+				}
+			}
+		}
+		return true, nil
+	}
+
+	if gc, ok := other.(*geometryCollection); ok {
+		for _, shape := range gc.Members() {
+			contains, err := shapeIn.Contains(shape)
+			if err == nil && !contains {
+				return false, nil
+			}
+		}
+		return true, nil
+	}
+
+	// check if the other shape is a circle.
+	if c, ok := other.(*circle); ok {
+		cp := s2.PointFromLatLng(s2.LatLngFromDegrees(c.Vertices[1],
+			c.Vertices[0]))
+		angle := radiusInMetersToS1Angle(float64(c.RadiusInMeters))
+		s2capInDoc := s2.CapFromCenterAngle(cp, angle)
+
+		if s2rect.Contains(s2capInDoc.RectBound()) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	// check if the other shape is a envelope.
+	if e, ok := other.(*envelope); ok {
+		s2rectInDoc := s2RectFromBounds(e.Vertices[0], e.Vertices[1])
+
+		if s2rect.Contains(*s2rectInDoc) {
+			return true, nil
+		}
+
+		return false, nil
+	}
+
+	return false, fmt.Errorf("unknown geojson type: %s"+
+		" found in document", other.Type())
+}
diff --git a/geo/geojson_shapes_util.go b/geo/geojson_shapes_util.go
new file mode 100644
index 00000000..ee6d9887
--- /dev/null
+++ b/geo/geojson_shapes_util.go
@@ -0,0 +1,354 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package geo
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+// FilterGeoShapesOnRelation extracts the shapes in the document, apply
+// the `relation` filter and confirms whether the shape in the document
+//  satisfies the given relation.
+func FilterGeoShapesOnRelation(shape index.GeoJSON, targetShapeBytes []byte,
+	relation string) (bool, error) {
+
+	shapeInDoc, err := extractShapesFromBytes(targetShapeBytes)
+	if err != nil {
+		return false, err
+	}
+
+	return filterShapes(shape, shapeInDoc, relation)
+}
+
+// extractShapesFromBytes unmarshal the bytes to retrieve the
+// embedded geojson shape.
+func extractShapesFromBytes(targetShapeBytes []byte) (
+	index.GeoJSON, error) {
+	meta := struct {
+		Typ string `json:"type"`
+	}{}
+
+	err := json.Unmarshal(targetShapeBytes, &meta)
+	if err != nil {
+		return nil, err
+	}
+
+	var shapeInDoc index.GeoJSON
+
+	switch strings.ToLower(meta.Typ) {
+	case PolygonType:
+		shapeInDoc = &polygon{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case MultiPolygonType:
+		shapeInDoc = &multipolygon{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case PointType:
+		shapeInDoc = &point{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case MultiPointType:
+		shapeInDoc = &multipoint{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case LineStringType:
+		shapeInDoc = &linestring{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case MultiLineStringType:
+		shapeInDoc = &multilinestring{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case GeometryCollectionType:
+		shapeInDoc = &geometryCollection{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case CircleType:
+		shapeInDoc = &circle{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	case EnvelopeType:
+		shapeInDoc = &envelope{Typ: meta.Typ}
+		err := json.Unmarshal(targetShapeBytes, shapeInDoc)
+		if err != nil {
+			return nil, err
+		}
+		return shapeInDoc, nil
+
+	}
+
+	return nil, nil
+}
+
+// filterShapes applies the given relation between the query shape
+// and the shape in the document.
+func filterShapes(shape index.GeoJSON,
+	shapeInDoc index.GeoJSON, relation string) (bool, error) {
+
+	if relation == "intersects" {
+		return shape.Intersects(shapeInDoc)
+	}
+
+	if relation == "contains" {
+		return shapeInDoc.Contains(shape)
+	}
+
+	if relation == "within" {
+		return shape.Contains(shapeInDoc)
+	}
+
+	return false, fmt.Errorf("unknown relation: %s", relation)
+}
+
+// ParseGeoJSONShape unmarshals the geojson/circle/envelope shape
+// embedded in the given bytes.
+func ParseGeoJSONShape(input []byte) (index.GeoJSON, error) {
+	var sType string
+	var tmp struct {
+		Typ string `json:"type"`
+	}
+	err := json.Unmarshal(input, &tmp)
+	if err != nil {
+		return nil, err
+	}
+
+	sType = strings.ToLower(tmp.Typ)
+
+	switch sType {
+	case PolygonType:
+		var rv polygon
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case MultiPolygonType:
+		var rv multipolygon
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case PointType:
+		var rv point
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case MultiPointType:
+		var rv multipoint
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case LineStringType:
+		var rv linestring
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case MultiLineStringType:
+		var rv multilinestring
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case GeometryCollectionType:
+		var rv geometryCollection
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case CircleType:
+		var rv circle
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	case EnvelopeType:
+		var rv envelope
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+
+	default:
+		return nil, fmt.Errorf("unknown shape type: %s", sType)
+	}
+
+	return nil, err
+}
+
+// NewGeoJsonShape instantiate a geojson shape/circle or
+// an envelope from the given coordinates and type.
+func NewGeoJsonShape(coordinates [][][][]float64, typ string) (
+	rv index.GeoJSON, value []byte, err error) {
+	if len(coordinates) == 0 {
+		return nil, nil, fmt.Errorf("missing coordinates")
+	}
+
+	typ = strings.ToLower(typ)
+
+	switch typ {
+	case PointType:
+		rv = &point{Typ: typ, Vertices: coordinates[0][0][0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case MultiPointType:
+		rv = &multipoint{Typ: typ, Vertices: coordinates[0][0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case LineStringType:
+		rv = &linestring{Typ: typ, Vertices: coordinates[0][0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case MultiLineStringType:
+		rv = &multilinestring{Typ: typ, Vertices: coordinates[0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case PolygonType:
+		rv = &polygon{Typ: typ, Vertices: coordinates[0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case MultiPolygonType:
+		rv = &multipolygon{Typ: typ, Vertices: coordinates}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	case EnvelopeType:
+		rv = &envelope{Typ: typ, Vertices: coordinates[0][0]}
+		value, err = json.Marshal(rv)
+		if err != nil {
+			return rv, nil, err
+		}
+
+	default:
+		return rv, nil, fmt.Errorf("unknown shape type: %s", typ)
+	}
+
+	value = append(GlueBytes, value...)
+	return rv, value, nil
+}
+
+// NewGeometryCollection instantiate a geometrycollection
+// and prefix the byte contents with certain glue bytes that
+// can be used later while filering the doc values.
+func NewGeometryCollection(coordinates [][][][][]float64,
+	typs []string) (index.GeoJSON, []byte, error) {
+	shapes := make([]index.GeoJSON, 0, len(coordinates))
+	for i, vertices := range coordinates {
+		s, _, err := NewGeoJsonShape(vertices, typs[i])
+		if err != nil {
+			continue
+		}
+		shapes = append(shapes, s)
+	}
+
+	var gc geometryCollection
+	gc.Typ = GeometryCollectionType
+	gc.Shapes = shapes
+	vbytes, err := json.Marshal(&gc)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	vbytes = append(GlueBytes, vbytes...)
+	return &gc, vbytes, nil
+}
+
+// NewGeoCircleShape instantiate a circle shape and
+// prefix the byte contents with certain glue bytes that
+// can be used later while filering the doc values.
+func NewGeoCircleShape(cp []float64,
+	radiusInMeter float64) (*circle, []byte, error) {
+	rv := &circle{Typ: CircleType, Vertices: cp,
+		RadiusInMeters: radiusInMeter}
+	vbytes, err := json.Marshal(rv)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	vbytes = append(GlueBytes, vbytes...)
+	return rv, vbytes, nil
+}
diff --git a/geo/parse.go b/geo/parse.go
index 8286805f..7725da0a 100644
--- a/geo/parse.go
+++ b/geo/parse.go
@@ -179,3 +179,229 @@ type later interface {
 type lnger interface {
 	Lng() float64
 }
+
+// GlueBytes primarily for quicker filtering of docvalues
+// during the filtering phase.
+var GlueBytes = []byte("##")
+
+var GlueBytesOffset = len(GlueBytes)
+
+func extractCoordinates(thing interface{}) []float64 {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil
+	}
+
+	if thingVal.Kind() == reflect.Slice {
+		// must be length 2
+		if thingVal.Len() == 2 {
+			var foundLon, foundLat bool
+			var lon, lat float64
+			first := thingVal.Index(0)
+			if first.CanInterface() {
+				firstVal := first.Interface()
+				lon, foundLon = extractNumericVal(firstVal)
+			}
+			second := thingVal.Index(1)
+			if second.CanInterface() {
+				secondVal := second.Interface()
+				lat, foundLat = extractNumericVal(secondVal)
+			}
+
+			if !foundLon || !foundLat {
+				return nil
+			}
+
+			return []float64{lon, lat}
+		}
+	}
+	return nil
+}
+
+func extract2DCoordinates(thing interface{}) [][]float64 {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil
+	}
+
+	rv := make([][]float64, 0, 8)
+	if thingVal.Kind() == reflect.Slice {
+		for j := 0; j < thingVal.Len(); j++ {
+			edges := thingVal.Index(j).Interface()
+			if es, ok := edges.([]interface{}); ok {
+				rv = append(rv, extractCoordinates(es))
+			}
+		}
+
+		return rv
+	}
+
+	return nil
+}
+
+func extract3DCoordinates(thing interface{}) (c [][][]float64) {
+	coords := reflect.ValueOf(thing)
+	for i := 0; i < coords.Len(); i++ {
+		vals := coords.Index(i)
+
+		edges := vals.Interface()
+		if es, ok := edges.([]interface{}); ok {
+			loop := extract2DCoordinates(es)
+			if len(loop) > 0 {
+				c = append(c, loop)
+			}
+		}
+	}
+
+	return c
+}
+
+func extract4DCoordinates(thing interface{}) (rv [][][][]float64) {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil
+	}
+
+	if thingVal.Kind() == reflect.Slice {
+		for j := 0; j < thingVal.Len(); j++ {
+			c := extract3DCoordinates(thingVal.Index(j).Interface())
+			rv = append(rv, c)
+		}
+	}
+
+	return rv
+}
+
+func extractGeoShape(thing interface{}) ([][][][]float64, string, bool) {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil, "", false
+	}
+
+	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)
+				continue
+			}
+			if iter.Key().String() == "coordinates" {
+				coordValue = iter.Value().Interface()
+			}
+		}
+	}
+
+	return ExtractGeoShapeCoordinates(coordValue, typ)
+}
+
+// ExtractGeometryCollection takes an interface{} and tries it's best to
+// interpret all the member geojson shapes within it.
+func ExtractGeometryCollection(thing interface{}) ([][][][][]float64, []string, bool) {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil, nil, false
+	}
+	var rv [][][][][]float64
+	var types []string
+	var f bool
+
+	if thingVal.Kind() == reflect.Map {
+		iter := thingVal.MapRange()
+		for iter.Next() {
+
+			if iter.Key().String() == "type" {
+				continue
+			}
+
+			if iter.Key().String() == "geometries" {
+				collection := iter.Value().Interface()
+				items := reflect.ValueOf(collection)
+
+				for j := 0; j < items.Len(); j++ {
+					coords, shape, found := extractGeoShape(items.Index(j).Interface())
+					if found {
+						f = found
+						rv = append(rv, coords)
+						types = append(types, shape)
+					}
+				}
+			}
+		}
+	}
+
+	return rv, types, f
+}
+
+// ExtractCircle takes an interface{} and tries it's best to
+// interpret the center point coordinates and the radius for a
+// given circle shape.
+func ExtractCircle(thing interface{}) ([]float64, float64, bool) {
+	thingVal := reflect.ValueOf(thing)
+	if !thingVal.IsValid() {
+		return nil, 0, false
+	}
+	var rv []float64
+	var radius float64
+	var radiusStr string
+
+	if thingVal.Kind() == reflect.Map {
+		iter := thingVal.MapRange()
+		for iter.Next() {
+
+			if iter.Key().String() == "radius" {
+				radiusStr = iter.Value().Interface().(string)
+				r, err := ParseDistance(radiusStr)
+				if err != nil {
+					return nil, 0, false
+				}
+				radius = r
+				continue
+			}
+
+			if iter.Key().String() == "coordinates" {
+				lng, lat, found := ExtractGeoPoint(iter.Value().Interface())
+				if !found {
+					return nil, 0, false
+				}
+				rv = append(rv, lng)
+				rv = append(rv, lat)
+			}
+		}
+	}
+
+	return rv, radius, true
+}
+
+// ExtractGeoShapeCoordinates takes an interface{} and tries it's best to
+// interpret the coordinates for any of the given geoshape typ like
+// a point, multipoint, linestring, multilinestring, polygon, multipolygon,
+func ExtractGeoShapeCoordinates(coordValue interface{},
+	typ string) ([][][][]float64, string, bool) {
+	var rv [][][][]float64
+	if typ == PointType {
+		rv = [][][][]float64{{{extractCoordinates(coordValue)}}}
+		return rv, typ, true
+	}
+
+	if typ == MultiPointType || typ == LineStringType ||
+		typ == EnvelopeType {
+		rv = [][][][]float64{{extract2DCoordinates(coordValue)}}
+		return rv, typ, true
+	}
+
+	if typ == PolygonType || typ == MultiLineStringType {
+		rv = [][][][]float64{extract3DCoordinates(coordValue)}
+		return rv, typ, true
+	}
+
+	if typ == MultiPolygonType {
+		rv = extract4DCoordinates(coordValue)
+		return rv, typ, true
+	}
+
+	return rv, typ, false
+}
diff --git a/go.mod b/go.mod
index 47e1500c..2bbe0222 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.13
 require (
 	github.com/RoaringBitmap/roaring v0.9.4
 	github.com/bits-and-blooms/bitset v1.2.0
-	github.com/blevesearch/bleve_index_api v1.0.2-0.20220322163856-157bc7f3ed4d
+	github.com/blevesearch/bleve_index_api v1.0.2-0.20220328092731-fdf526452081
 	github.com/blevesearch/geo v0.1.9
 	github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475
 	github.com/blevesearch/go-porterstemmer v1.0.3
@@ -23,6 +23,7 @@ require (
 	github.com/blevesearch/zapx/v14 v14.3.3
 	github.com/blevesearch/zapx/v15 v15.3.3
 	github.com/couchbase/moss v0.2.0
+	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551
 	github.com/golang/protobuf v1.3.2
 	github.com/spf13/cobra v0.0.5
 	go.etcd.io/bbolt v1.3.5
diff --git a/go.sum b/go.sum
index f3cb6c28..ac2f2bd5 100644
--- a/go.sum
+++ b/go.sum
@@ -5,8 +5,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
 github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/blevesearch/bleve_index_api v1.0.1/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
-github.com/blevesearch/bleve_index_api v1.0.2-0.20220322163856-157bc7f3ed4d h1:fu5XNK9duzvlgrb2CQv4Lw/P+/W/UAxQlp8DRq2dpAI=
-github.com/blevesearch/bleve_index_api v1.0.2-0.20220322163856-157bc7f3ed4d/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
+github.com/blevesearch/bleve_index_api v1.0.2-0.20220328092731-fdf526452081 h1:fNEcBSZemFr2krbmxaEeejjR+31J+12WALApLXEFhPQ=
+github.com/blevesearch/bleve_index_api v1.0.2-0.20220328092731-fdf526452081/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
 github.com/blevesearch/geo v0.1.9 h1:3PkWcg/Os+AVezVKptfHVv6HbIL0qZ9hodQTnoBnLn8=
 github.com/blevesearch/geo v0.1.9/go.mod h1:XqONL2MSA0A3hDWq35mX+dkXbJwq+LNdSgDHvqOhQZM=
 github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
diff --git a/index/scorch/scorch.go b/index/scorch/scorch.go
index f6824b34..82209372 100644
--- a/index/scorch/scorch.go
+++ b/index/scorch/scorch.go
@@ -612,10 +612,10 @@ type customAnalyzerPluginInitFunc func(field index.Field)
 
 func (s *Scorch) setSpatialAnalyzerPlugin(f index.Field) {
 	if s.segPlugin != nil {
-		// check whether the current field is a custom tokenisable
+		// check whether the current field is a custom tokenizable
 		// spatial field then set the spatial analyser plugin for
 		// overriding the tokenisation during the analysis stage.
-		if sf, ok := f.(index.TokenisableSpatialField); ok {
+		if sf, ok := f.(index.TokenizableSpatialField); ok {
 			sf.SetSpatialAnalyzerPlugin(s.spatialPlugin)
 		}
 	}
diff --git a/mapping/document.go b/mapping/document.go
index ae881dba..06b4990c 100644
--- a/mapping/document.go
+++ b/mapping/document.go
@@ -77,7 +77,7 @@ func (dm *DocumentMapping) Validate(cache *registry.Cache) error {
 			}
 		}
 		switch field.Type {
-		case "text", "datetime", "number", "boolean", "geopoint", "IP":
+		case "text", "datetime", "number", "boolean", "geopoint", "geoshape", "IP":
 		default:
 			return fmt.Errorf("unknown field type: '%s'", field.Type)
 		}
@@ -427,7 +427,9 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
 		if subDocMapping != nil {
 			// index by explicit mapping
 			for _, fieldMapping := range subDocMapping.Fields {
-				if fieldMapping.Type == "geopoint" {
+				if fieldMapping.Type == "geoshape" {
+					fieldMapping.processGeoShape(property, pathString, path, indexes, context)
+				} else if fieldMapping.Type == "geopoint" {
 					fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
 				} else {
 					fieldMapping.processString(propertyValueString, pathString, path, indexes, context)
@@ -510,6 +512,8 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
 				for _, fieldMapping := range subDocMapping.Fields {
 					if fieldMapping.Type == "geopoint" {
 						fieldMapping.processGeoPoint(property, pathString, path, indexes, context)
+					} else if fieldMapping.Type == "geoshape" {
+						fieldMapping.processGeoShape(property, pathString, path, indexes, context)
 					}
 				}
 			}
@@ -526,6 +530,8 @@ func (dm *DocumentMapping) processProperty(property interface{}, path []string,
 					if ok {
 						fieldMapping.processIP(ip, pathString, path, indexes, context)
 					}
+				case "geoshape":
+					fieldMapping.processGeoShape(property, pathString, path, indexes, context)
 				}
 			}
 		}
diff --git a/mapping/field.go b/mapping/field.go
index 6e4f5290..962696eb 100644
--- a/mapping/field.go
+++ b/mapping/field.go
@@ -18,6 +18,8 @@ import (
 	"encoding/json"
 	"fmt"
 	"net"
+	"reflect"
+	"strings"
 	"time"
 
 	"github.com/blevesearch/bleve/v2/analysis/analyzer/keyword"
@@ -182,6 +184,17 @@ func NewIPFieldMapping() *FieldMapping {
 	}
 }
 
+// NewGeoShapeFieldMapping returns a default field mapping
+// for geoshapes
+func NewGeoShapeFieldMapping() *FieldMapping {
+	return &FieldMapping{
+		Type:         "geoshape",
+		Index:        true,
+		IncludeInAll: true,
+		DocValues:    true,
+	}
+}
+
 // Options returns the indexing options for this field.
 func (fm *FieldMapping) Options() index.FieldIndexingOptions {
 	var rv index.FieldIndexingOptions
@@ -302,6 +315,82 @@ 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)
+	if err != nil {
+		return
+	}
+
+	if shape == geo.CircleType {
+		center, radiusInMeter, found := geo.ExtractCircle(propertyMightBeGeoShape)
+		if found {
+			fieldName := getFieldName(pathString, path, fm)
+			options := fm.Options()
+			field := document.NewGeoCircleFieldWithIndexingOptions(fieldName,
+				indexes, center, radiusInMeter, options)
+			context.doc.AddField(field)
+
+			if !fm.IncludeInAll {
+				context.excludedFromAll = append(context.excludedFromAll, fieldName)
+			}
+		}
+	} else if shape == geo.GeometryCollectionType {
+		coordinates, shapes, found := geo.ExtractGeometryCollection(propertyMightBeGeoShape)
+		if found {
+			fieldName := getFieldName(pathString, path, fm)
+			options := fm.Options()
+			field := document.NewGeometryCollectionFieldWithIndexingOptions(fieldName,
+				indexes, coordinates, shapes, options)
+			context.doc.AddField(field)
+
+			if !fm.IncludeInAll {
+				context.excludedFromAll = append(context.excludedFromAll, fieldName)
+			}
+		}
+	} else {
+		coordinates, shape, found := geo.ExtractGeoShapeCoordinates(coordValue, shape)
+		if found {
+			fieldName := getFieldName(pathString, path, fm)
+			options := fm.Options()
+			field := document.NewGeoShapeFieldWithIndexingOptions(fieldName,
+				indexes, coordinates, shape, options)
+			context.doc.AddField(field)
+
+			if !fm.IncludeInAll {
+				context.excludedFromAll = append(context.excludedFromAll, fieldName)
+			}
+		}
+	}
+}
+
 func (fm *FieldMapping) analyzerForField(path []string, context *walkContext) *analysis.Analyzer {
 	analyzerName := fm.Analyzer
 	if analyzerName == "" {
diff --git a/search/query/geo_shape.go b/search/query/geo_shape.go
new file mode 100644
index 00000000..47c8a1d6
--- /dev/null
+++ b/search/query/geo_shape.go
@@ -0,0 +1,95 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package query
+
+import (
+	"encoding/json"
+
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/mapping"
+	"github.com/blevesearch/bleve/v2/search"
+	"github.com/blevesearch/bleve/v2/search/searcher"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+type Geometry struct {
+	Shape    index.GeoJSON `json:"shape"`
+	Relation string        `json:"relation"`
+}
+
+type GeoShapeQuery struct {
+	Geometry Geometry `json:"geometry"`
+	FieldVal string   `json:"field,omitempty"`
+	BoostVal *Boost   `json:"boost,omitempty"`
+}
+
+func NewGeoShapeQuery(coordinates [][][][]float64, typ, relation string) *GeoShapeQuery {
+	s, _, err := geo.NewGeoJsonShape(coordinates, typ)
+	if err != nil {
+		return nil
+	}
+
+	return &GeoShapeQuery{Geometry: Geometry{Shape: s, Relation: relation}}
+}
+
+func (q *GeoShapeQuery) SetBoost(b float64) {
+	boost := Boost(b)
+	q.BoostVal = &boost
+}
+
+func (q *GeoShapeQuery) Boost() float64 {
+	return q.BoostVal.Value()
+}
+
+func (q *GeoShapeQuery) SetField(f string) {
+	q.FieldVal = f
+}
+
+func (q *GeoShapeQuery) Field() string {
+	return q.FieldVal
+}
+
+func (q *GeoShapeQuery) Searcher(i index.IndexReader,
+	m mapping.IndexMapping, options search.SearcherOptions) (search.Searcher, error) {
+	field := q.FieldVal
+	if q.FieldVal == "" {
+		field = m.DefaultSearchField()
+	}
+
+	return searcher.NewGeoShapeSearcher(i, q.Geometry.Shape, q.Geometry.Relation, field, q.BoostVal.Value(), options)
+}
+
+func (q *GeoShapeQuery) Validate() error {
+	return nil
+}
+
+func (q *Geometry) UnmarshalJSON(data []byte) error {
+	tmp := struct {
+		Shape    json.RawMessage `json:"shape"`
+		Relation string          `json:"relation"`
+	}{}
+
+	err := json.Unmarshal(data, &tmp)
+	if err != nil {
+		return err
+	}
+
+	q.Shape, err = geo.ParseGeoJSONShape(tmp.Shape)
+	if err != nil {
+		return err
+	}
+	q.Relation = tmp.Relation
+	return nil
+}
diff --git a/search/query/query.go b/search/query/query.go
index 7f2781c3..60add3b6 100644
--- a/search/query/query.go
+++ b/search/query/query.go
@@ -282,6 +282,16 @@ func ParseQuery(input []byte) (Query, error) {
 		}
 		return &rv, nil
 	}
+
+	_, hasGeo := tmp["geometry"]
+	if hasGeo {
+		var rv GeoShapeQuery
+		err := json.Unmarshal(input, &rv)
+		if err != nil {
+			return nil, err
+		}
+		return &rv, nil
+	}
 	return nil, fmt.Errorf("unknown query type")
 }
 
diff --git a/search/searcher/search_geoshape.go b/search/searcher/search_geoshape.go
new file mode 100644
index 00000000..b26cbde1
--- /dev/null
+++ b/search/searcher/search_geoshape.go
@@ -0,0 +1,80 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"bytes"
+	"fmt"
+
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func NewGeoShapeSearcher(indexReader index.IndexReader, shape index.GeoJSON,
+	relation string, field string, boost float64,
+	options search.SearcherOptions) (search.Searcher, error) {
+
+	if sr, ok := indexReader.(index.SpatialIndexPlugin); ok {
+		tp, err := sr.GetSpatialAnalyzerPlugin("s2")
+		if err == nil {
+			// obtain the query tokens.
+			terms := tp.GetQueryTokens(shape)
+			mSearcher, err := NewMultiTermSearcher(indexReader, terms,
+				field, boost, options, false)
+			if err != nil {
+				return nil, err
+			}
+
+			dvReader, err := indexReader.DocValueReader([]string{field})
+			if err != nil {
+				return nil, err
+			}
+
+			return NewFilteringSearcher(mSearcher,
+				buildRelationFilterOnShapes(dvReader, field, relation, shape)), nil
+		}
+	}
+
+	return nil, fmt.Errorf("no SpatialIndexPlugin implementation found")
+}
+
+func buildRelationFilterOnShapes(dvReader index.DocValueReader, field string,
+	relation string, shape index.GeoJSON) FilterFunc {
+	return func(d *search.DocumentMatch) bool {
+		var found bool
+
+		err := dvReader.VisitDocValues(d.IndexInternalID,
+			func(field string, term []byte) {
+				// only consider the values which are GlueBytes prefixed.
+				if len(term) > geo.GlueBytesOffset {
+					if bytes.Equal(geo.GlueBytes, term[:geo.GlueBytesOffset]) {
+
+						v, err := geo.FilterGeoShapesOnRelation(shape,
+							term[geo.GlueBytesOffset:], relation)
+						if err == nil && v {
+							found = true
+						}
+					}
+				}
+			})
+
+		if err == nil && found {
+			return found
+		}
+
+		return false
+	}
+}


Test patch:
diff --git a/geo/parse_test.go b/geo/parse_test.go
index 334b65ab..1b46d8c8 100644
--- a/geo/parse_test.go
+++ b/geo/parse_test.go
@@ -102,7 +102,7 @@ func TestExtractGeoPoint(t *testing.T) {
 		},
 		// test going throug interface
 		{
-			in: &s1{
+			in: &s11{
 				lon: 4.0,
 				lat: 6.9,
 			},
@@ -174,16 +174,16 @@ func TestExtractGeoPoint(t *testing.T) {
 	}
 }
 
-type s1 struct {
+type s11 struct {
 	lon float64
 	lat float64
 }
 
-func (s *s1) Lon() float64 {
+func (s *s11) Lon() float64 {
 	return s.lon
 }
 
-func (s *s1) Lat() float64 {
+func (s *s11) Lat() float64 {
 	return s.lat
 }
 
diff --git a/search/query/query_test.go b/search/query/query_test.go
index f8a9227d..ffa9c5b8 100644
--- a/search/query/query_test.go
+++ b/search/query/query_test.go
@@ -20,6 +20,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/blevesearch/bleve/v2/geo"
 	"github.com/blevesearch/bleve/v2/mapping"
 )
 
@@ -441,3 +442,67 @@ func TestDumpQuery(t *testing.T) {
 		t.Fatalf("query:\n%s\ndiffers from expected:\n%s", s, wanted)
 	}
 }
+
+func TestGeoShapeQuery(t *testing.T) {
+	tests := []struct {
+		input  []byte
+		output Query
+		err    bool
+	}{
+		{
+			input: []byte(`{      
+				"field" : "region",
+				 "geometry": {
+					 "shape": {
+						 "type": "polygon",
+						"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},
+					{67.0166015625, 21.57571893245848}, {68.8623046875, 9.145486056167277},
+					{83.1884765625, 4.083452772038619}, {88.9892578125, 22.67484735118852},
+					{74.1357421875, 30.600093873550072}}}}, geo.PolygonType, "intersects")
+				q.SetField("region")
+				return q
+			}(),
+		},
+	}
+
+	for i, test := range tests {
+		actual, err := ParseQuery(test.input)
+		if err != nil && test.err == false {
+			t.Errorf("error %v for %d", err, i)
+		}
+
+		if !reflect.DeepEqual(test.output, actual) {
+			t.Errorf("expected: %#v, got: %#v for %s", test.output, actual, string(test.input))
+		}
+	}
+}
diff --git a/search/searcher/search_geoshape_circle_test.go b/search/searcher/search_geoshape_circle_test.go
new file mode 100644
index 00000000..068414e2
--- /dev/null
+++ b/search/searcher/search_geoshape_circle_test.go
@@ -0,0 +1,423 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJsonCircleIntersectsQuery(t *testing.T) {
+	tests := []struct {
+		centrePoint    []float64
+		radiusInMeters float64
+		field          string
+		want           []string
+	}{
+		// test intersecting query circle for polygon1.
+		{[]float64{77.68115043640137, 12.94663769274367}, 200,
+			"geometry", []string{"polygon1"}},
+
+		// test intersecting query circle for polygon1, circle1 and linestring1.
+		{[]float64{77.68115043640137, 12.94663769274367}, 750,
+			"geometry", []string{"polygon1", "circle1", "linestring1"}},
+
+		// test intersecting query circle for linestring2.
+		{[]float64{77.69591331481932, 12.92756503709986}, 250,
+			"geometry", []string{"linestring2"}},
+
+		// test intersecting query circle for circle1.
+		{[]float64{77.6767, 12.9422}, 250, "geometry", []string{"circle1"}},
+
+		// test intersecting query circle for point1, envelope1 and linestring3.
+		{[]float64{81.243896484375, 26.22444694563432}, 90000,
+			"geometry", []string{"point1", "envelope1", "linestring3"}},
+
+		// test intersecting query circle for envelope.
+		{[]float64{79.98458862304688, 25.339061458818374}, 1250,
+			"geometry", []string{"envelope1"}},
+
+		// test intersecting query circle for multipoint.
+		{[]float64{81.87346458435059, 25.41505910223247}, 200,
+			"geometry", []string{"multipoint1"}},
+
+		// test intersecting query circle for multilinestring.
+		{[]float64{81.8669843673706, 25.512661276952272}, 90,
+			"geometry", []string{"multilinestring1"}},
+	}
+
+	i := setupGeoJsonShapesIndexForCircleQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeCircleRelationQuery("intersects",
+			indexReader, test.centrePoint, test.radiusInMeters, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.centrePoint)
+		}
+	}
+}
+
+func TestGeoJsonCircleWithInQuery(t *testing.T) {
+	tests := []struct {
+		centrePoint    []float64
+		radiusInMeters float64
+		field          string
+		want           []string
+	}{
+		// test query circle containing polygon2 and multilinestring2.
+		{[]float64{81.85981750488281, 25.546778150624146}, 3700,
+			"geometry", []string{"polygon2", "multilinestring2"}},
+
+		// test query circle containing multilinestring2.
+		{[]float64{81.85981750488281, 25.546778150624146}, 3250,
+			"geometry", []string{"multilinestring2"}},
+
+		// test query circle containing multipoint1.
+		{[]float64{81.88599586486816, 25.425756968727935}, 1650,
+			"geometry", []string{"multipoint1"}},
+
+		// test query circle containing circle2.
+		{[]float64{82.09362030029297, 25.546313513788725}, 1280,
+			"geometry", []string{"envelope2", "circle2"}},
+
+		// test query circle containing envelope2 and circle2.
+		{[]float64{82.10289001464844, 25.544919592476727}, 700,
+			"geometry", []string{"envelope2", "circle2"}},
+
+		// test query circle containing point1 and linestring3.
+		{[]float64{81.27685546875, 26.1899475672235}, 5600,
+			"geometry", []string{"point1", "linestring3"}},
+	}
+
+	i := setupGeoJsonShapesIndexForCircleQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeCircleRelationQuery("within",
+			indexReader, test.centrePoint, test.radiusInMeters, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.centrePoint)
+		}
+	}
+}
+
+func TestGeoJsonCircleContainsQuery(t *testing.T) {
+	tests := []struct {
+		centrePoint    []float64
+		radiusInMeters float64
+		field          string
+		want           []string
+	}{
+		// test query circle within polygon3.
+		{[]float64{8.549551963806152, 47.3759038562437}, 180,
+			"geometry", []string{"polygon3"}},
+
+		// test query circle containing envelope3.
+		{[]float64{8.551011085510254, 47.380117626829275}, 75,
+			"geometry", []string{"envelope3"}},
+
+		// test query circle exceeding envelope3 with a few meters.
+		{[]float64{8.551011085510254, 47.380117626829275}, 78,
+			"geometry", nil},
+
+		// test query circle containing circle3.
+		{[]float64{8.535819053649902, 47.38297989270074}, 185,
+			"geometry", []string{"circle3"}},
+	}
+
+	i := setupGeoJsonShapesIndexForCircleQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeCircleRelationQuery("contains",
+			indexReader, test.centrePoint, test.radiusInMeters, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.centrePoint)
+		}
+	}
+}
+
+func runGeoShapeCircleRelationQuery(relation string, i index.IndexReader,
+	points []float64, radius float64, field string) ([]string, error) {
+	var rv []string
+	s := geo.NewGeoCircle(points, radius)
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForCircleQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon1 := [][][][]float64{{{{77.67248153686523, 12.957679089615821},
+		{77.67956256866455, 12.948101542434257}, {77.68908977508545, 12.948896200093982},
+		{77.68934726715086, 12.955211547173878}, {77.68016338348389, 12.954291440344619},
+		{77.67248153686523, 12.957679089615821}}}}
+	doc := document.NewDocument("polygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+	polygon2 := [][][][]float64{{{{81.84951782226561, 25.522692102524033},
+		{81.8557834625244, 25.521762640415535}, {81.86264991760254, 25.521762640415535},
+		{81.86676979064941, 25.521607729364224}, {81.89560890197754, 25.542673796271302},
+		{81.88977241516113, 25.543293330460937}, {81.84951782226561, 25.522692102524033}}}}
+	doc = document.NewDocument("polygon2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon2, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon3 := [][][][]float64{{{{8.548071384429932, 47.379216780040124},
+		{8.547642230987549, 47.3771680227784}, {8.545818328857422, 47.37677569847655},
+		{8.546290397644043, 47.37417465983494}, {8.551719188690186, 47.37417465983494},
+		{8.553242683410645, 47.37679022905829}, {8.548071384429932, 47.379216780040124}}}}
+	doc = document.NewDocument("polygon3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon3, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	point1 := [][][][]float64{{{{81.2439, 26.2244}}}}
+	doc = document.NewDocument("point1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		point1, "point", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope1 := [][][][]float64{{{{79.9969482421875, 23.895882703682627},
+		{80.7220458984375, 25.750424835909385}}}}
+	doc = document.NewDocument("envelope1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope1, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope2 := [][][][]float64{{{{82.10409164428711, 25.54360309635522},
+		{82.10537910461424, 25.544609829984058}}}}
+	doc = document.NewDocument("envelope2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope2, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope3 := [][][][]float64{{{{8.545668125152588, 47.37942019840244},
+		{8.552148342132568, 47.383778974713124}}}}
+	doc = document.NewDocument("envelope3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope3, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle1")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{77.67252445220947, 12.936348678099293}, 900,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle2")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{82.10289001464844, 25.544919592476727}, 100,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle3")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{8.53363037109375,
+			47.38191927423153}, 400,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring := [][][][]float64{{{{77.68715858459473, 12.944755587650944},
+		{77.69213676452637, 12.945090185150542}}}}
+	doc = document.NewDocument("linestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring1 := [][][][]float64{{{{77.68913269042969, 12.929614580987227},
+		{77.70252227783203, 12.929698235482276}}}}
+	doc = document.NewDocument("linestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring1, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring2 := [][][][]float64{{{{81.26792907714844, 26.170845301716813},
+		{81.30157470703125, 26.18440207077121}}}}
+	doc = document.NewDocument("linestring3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring2, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring := [][][][]float64{{{{81.86170578002928, 25.430407918899984},
+		{81.86273574829102, 25.421958559611397}}, {{81.88230514526367, 25.437616536907512},
+		{81.90084457397461, 25.431415601111418}}, {{81.86805725097656, 25.514868905100244},
+		{81.86702728271484, 25.502474677473746}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring1 := [][][][]float64{{{{81.84642791748047, 25.561335859046192},
+		{81.84230804443358, 25.550495180470026}},
+		{{81.87423706054688, 25.55142441992021}, {81.88453674316406, 25.555141305670045}},
+		{{81.8642807006836, 25.572175556682115}, {81.87458038330078, 25.567839795359724}}}}
+	doc = document.NewDocument("multilinestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring1, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{81.87337875366211, 25.432268248708212},
+		{81.87355041503906, 25.416299483230368}, {81.90118789672852, 25.426067037656946}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipoint1, "multipoint", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonWithHole1 := [][][][]float64{{
+		{{77.59991168975829, 12.972232910164502}, {77.6039457321167, 12.97582941279006},
+			{77.60424613952637, 12.98168407323241}, {77.59974002838135, 12.985489528568463},
+			{77.59321689605713, 12.979300406693417}, {77.59991168975829, 12.972232910164502}},
+		{{77.59682178497314, 12.975787593290978}, {77.60295867919922, 12.975787593290978},
+			{77.60295867919922, 12.98143316204164}, {77.59682178497314, 12.98143316204164},
+			{77.59682178497314, 12.975787593290978}}}}
+
+	doc = document.NewDocument("polygonWithHole1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygonWithHole1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
diff --git a/search/searcher/search_geoshape_envelope_test.go b/search/searcher/search_geoshape_envelope_test.go
new file mode 100644
index 00000000..651ae4ec
--- /dev/null
+++ b/search/searcher/search_geoshape_envelope_test.go
@@ -0,0 +1,400 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJsonEnvelopeWithInQuery(t *testing.T) {
+	tests := []struct {
+		points [][]float64
+		field  string
+		want   []string
+	}{
+		// test within query envelope for point1.
+		{[][]float64{{76.256103515625, 16.76772739719064},
+			{76.35772705078125, 16.872890378907783}},
+			"geometry", []string{"point1"}},
+
+		// test within query envelope for multipoint1.
+		{[][]float64{{81.046142578125, 17.156537255486093},
+			{81.331787109375, 17.96305758238804}},
+			"geometry", []string{"multipoint1"}},
+
+		// test within query envelope for partial points in a multipoint1.
+		{[][]float64{{81.05987548828125, 17.16178591271515},
+			{81.36199951171875, 17.861132899477624}},
+			"geometry", nil},
+
+		// test within query envelope for polygon2 and point1.
+		{[][]float64{{76.00341796875, 16.573022719182777},
+			{76.717529296875, 17.006888277600524}},
+			"geometry", []string{"polygon2", "point1"}},
+
+		// test within query envelope for linestring1.
+		{[][]float64{{76.84112548828125, 16.86500518090961},
+			{77.62115478515625, 17.531439701706244}},
+			"geometry", []string{"linestring1"}},
+
+		// test within query envelope for multilinestring1.
+		{[][]float64{{81.683349609375, 17.104042525557904},
+			{81.99234008789062, 17.66495983051931}},
+			"geometry", []string{"multilinestring1"}},
+
+		// test within query envelope that is intersecting multilinestring1.
+		{[][]float64{{81.65725708007812, 17.2601707001208},
+			{81.95114135742186, 17.66495983051931}},
+			"geometry", nil},
+
+		// test within query envelope for envelope1 and circle1.
+		{[][]float64{{74.75372314453125, 17.36636733709516},
+			{75.509033203125, 18.038809662036805}},
+			"geometry", []string{"envelope1", "circle1"}},
+
+		// test within query envelope for envelope1.
+		{[][]float64{{74.783935546875, 17.38209494787749},
+			{75.96221923828125, 17.727758609852284}},
+			"geometry", []string{"envelope1"}},
+	}
+	i := setupGeoJsonShapesIndexForEnvelopeQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeEnvelopeRelationQuery("within",
+			indexReader, test.points, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func TestGeoJsonEnvelopeIntersectsQuery(t *testing.T) {
+	tests := []struct {
+		points [][]float64
+		field  string
+		want   []string
+	}{
+
+		// test intersecting query envelope for partial points in a multipoint1.
+		{[][]float64{{81.00769042968749, 17.80622614478282},
+			{81.199951171875, 17.983957957423037}},
+			"geometry", []string{"multipoint1"}},
+
+		// test intersecting query envelope that is intersecting multilinestring1.
+		{[][]float64{{81.65725708007812, 17.2601707001208},
+			{81.95114135742186, 17.66495983051931}},
+			"geometry", []string{"multilinestring1"}},
+
+		// test intersecting query envelope for linestring2.
+		{[][]float64{{81.9854736328125, 18.27369419984127},
+			{82.14752197265625, 18.633232565431218}},
+			"geometry", []string{"linestring2"}},
+
+		// test intersecting query envelope for circle2.
+		{[][]float64{{82.6336669921875, 17.82714499951342},
+			{82.66387939453125, 17.861132899477624}},
+			"geometry", []string{"circle2"}},
+
+		// test intersecting query envelope for polygon3.
+		{[][]float64{{82.92343139648438, 17.739530934289657},
+			{82.98797607421874, 17.79184300887134}},
+			"geometry", []string{"polygon3"}},
+	}
+	i := setupGeoJsonShapesIndexForEnvelopeQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeEnvelopeRelationQuery("intersects",
+			indexReader, test.points, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func TestGeoJsonEnvelopeContainsQuery(t *testing.T) {
+	tests := []struct {
+		points [][]float64
+		field  string
+		want   []string
+	}{
+
+		// test envelope contained within polygon1.
+		{[][]float64{{8.548285961151123, 47.376092756617446},
+			{8.551225662231445, 47.37764752629426}},
+			"geometry", []string{"polygon1"}},
+
+		// test envelope partially contained within polygon1.
+		{[][]float64{{8.549273014068604, 47.376194471922986},
+			{8.551654815673828, 47.37827232736301}},
+			"geometry", nil},
+
+		// test envelope partially contained within polygon1.
+		{[][]float64{{8.549273014068604, 47.376194471922986},
+			{8.551654815673828, 47.37827232736301}},
+			"geometry", nil},
+
+		// test envelope fully contained within circle3.
+		{[][]float64{{8.532772064208984, 47.380379160110856},
+			{8.534531593322752, 47.38299442157271}},
+			"geometry", []string{"circle3"}},
+
+		// test envelope partially contained within circle3.
+		{[][]float64{{8.532836437225342, 47.38010309716447},
+			{8.538415431976318, 47.383081594720466}},
+			"geometry", nil},
+	}
+	i := setupGeoJsonShapesIndexForEnvelopeQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeEnvelopeRelationQuery("contains",
+			indexReader, test.points, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func runGeoShapeEnvelopeRelationQuery(relation string, i index.IndexReader,
+	points [][]float64, field string) ([]string, error) {
+	var rv []string
+	s := geo.NewGeoEnvelope(points)
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForEnvelopeQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon1 := [][][][]float64{{{{8.548071384429932, 47.379216780040124},
+		{8.547642230987549, 47.3771680227784}, {8.545818328857422, 47.37677569847655},
+		{8.546290397644043, 47.37417465983494}, {8.551719188690186, 47.37417465983494},
+		{8.553242683410645, 47.37679022905829}, {8.548071384429932, 47.379216780040124}}}}
+	doc := document.NewDocument("polygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon2 := [][][][]float64{{{{76.70379638671874, 16.828203242420393},
+		{76.36322021484375, 16.58881695544584}, {76.70928955078125, 16.720385051694},
+		{76.70379638671874, 16.828203242420393}}}}
+	doc = document.NewDocument("polygon2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon2, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon3 := [][][][]float64{{{{82.9522705078125, 17.749994573141873},
+		{82.94952392578125, 17.692436998627272}, {82.87673950195312, 17.64009591883757},
+		{82.76412963867188, 17.58643052828743}, {82.8094482421875, 17.522272941245202},
+		{82.99621582031249, 17.64009591883757}, {82.9522705078125, 17.749994573141873}}}}
+	doc = document.NewDocument("polygon3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon3, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope1 := [][][][]float64{{{{74.89654541015625, 17.403062993328923},
+		{74.92401123046875, 17.66495983051931}}}}
+	doc = document.NewDocument("envelope1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope1, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle1")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{75.0531005859375, 17.675427818339383}, 12900,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle2")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{82.69683837890625, 17.902955242676995}, 6000,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle3")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{8.53363037109375,
+			47.38191927423153}, 400,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	point1 := [][][][]float64{{{{76.29730224609375, 16.796653031618053}}}}
+	doc = document.NewDocument("point1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		point1, "point",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring1 := [][][][]float64{{{{76.85211181640624, 17.51048642597462},
+		{77.24212646484374, 16.93070509876554}}}}
+	doc = document.NewDocument("linestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring1, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring2 := [][][][]float64{{{{81.89208984375, 18.555136195095105},
+		{82.21343994140625, 18.059701055000478}}}}
+	doc = document.NewDocument("linestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring2, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{81.24938964843749, 17.602139123350838},
+		{81.30432128906249, 17.56548361143177}, {81.29058837890625, 17.180155043474496},
+		{81.09283447265625, 17.87681743233167}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipoint1, "multipoint", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring := [][][][]float64{{{{81.69708251953125, 17.641404631355755},
+		{81.90994262695312, 17.642713334367667}},
+		{{81.6998291015625, 17.620464090732245}, {81.69708251953125, 17.468572623463153}},
+		{{81.70120239257811, 17.458092664041494}, {81.81243896484375, 17.311310073048123}},
+		{{81.815185546875, 17.3034434020238}, {81.81243896484375, 17.109292665395643}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring1 := [][][][]float64{{{{77.6015853881836, 12.990089451715061},
+		{77.60476112365723, 12.987747683302153}},
+		{{77.59875297546387, 12.988751301039581}, {77.59446144104004, 12.98197680263484}},
+		{{77.60188579559325, 12.982604078764705}, {77.60557651519775, 12.987329508048184}}}}
+	doc = document.NewDocument("multilinestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring1, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
diff --git a/search/searcher/search_geoshape_geometrycollection_test.go b/search/searcher/search_geoshape_geometrycollection_test.go
new file mode 100644
index 00000000..16fca089
--- /dev/null
+++ b/search/searcher/search_geoshape_geometrycollection_test.go
@@ -0,0 +1,462 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJSONIntersectsQueryAgainstGeometryCollection(t *testing.T) {
+	tests := []struct {
+		points [][][][][]float64
+		types  []string
+		field  string
+		want   []string
+	}{
+		// test intersects geometrycollection query for gc_polygon1_linestring1.
+		{[][][][][]float64{
+			{{{{-120.80017089843749, 36.54053616262899}, {-120.67932128906249, 36.33725319397006},
+				{-120.30578613281251, 36.90597988519294},
+				{-120.80017089843749, 36.54053616262899}}}},
+			{{{{-118.24584960937499, 35.32184842037683}, {-117.8668212890625, 35.06597313798418}}}}},
+			[]string{"polygon", "linestring"}, "geometry", []string{"gc_polygon1_linestring1"}},
+
+		// test intersects geometrycollection query for gc_polygon1_linestring1.
+		{[][][][][]float64{
+			{{{{-118.3172607421875, 35.250105158539355}, {-117.50976562499999, 35.37561413174875}},
+				{{-118.69628906249999, 34.6241677899049}, {-118.3172607421875, 35.03899204678081}},
+				{{-117.94921874999999, 35.146862906756304}, {-117.674560546875, 34.41144164327245}}}},
+			{{{{-117.04284667968749, 35.263561862152095}, {-116.8505859375, 35.263561862152095},
+				{-116.8505859375, 35.33529320309328},
+				{-117.04284667968749, 35.33529320309328}, {-117.04284667968749, 35.263561862152095}}}}},
+			[]string{"multilinestring", "polygon"}, "geometry", []string{"gc_polygon1_linestring1"}},
+
+		// test intersects geometrycollection query for gc_multipolygon1_multilinestring1.
+		{[][][][][]float64{
+			{{{{-115.8563232421875, 38.53957267203905}, {-115.58166503906251, 38.54816542304656}},
+				{{-115.8343505859375, 38.45789034424927}, {-115.81237792968749, 38.19502155795575}}}},
+			{{{{-116.64905548095702, 37.94920616351679}}}}},
+			[]string{"multilinestring", "point"}, "geometry", []string{"gc_multipolygon1_multilinestring1"}},
+
+		// test intersects geometrycollection query for gc_polygon1_linestring1 and gc_multipolygon1_multilinestring1.
+		{[][][][][]float64{
+			{{{{-116.64905548095702, 37.94920616351679}, {-118.29528808593751, 34.52466147177172}}}},
+			{{{{-115.8563232421875, 38.53957267203905}, {-115.58166503906251, 38.54816542304656}},
+				{{-115.8343505859375, 38.45789034424927}, {-115.81237792968749, 38.19502155795575}}}}},
+			[]string{"multipoint", "multilinestring"}, "geometry", []string{"gc_polygon1_linestring1",
+				"gc_multipolygon1_multilinestring1"}},
+
+		// test intersects geometrycollection query for gc_polygon1_linestring1 and gc_multipolygon1_multilinestring1.
+		{[][][][][]float64{
+			{{{{-117.46582031249999, 36.146746777814364}, {-116.70227050781249, 36.146746777814364},
+				{-116.70227050781249, 36.69485094156225}, {-117.46582031249999, 36.69485094156225},
+				{-117.46582031249999, 36.146746777814364}}}, {{{-115.5267333984375, 38.06106741381201},
+				{-115.4937744140625, 37.18220222107978}, {-114.93896484374999, 37.304644804751106},
+				{-115.5267333984375, 38.06106741381201}}}},
+			{{{{-115.8563232421875, 38.53957267203905}, {-115.58166503906251, 38.54816542304656}},
+				{{-115.8343505859375, 38.45789034424927}, {-115.81237792968749, 38.19502155795575}}}}},
+			[]string{"multipolygon", "multilinestring"}, "geometry", []string{"gc_point1_multipoint1"}},
+	}
+	i := setupGeoJsonShapesIndexForGeometryCollectionQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeGeometryCollectionRelationQuery("intersects",
+			indexReader, test.points, test.types, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func TestGeoJSONWithInQueryAgainstGeometryCollection(t *testing.T) {
+	tests := []struct {
+		points [][][][][]float64
+		types  []string
+		field  string
+		want   []string
+	}{
+		// test within geometrycollection query for gc_multipoint2_multipolygon2_multiline2.
+		{[][][][][]float64{
+			{{{{-122.40434646606444, 37.73400071182758}, {-122.39730834960938, 37.73691949864062}}}},
+			{{{{-122.42511749267578, 37.760808496517235}, {-122.42314338684082, 37.74248523826606},
+				{-122.40082740783691, 37.756669194195815}, {-122.42511749267578, 37.760808496517235}}}},
+			{{{{-122.46339797973633, 37.76637243960179}, {-122.46176719665527, 37.7502901437285},
+				{-122.43644714355469, 37.75911208915015}, {-122.46339797973633, 37.76637243960179}}},
+				{{{-122.43653297424315, 37.714720253587004}, {-122.40563392639159, 37.714720253587004},
+					{-122.40563392639159, 37.72904529863455}, {-122.43653297424315, 37.72904529863455},
+					{-122.43653297424315, 37.714720253587004}}}}},
+			[]string{"linestring", "polygon", "multipolygon"}, "geometry",
+			[]string{"gc_multipoint2_multipolygon2_multiline2"}},
+
+		// test within geometrycollection query.
+		{[][][][][]float64{
+			{{{{-122.40434646606444, 37.73400071182758}, {-122.39730834960938, 37.73691949864062}}}},
+			{{{{-122.46339797973633, 37.76637243960179}, {-122.46176719665527, 37.7502901437285},
+				{-122.43644714355469, 37.75911208915015}, {-122.46339797973633, 37.76637243960179}}},
+				{{{-122.43653297424315, 37.714720253587004}, {-122.40563392639159, 37.714720253587004},
+					{-122.40563392639159, 37.72904529863455}, {-122.43653297424315, 37.72904529863455},
+					{-122.43653297424315, 37.714720253587004}}}}},
+			[]string{"linestring", "multipolygon"}, "geometry", nil},
+
+		// test within geometrycollection for gc_multipoint2_multipolygon2_multiline2.
+		{[][][][][]float64{
+			{{{{-122.4491500854492, 37.78170504295941}, {-122.4862289428711, 37.747371884118664},
+				{-122.43078231811525, 37.6949593672454}, {-122.3799705505371, 37.72945260537779},
+				{-122.3928451538086, 37.78007695280165}, {-122.4491500854492, 37.78170504295941}}}}},
+			[]string{"polygon"}, "geometry", []string{"gc_multipoint2_multipolygon2_multiline2"}},
+
+		// test within geometrycollection for gc_multipolygon3
+		// gc_multipolygon3's multipolygons within the geometrycollection is covered by the
+		// query's geometric collection of a polygon and a multipolygon.
+		{[][][][][]float64{
+			{{{{86.6162109375, 57.26716357153586}, {85.1220703125, 8119},
+				{84.462890625, 56.27996083172844}, {86.98974609375, 55.70235509327093},
+				{87.802734375, 56.77680831656842}, {86.6162109375, 57.26716357153586}}}},
+			{{{{75.1025390625, 54.3549556895541}, {73.1689453125, 54.29088164657006},
+				{72.7294921875, 53.08082737207479}, {74.091796875, 51.998410382390325},
+				{76.79443359375, 53.396432127095984}, {75.1025390625, 54.3549556895541}}},
+				{{{80.1123046875, 55.57834467218206}, {78.9697265625, 55.65279803318956},
+					{78.5302734375, 54.635697306063854}, {79.87060546875, 54.18815548107151},
+					{80.96923828125, 54.80068486732233}, {80.1123046875, 55.57834467218206}}}}},
+			[]string{"polygon", "multipolygon"}, "geometry", []string{"gc_multipolygon3"}},
+	}
+
+	i := setupGeoJsonShapesIndexForGeometryCollectionQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeGeometryCollectionRelationQuery("within",
+			indexReader, test.points, test.types, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func TestGeoJSONContainsQueryAgainstGeometryCollection(t *testing.T) {
+	tests := []struct {
+		points [][][][][]float64
+		types  []string
+		field  string
+		want   []string
+	}{
+		// test contains for a geometrycollection that comprises of a linestring,
+		// polygon, multipolygon, point and multipoint for polygon2.
+		{[][][][][]float64{
+			// linestring
+			{{{{7.457013130187988, 46.966401589723894}, {7.482891082763671, 46.94554547022893}}}},
+			// polygon
+			{{{{7.466454505920409, 46.965054389418476}, {7.46143341064453, 46.9641171865865},
+				{7.466325759887694, 46.96101258493027}, {7.466454505920409, 46.965054389418476}}}},
+			// multipolygon
+			{{{{7.4811744689941415, 46.957966385567474}, {7.478899955749511, 46.95492001277476},
+				{7.484478950500488, 46.95509576976545}, {7.4811744689941415, 46.957966385567474}}},
+				{{{7.466540336608888, 46.94753769790697}, {7.464609146118165, 46.946219320241674},
+					{7.468342781066894, 46.94592634301753}, {7.466540336608888, 46.94753769790697}}},
+				{{{7.504348754882812, 47.00425575323296}, {7.501087188720703, 47.001680295206874},
+					{7.507266998291015, 47.00191443288521}, {7.504348754882812, 47.00425575323296}}}},
+			// point
+			{{{{7.449932098388673, 46.95817142366062}}}},
+			// multipoint
+			{{{{7.479157447814942, 46.96370715518446}, {7.4532365798950195, 46.96657730900153}}}},
+		},
+			[]string{"linestring", "polygon", "multipolygon", "point", "multipoint"}, "geometry",
+			[]string{"multipolygon4"}},
+
+		// test contains for a geometrycollection query with one point inside the multipoint lying outside
+		// polygon2.
+		{[][][][][]float64{
+			// linestring
+			{{{{7.457013130187988, 46.966401589723894}, {7.482891082763671, 46.94554547022893}}}},
+			// polygon
+			{{{{7.466454505920409, 46.965054389418476}, {7.46143341064453, 46.9641171865865},
+				{7.466325759887694, 46.96101258493027}, {7.466454505920409, 46.965054389418476}}}},
+			// multipolygon
+			{{{{7.4811744689941415, 46.957966385567474}, {7.478899955749511, 46.95492001277476},
+				{7.484478950500488, 46.95509576976545}, {7.4811744689941415, 46.957966385567474}}},
+				{{{7.466540336608888, 46.94753769790697}, {7.464609146118165, 46.946219320241674},
+					{7.468342781066894, 46.94592634301753}, {7.466540336608888, 46.94753769790697}}}},
+			// point
+			{{{{7.449932098388673, 46.95817142366062}}}},
+			// multipoint
+			{{{{7.479157447814942, 46.96370715518446}, {7.475638389587402, 46.965200825877794}}}},
+		},
+			[]string{"linestring", "polygon", "multipolygon", "point", "multipoint"}, "geometry",
+			nil},
+
+		// test contains for a geometrycollection query with one point inside the multipoint lying outside
+		// polygon2.
+		{[][][][][]float64{
+			// linestring
+			{{{{7.457013130187988, 46.966401589723894}, {7.482891082763671, 46.94554547022893}}}},
+			// polygon
+			{{{{7.466454505920409, 46.965054389418476}, {7.46143341064453, 46.9641171865865},
+				{7.466325759887694, 46.96101258493027}, {7.466454505920409, 46.965054389418476}}}},
+			// multipolygon
+			{{{{7.4811744689941415, 46.957966385567474}, {7.478899955749511, 46.95492001277476},
+				{7.484478950500488, 46.95509576976545}, {7.4811744689941415, 46.957966385567474}}},
+				{{{7.466540336608888, 46.94753769790697}, {7.464609146118165, 46.946219320241674},
+					{7.468342781066894, 46.94592634301753}, {7.466540336608888, 46.94753769790697}}}},
+			// point
+			{{{{7.449932098388673, 46.95817142366062}}}},
+			// multipoint
+			{{{{7.479157447814942, 46.96370715518446}, {7.4532365798950195, 46.96657730900153}}}},
+		},
+			[]string{"linestring", "polygon", "multipolygon", "point", "multipoint"}, "geometry",
+			[]string{"polygon2", "multipolygon4"}},
+	}
+
+	i := setupGeoJsonShapesIndexForGeometryCollectionQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeGeometryCollectionRelationQuery("contains",
+			indexReader, test.points, test.types, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.points)
+		}
+	}
+}
+
+func runGeoShapeGeometryCollectionRelationQuery(relation string, i index.IndexReader,
+	points [][][][][]float64, types []string, field string) ([]string, error) {
+	var rv []string
+	s, _, err := geo.NewGeometryCollection(points, types)
+	if err != nil {
+		return nil, err
+	}
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForGeometryCollectionQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// document gc_polygon1_linestring1
+	polygon1 := [][][][]float64{{{{-118.15246582031249, 34.876918445772084},
+		{-118.46557617187499, 34.773203753940734}, {-118.3172607421875, 34.50655662164561},
+		{-117.91625976562499, 34.4793919710481}, {-117.76245117187499, 34.76417891445512},
+		{-118.15246582031249, 34.876918445772084}}}}
+
+	linestring1 := [][][][]float64{{{{-120.78918457031251, 36.87522650673951},
+		{-118.9215087890625, 34.95349314197422}}}}
+
+	coordinates := [][][][][]float64{polygon1, linestring1}
+	types := []string{"polygon", "linestring"}
+
+	doc := document.NewDocument("gc_polygon1_linestring1")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types, document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// document gc_multipolygon1_multilinestring1
+	multipolygon1 := [][][][]float64{{{{-117.24609374999999, 37.67512527892127},
+		{-117.61962890624999, 37.26530995561875}, {-116.597900390625, 37.56199695314352},
+		{-117.24609374999999, 37.67512527892127}}},
+		{{{-117.60864257812501, 38.71123253895224}, {-117.41638183593749, 38.36750215395045},
+			{-117.66357421875, 37.93986540897977}, {-116.6473388671875, 37.94852933714952},
+			{-117.1307373046875, 38.363195134453846}, {-116.75170898437501, 38.7283759182398},
+			{-117.60864257812501, 38.71123253895224}}}}
+	multilinestring1 := [][][][]float64{{
+		{{-118.9215087890625, 38.74123075381228}, {-118.78967285156249, 38.43207668538207}},
+		{{-118.57543945312501, 38.8225909761771}, {-118.45458984375, 38.522384090200845}},
+		{{-118.94897460937499, 38.788345355085625}, {-118.61938476562499, 38.86965182408357}}}}
+
+	coordinates = [][][][][]float64{multipolygon1, multilinestring1}
+	types = []string{"multipolygon", "multilinestring"}
+	doc = document.NewDocument("gc_multipolygon1_multilinestring1")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types, document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// document gc_point1_multipoint1
+	point1 := [][][][]float64{{{{-115.10925292968749, 36.20882309283712}}}}
+	multipoint1 := [][][][]float64{{{{-117.13623046874999, 36.474306755095235},
+		{-118.57543945312501, 36.518465989675875},
+		{-118.58642578124999, 36.90597988519294}, {-119.5477294921875, 37.85316995894978}}}}
+
+	coordinates = [][][][][]float64{point1, multipoint1}
+	types = []string{"point", "multipoint"}
+
+	doc = document.NewDocument("gc_point1_multipoint1")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types, document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// document gc_multipoint2_multipolygon2_multiline2
+	multipoint2 := [][][][]float64{{{{-122.4052906036377, 37.75626203719391},
+		{-122.42091178894044, 37.74757548736071}}}}
+	multipolygon2 := [][][][]float64{{{{-122.46168136596681, 37.765151122096945},
+		{-122.46168136596681, 37.754972691904946}, {-122.45103836059569, 37.754972691904946},
+		{-122.451810836792, 37.7624370109886}, {-122.46168136596681, 37.765151122096945}}},
+		{{{-122.41902351379395, 37.726194088705576}, {-122.43533134460448, 37.71668926284967},
+			{-122.40777969360353, 37.71634978222733}, {-122.41902351379395, 37.726194088705576}}}}
+	multilinestring2 := [][][][]float64{{
+		{{-122.41284370422362, 37.73155698786267}, {-122.40700721740721, 37.73338978839743}},
+		{{-122.40434646606444, 37.73400071182758}, {-122.39730834960938, 37.73691949864062}}}}
+
+	coordinates = [][][][][]float64{multipoint2, multipolygon2, multilinestring2}
+	types = []string{"multipoint", "multipolygon", "multiline"}
+
+	doc = document.NewDocument("gc_multipoint2_multipolygon2_multiline2")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types, document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// document gc_multipolygon3
+	multipolygon3 := [][][][]float64{
+		{{{85.60546875, 57.20771009775018}, {86.396484375, 55.99838095535963},
+			{87.03369140625, 56.71656572651468}, {85.60546875, 57.20771009775018}}},
+		{{{79.56298828125, 55.3915921070334}, {79.60693359375, 54.43171285946844},
+			{80.39794921875, 54.85131525968606}, {79.56298828125, 55.3915921070334}}},
+		{{{74.35546875, 54.13669645687002}, {74.1796875, 52.802761415419674},
+			{75.87158203125, 53.44880683542759}, {74.35546875, 54.13669645687002}}}}
+
+	coordinates = [][][][][]float64{multipolygon3}
+	types = []string{"multipolygon"}
+
+	doc = document.NewDocument("gc_multipolygon3")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types, document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon2 := [][][][]float64{{{{7.452635765075683, 46.96692874582506},
+		{7.449803352355956, 46.95817142366062}, {7.4573564529418945, 46.95149263607834},
+		{7.462162971496582, 46.945955640812095}, {7.483148574829102, 46.945311085627445},
+		{7.487225532531738, 46.957029058564686}, {7.4793291091918945, 46.96388288331302},
+		{7.464480400085448, 46.96903731827891}, {7.452635765075683, 46.96692874582506},
+	}}}
+
+	doc = document.NewDocument("polygon2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon2, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipolygon4 := [][][][]float64{{{{7.452635765075683, 46.96692874582506},
+		{7.449803352355956, 46.95817142366062}, {7.4573564529418945, 46.95149263607834},
+		{7.462162971496582, 46.945955640812095}, {7.483148574829102, 46.945311085627445},
+		{7.487225532531738, 46.957029058564686}, {7.4793291091918945, 46.96388288331302},
+		{7.464480400085448, 46.96903731827891}, {7.452635765075683, 46.96692874582506}}},
+		{{{7.4478721618652335, 47.00015837528636}, {7.5110435485839835, 47.00015837528636},
+			{7.5110435485839835, 47.00683108710118}, {7.4478721618652335, 47.00683108710118},
+			{7.4478721618652335, 47.00015837528636}}},
+	}
+
+	doc = document.NewDocument("multipolygon4")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipolygon4, "multipolygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
diff --git a/search/searcher/search_geoshape_linestring_test.go b/search/searcher/search_geoshape_linestring_test.go
new file mode 100644
index 00000000..f18e4d37
--- /dev/null
+++ b/search/searcher/search_geoshape_linestring_test.go
@@ -0,0 +1,498 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJsonLinestringIntersectsQuery(t *testing.T) {
+	tests := []struct {
+		line  [][]float64
+		field string
+		want  []string
+	}{
+		// test intersecting linestring query for polygon1.
+		{[][]float64{{74.85860824584961, 22.407219759334023},
+			{74.8663330078125, 22.382936446589863}},
+			"geometry", []string{"polygon1"}},
+
+		// test intersecting linestring query for polygon1 and polygon2.
+		{[][]float64{{74.82461929321289, 22.393729553598526},
+			{74.93671417236328, 22.356743809494784}},
+			"geometry", []string{"polygon1", "polygon2"}},
+
+		// test intersecting linestring query for envelope1.
+		{[][]float64{{74.83938217163086, 22.325782524687973},
+			{74.8692512512207, 22.311172762889516}},
+			"geometry", []string{"envelope1"}},
+
+		// test intersecting linestring query for circle.
+		{[][]float64{{74.94546890258789, 22.310815439776572},
+			{74.93276596069336, 22.303708490145645}},
+			"geometry", []string{"circle1"}},
+
+		// test intersecting linestring query for linestring1.
+		{[][]float64{{74.938645362854, 22.321614134448936},
+			{74.94070529937744, 22.320224643365446}},
+			"geometry", []string{"linestring1"}},
+
+		// test intersecting linestring query for multilinestring1.
+		{[][]float64{{74.9241828918457, 22.307996525380194},
+			{74.94100570678711, 22.293781977618558}},
+			"geometry", []string{"multilinestring1"}},
+
+		// test intersecting linestring query for multipolygon1.
+		{[][]float64{{36.22072219848633, 50.007132228568786},
+			{36.22218132019043, 49.99791917183082}},
+			"geometry", []string{"multipolygon1"}},
+
+		// test intersecting linestring query for envelope2, circle2,
+		// multipolygon1 and gc_polygonInGc_multipolygonInGc.
+		{[][]float64{{36.19840621948242, 50.03834418692451},
+			{36.25720024108887, 50.02136210283289}}, "geometry",
+			[]string{"envelope2", "circle2", "multipolygon1", "gc_polygonInGc_multipolygonInGc"}},
+	}
+	i := setupGeoJsonShapesIndexForLinestringQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeLinestringIntersectsQuery("intersects",
+			indexReader, test.line, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.line)
+		}
+	}
+}
+
+func TestGeoJsonLinestringContainsQuery(t *testing.T) {
+	tests := []struct {
+		line  [][]float64
+		field string
+		want  []string
+	}{
+		// test a linestring query for multipolygon1.
+		{[][]float64{{36.21668815612793, 50.040494087443996},
+			{36.226301193237305, 50.03861982057644}},
+			"geometry", []string{"multipolygon1"}},
+
+		// test a linestring query with endspoints on two
+		// different polygons in a multipolygon.
+		{[][]float64{{36.19746208190918, 50.038564693972646},
+			{36.21565818786621, 50.03718650830641}},
+			"geometry", nil},
+
+		// test a linestring query for envelope2.
+		{[][]float64{{36.25290870666503, 50.03018471417061},
+			{36.23110771179199, 50.01854955486945}},
+			"geometry", []string{"envelope2"}},
+
+		// test a linestring query for circle2.
+		{[][]float64{{36.220550537109375, 50.02930252595981},
+			{36.224327087402344, 50.02847545979485}},
+			"geometry", []string{"circle2"}},
+
+		// test a linestring query for polygonWithHole2.
+		{[][]float64{{36.27367973327637, 49.89883638369706},
+			{36.27445220947265, 49.89596137883285}},
+			"geometry", []string{"polygonWithHole2"}},
+
+		// test a linestring query within the hole of polygonWithHole2.
+		{[][]float64{{36.261234283447266, 49.89540847364305},
+			{36.26243591308594, 49.89087441212101}}, "geometry", nil},
+	}
+	i := setupGeoJsonShapesIndexForLinestringQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeLinestringIntersectsQuery("contains",
+			indexReader, test.line, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.line)
+		}
+	}
+}
+
+func TestGeoJsonMultiLinestringContainsQuery(t *testing.T) {
+	tests := []struct {
+		line  [][][]float64
+		field string
+		want  []string
+	}{
+		// test a multilinestring query for multipolygon1.
+		{[][][]float64{{{36.21668815612793, 50.040494087443996},
+			{36.226301193237305, 50.03861982057644}},
+			{{36.226816177368164, 49.999463999158},
+				{36.234025955200195, 50.00271900853649}}},
+			"geometry", []string{"multipolygon1"}},
+
+		// test a multilinestring query that is covered by the geometryCollection.
+		{[][][]float64{{{36.28664016723633, 49.96574238290487},
+			{36.30251884460449, 49.96369956194569}}, {{36.19179725646973, 50.03983258984584},
+			{36.19420051574707, 50.03801342445342}}}, "geometry",
+			[]string{"gc_polygonInGc_multipolygonInGc"}},
+
+		// test a multilinestring query for envelope2.
+		{[][][]float64{{{36.23213768005371, 50.02913711386621},
+			{36.25187873840332, 50.02902683882067}},
+			{{36.231794357299805, 50.018935600613254},
+				{36.2314510345459, 50.025883893582055}}}, "geometry",
+			[]string{"envelope2"}},
+
+		// test a multilinestring query with one linestring outside of envelope2.
+		{[][][]float64{{{36.23213768005371, 50.02913711386621},
+			{36.25187873840332, 50.02902683882067}},
+			{{36.231794357299805, 50.018935600613254}, {36.2314510345459, 50.025883893582055}},
+			{{36.25659942626953, 50.024284772330844}, {36.24406814575195, 50.01518531066489}}},
+			"geometry", nil},
+
+		// test a multilinestring query with one linestring
+		// inside the whole of a polygonWithHole2.
+		{[][][]float64{{{36.27367973327637, 49.89883638369706},
+			{36.27445220947265, 49.89596137883285}},
+			{{36.261234283447266, 49.89540847364305}, {36.26243591308594, 49.89087441212101}}},
+			"geometry", nil},
+
+		// test a multilinestring query for polygonWithHole2.
+		{[][][]float64{{{36.27367973327637, 49.89883638369706},
+			{36.27445220947265, 49.89596137883285}},
+			{{36.279258728027344, 49.894302644257856}, {36.28166198730469, 49.887335336408235}}},
+			"geometry", []string{"polygonWithHole2"}},
+
+		// test a multilinestring query for polygonWithHole2 with last line cross the hole.
+		{[][][]float64{{{36.27367973327637, 49.89883638369706},
+			{36.27445220947265, 49.89596137883285}},
+			{{36.279258728027344, 49.894302644257856}, {36.28166198730469, 49.887335336408235}},
+			{{36.254024505615234, 49.89839408640621}, {36.27016067504883, 49.90038439228633}}},
+			"geometry", nil},
+	}
+	i := setupGeoJsonShapesIndexForLinestringQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeMultiLinestringIntersectsQuery("contains",
+			indexReader, test.line, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.line)
+		}
+	}
+}
+
+func runGeoShapeMultiLinestringIntersectsQuery(relation string, i index.IndexReader,
+	points [][][]float64, field string) ([]string, error) {
+
+	s := geo.NewGeoJsonMultilinestring(points)
+	return executeSearch(relation, i, s, field)
+}
+
+func runGeoShapeLinestringIntersectsQuery(relation string, i index.IndexReader,
+	points [][]float64, field string) ([]string, error) {
+
+	s := geo.NewGeoJsonLinestring(points)
+	return executeSearch(relation, i, s, field)
+}
+
+func executeSearch(relation string, i index.IndexReader,
+	s index.GeoJSON, field string) ([]string, error) {
+	var rv []string
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForLinestringQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon1 := [][][][]float64{{{{74.84642028808594, 22.402776071459712},
+		{74.83234405517578, 22.39039647758608}, {74.86719131469727, 22.38801566009795},
+		{74.85139846801758, 22.39103135536648}, {74.86461639404297, 22.394840561182853},
+		{74.8495101928711, 22.397697397065034},
+		{74.86186981201172, 22.401982540816856}, {74.84642028808594, 22.402776071459712}}}}
+	doc := document.NewDocument("polygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, polygon1, "polygon",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon2 := [][][][]float64{{{{74.93431091308592, 22.376428433285266},
+		{74.92898941040039, 22.39103135536648}, {74.9241828918457, 22.37722210974017},
+		{74.90821838378906, 22.37388863821397}, {74.92504119873047, 22.369920115637292},
+		{74.92864608764648, 22.355632497760894}, {74.93207931518555, 22.370396344320053},
+		{74.94855880737305, 22.3743648533201},
+		{74.93431091308592, 22.376428433285266}}}}
+	doc = document.NewDocument("polygon2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, polygon2, "polygon",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope1 := [][][][]float64{{{{74.86736297607422, 22.307361269208684},
+		{74.87028121948242, 22.345471522338478}}}}
+	doc = document.NewDocument("envelope1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, envelope1, "envelope",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	envelope2 := [][][][]float64{{{{36.23007774353027, 50.01810835593541},
+		{36.25333786010742, 50.03068093791795}}}}
+	doc = document.NewDocument("envelope2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, envelope2, "envelope",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle1")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry",
+		[]uint64{}, []float64{74.93671417236328, 22.308314152382284}, 300,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle2")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry",
+		[]uint64{}, []float64{36.22243881225586, 50.02941280037234}, 600,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring := [][][][]float64{{{{74.92697238922119, 22.320343743143248},
+		{74.94036197662354, 22.32054224254707}}}}
+	doc = document.NewDocument("linestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, linestring, "linestring",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring1 := [][][][]float64{{{{77.60188579559325, 12.982604078764705},
+		{77.60557651519775, 12.987329508048184}}}}
+	doc = document.NewDocument("linestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, linestring1, "linestring",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring := [][][][]float64{{{{74.92203712463379, 22.3113315728684},
+		{74.92323875427246, 22.307798008137024}},
+		{{74.92405414581299, 22.307559787072712}, {74.92735862731934, 22.310021385140573}},
+		{{74.9223804473877, 22.311688894660474}, {74.92534160614014, 22.30930673210729}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, multilinestring, "multilinestring",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring1 := [][][][]float64{{{{77.6015853881836, 12.990089451715061},
+		{77.60476112365723, 12.987747683302153}},
+		{{77.59875297546387, 12.988751301039581}, {77.59446144104004, 12.98197680263484}},
+		{{77.60188579559325, 12.982604078764705}, {77.60557651519775, 12.987329508048184}}}}
+	doc = document.NewDocument("multilinestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, multilinestring1, "multilinestring",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{77.56618022918701, 12.958180959662695},
+		{77.56407737731932, 12.951614746607163},
+		{77.56922721862793, 12.956173473406446}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, multipoint1, "multipoint",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonWithHole1 := [][][][]float64{{
+		{{77.59991168975829, 12.972232910164502}, {77.6039457321167, 12.97582941279006},
+			{77.60424613952637, 12.98168407323241}, {77.59974002838135, 12.985489528568463},
+			{77.59321689605713, 12.979300406693417}, {77.59991168975829, 12.972232910164502}},
+		{{77.59682178497314, 12.975787593290978}, {77.60295867919922, 12.975787593290978},
+			{77.60295867919922, 12.98143316204164}, {77.59682178497314, 12.98143316204164},
+			{77.59682178497314, 12.975787593290978}}}}
+
+	doc = document.NewDocument("polygonWithHole1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, polygonWithHole1, "polygon",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonWithHole2 := [][][][]float64{{
+		{{36.261234283447266, 49.90712870720605}, {36.2479305267334, 49.89480027061714},
+			{36.254539489746094, 49.883408870659736}, {36.280717849731445, 49.883408870659736},
+			{36.28741264343262, 49.890432041848264}, {36.27788543701172, 49.90276159448742},
+			{36.261234283447266, 49.90712870720605}},
+
+		{{36.264581680297844, 49.905249238801304}, {36.25368118286133, 49.89673543545543},
+			{36.253509521484375, 49.88578690918283}, {36.270332336425774, 49.886174020645804},
+			{36.27127647399902, 49.89579550794111},
+			{36.264581680297844, 49.905249238801304}}}}
+
+	doc = document.NewDocument("polygonWithHole2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, polygonWithHole2, "polygon",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipolygon1 := [][][][]float64{{{{36.1875057220459, 50.04363607656457},
+		{36.192398071289055, 50.034871067327856}, {36.20218276977539, 50.03955696315653},
+		{36.1875057220459, 50.04363607656457}}}, // polygon1
+		{{{36.2123966217041, 50.03795829715335}, {36.218318939208984, 50.0333273779768},
+			{36.226558685302734, 50.03867494711694}, {36.217031478881836, 50.04286437899031},
+			{36.2123966217041, 50.03795829715335}}}, // polygon2
+		{{{36.221065521240234, 50.00365685169585}, {36.226301193237305, 49.998029518286025},
+			{36.23342514038086, 49.9995743420677}, {36.23531341552734, 50.002994846659156},
+			{36.231021881103516, 50.00630478067617}, {36.22810363769531, 50.00663576154257},
+			{36.226043701171875, 50.004815338573046}, {36.221065521240234, 50.00365685169585}}}}
+	doc = document.NewDocument("multipolygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry",
+		[]uint64{}, multipolygon1, "multipolygon",
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonInGc := [][][][]float64{{{{36.1875057220459, 50.04363607656457},
+		{36.192398071289055, 50.034871067327856}, {36.20218276977539, 50.03955696315653},
+		{36.1875057220459, 50.04363607656457}}}}
+	multipolygonInGc := [][][][]float64{{{{36.29015922546387, 49.980150089789376},
+		{36.28337860107422, 49.961656654293485}, {36.307411193847656, 49.96033147865059},
+		{36.29015922546387, 49.980150089789376}}}, // polygon1
+		{{{36.16106986999512, 50.00387751801547}, {36.161842346191406, 49.9908012905034},
+			{36.17900848388672, 49.99841572888488}, {36.16106986999512, 50.00387751801547}}}}
+	coordinates := [][][][][]float64{polygonInGc, multipolygonInGc}
+	types := []string{"polygon", "multipolygon"}
+	doc = document.NewDocument("gc_polygonInGc_multipolygonInGc")
+	doc.AddField(document.NewGeometryCollectionFieldWithIndexingOptions("geometry",
+		[]uint64{}, coordinates, types,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
diff --git a/search/searcher/search_geoshape_points_test.go b/search/searcher/search_geoshape_points_test.go
new file mode 100644
index 00000000..548fc4b7
--- /dev/null
+++ b/search/searcher/search_geoshape_points_test.go
@@ -0,0 +1,427 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJsonPointWithInQuery(t *testing.T) {
+	tests := []struct {
+		point []float64
+		field string
+		want  []string
+	}{
+		// test points inside the polygon1.
+		{[]float64{77.58334636688232, 12.948268838994263},
+			"geometry", []string{"polygon1"}},
+
+		// test points inside the circle1.
+		{[]float64{77.58553504943848, 12.954040501528555},
+			"geometry", []string{"circle1"}},
+
+		// test points inside the polygon1 and the circle.
+		{[]float64{77.59293794631958, 12.948896200093982},
+			"geometry", []string{"polygon1", "circle1"}},
+
+		// test points outside the polygon1 and the circle1.
+		{[]float64{77.5614595413208, 12.953287683563568},
+			"geometry", nil},
+
+		// test point within the envelope1.
+		{[]float64{81.28166198730469, 26.34203746601541},
+			"geometry", []string{"envelope1"}},
+
+		// test point on the linestring vertex.
+		{[]float64{77.57776737213135, 12.952074805390097},
+			"geometry", []string{"linestring1"}},
+
+		// test point on the multilinestring vertex.
+		{[]float64{77.5779390335083, 12.945006535817749},
+			"geometry", []string{"multilinestring1"}},
+
+		// test point on the multipoint vertex.
+		{[]float64{77.56407737731932, 12.951614746607163},
+			"geometry", []string{"multipoint1"}},
+
+		// test point within the polygonWithHole1.
+		{[]float64{77.60334491729736, 12.979844051951334},
+			"geometry", []string{"polygonWithHole1"}},
+
+		// test point within the hole of the polygonWithHole1.
+		{[]float64{77.60244369506836, 12.976247607394027},
+			"geometry", nil},
+	}
+	i := setupGeoJsonShapesIndex(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePointRelationQuery("contains",
+			false, indexReader, [][]float64{test.point}, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.point)
+		}
+	}
+}
+
+func TestGeoJsonMultiPointWithInQuery(t *testing.T) {
+	tests := []struct {
+		multipoint [][]float64
+		field      string
+		want       []string
+	}{
+		// test multipoint inside the polygon1.
+		{[][]float64{{77.58334636688232, 12.948268838994263},
+			{77.58467674255371, 12.944295515355652}},
+			"geometry", []string{"polygon1"}},
+
+		// test multipoint inside the circle1.
+		{[][]float64{{77.58553504943848, 12.954040501528555},
+			{77.58643627166747, 12.956089827794571}},
+			"geometry", []string{"circle1"}},
+
+		// test multipoint inside the envelope1.
+		{[][]float64{{81.28166198730469, 26.34203746601541},
+			{80.94314575195312, 26.346960121309415}},
+			"geometry", []string{"envelope1"}},
+
+		// test multipoint inside the polygon1 and the circle.
+		{[][]float64{{77.59293794631958, 12.948896200093982},
+			{77.58532047271729, 12.953789562459688}},
+			"geometry", []string{"polygon1", "circle1"}},
+
+		// test multipoint (only 1 point outside) outside.
+		{[][]float64{{77.58334636688232, 12.948268838994263},
+			{77.58643627166747, 12.956089827794571},
+			{77.5615, 12.9533}}, "geometry", nil},
+
+		// test multipoint on the linestring vertex.
+		{[][]float64{{77.5841188430786, 12.957093573282744},
+			{77.57776737213135, 12.952074805390097}},
+			"geometry", []string{"linestring1"}},
+
+		// test multipoint outside the linestring vertex.
+		{[][]float64{{77.5841188430786, 12.957093573282744},
+			{77.57776737213135, 12.952074805390097},
+			{77.58334636688232, 12.948268838994263}},
+			"geometry", nil},
+
+		// test multipoint on the multilinestring vertex.
+		{[][]float64{{77.5779390335083, 12.94471376293191},
+			{77.57218837738037, 12.948268838994263}},
+			"geometry", []string{"multilinestring1"}},
+
+		// test multipoint outside the multilinestring vertex.
+		{[][]float64{{77.5779390335083, 12.94471376293191},
+			{77.57218837738037, 12.948268838994263},
+			{77.58532047271729, 12.953789562459688}},
+			"geometry", nil},
+
+		// test multipoint with one inside the hole within the polygonWithHole1.
+		{[][]float64{{77.60334491729736, 12.979844051951334},
+			{77.60244369506836, 12.976247607394027}},
+			"geometry", nil},
+
+		// test multipoint with all inside the hole witin the polygonWithHole1.
+		{[][]float64{{77.59656429290771, 12.981767710239714},
+			{77.59888172149658, 12.979969508380469}},
+			"geometry", nil},
+
+		// test multipoint with all inside the polygonWithHole1.
+		{[][]float64{{77.60334491729736, 12.979844051951334},
+			{77.59656429290771, 12.981767710239714},
+			{77.59802341461182, 12.9751602999608}},
+			"geometry", []string{"polygonWithHole1"}},
+	}
+	i := setupGeoJsonShapesIndex(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePointRelationQuery("contains",
+			true, indexReader, test.multipoint, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.multipoint)
+		}
+	}
+}
+
+func TestGeoJsonMultiPointIntersectsQuery(t *testing.T) {
+	tests := []struct {
+		multipoint [][]float64
+		field      string
+		want       []string
+	}{
+		// test multipoint inside the polygon1.
+		{[][]float64{{77.58334636688232, 12.948268838994263},
+			{77.58467674255371, 12.944295515355652}},
+			"geometry", []string{"polygon1"}},
+
+		// test multipoint inside the circle1.
+		{[][]float64{{77.58553504943848, 12.954040501528555},
+			{77.58643627166747, 12.956089827794571}},
+			"geometry", []string{"circle1"}},
+
+		// test multipoint inside the envelope1. (1 point outside)
+		{[][]float64{{81.28166198730469, 26.34203746601541},
+			{80.94314575195312, 26.346960121309415},
+			{81.12716674804688, 26.353728430338332}},
+			"geometry", []string{"envelope1"}},
+
+		// test multipoint inside the polygon1 and the circle.
+		{[][]float64{{77.59293794631958, 12.948896200093982},
+			{77.58532047271729, 12.953789562459688}},
+			"geometry", []string{"polygon1", "circle1"}},
+
+		// test multipoint (only 1 point outside) intersects.
+		{[][]float64{{77.58334636688232, 12.948268838994263},
+			{77.58643627166747, 12.956089827794571},
+			{77.5615, 12.9533}}, "geometry",
+			[]string{"polygon1", "circle1"}},
+
+		// test multipoint on the linestring vertex.
+		{[][]float64{{77.5841188430786, 12.957093573282744},
+			{77.57776737213135, 12.952074805390097}},
+			"geometry", []string{"linestring1"}},
+
+		// test multipoint outside the linestring vertex.
+		{[][]float64{{77.5841188430786, 12.957093573282744},
+			{77.57776737213135, 12.952074805390097},
+			{77.58334636688232, 12.948268838994263}},
+			"geometry", []string{"polygon1", "linestring1"}},
+
+		// test multipoint on the multilinestring vertex.
+		{[][]float64{{77.5779390335083, 12.94471376293191},
+			{77.57218837738037, 12.948268838994263}},
+			"geometry", []string{"multilinestring1"}},
+
+		// test multipoint outside the multilinestring vertex.
+		{[][]float64{{77.5779390335083, 12.94471376293191},
+			{77.57218837738037, 12.948268838994263},
+			{77.58532047271729, 12.953789562459688}},
+			"geometry", []string{"polygon1", "circle1", "multilinestring1"}},
+
+		// test multipoint with one inside the hole within the polygonWithHole1.
+		{[][]float64{{77.60334491729736, 12.979844051951334},
+			{77.60244369506836, 12.976247607394027}},
+			"geometry", []string{"polygonWithHole1"}},
+
+		// test multipoint with all inside the hole witin the polygonWithHole1.
+		{[][]float64{{77.60244369506836, 12.976247607394027},
+			{77.59888172149658, 12.979969508380469}},
+			"geometry", nil},
+
+		// test multipoint with all inside the polygonWithHole1.
+		{[][]float64{{77.60334491729736, 12.979844051951334},
+			{77.59656429290771, 12.981767710239714},
+			{77.59802341461182, 12.9751602999608}},
+			"geometry", []string{"polygonWithHole1"}},
+	}
+	i := setupGeoJsonShapesIndex(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePointRelationQuery("intersects",
+			true, indexReader, test.multipoint, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.multipoint)
+		}
+	}
+}
+
+func runGeoShapePointRelationQuery(relation string, multi bool,
+	i index.IndexReader, points [][]float64, field string) ([]string, error) {
+	var rv []string
+	var s index.GeoJSON
+	if multi {
+		s = geo.NewGeoJsonMultiPoint(points)
+	} else {
+		s = geo.NewGeoJsonPoint(points[0])
+	}
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+type Fatalfable interface {
+	Fatalf(format string, args ...interface{})
+}
+
+func setupGeoJsonShapesIndex(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon1 := [][][][]float64{{{{77.5853419303894, 12.953977766785052},
+		{77.58405447006226, 12.95393594361393}, {77.5819730758667, 12.9495026476557},
+		{77.58068561553955, 12.94883346405509}, {77.58019208908081, 12.948331575175299},
+		{77.57991313934326, 12.943814529775414}, {77.58497714996338, 12.94394000436408},
+		{77.58517026901245, 12.9446301134728}, {77.58572816848755, 12.945508431393435},
+		{77.58785247802734, 12.946365833997325}, {77.58967638015747, 12.946428570657417},
+		{77.59070634841918, 12.947474179333993}, {77.59317398071289, 12.948875288082773},
+		{77.59167194366454, 12.949962710338657}, {77.59077072143555, 12.950276388953625},
+		{77.59098529815674, 12.951196510612728}, {77.58729457855225, 12.952472128200755},
+		{77.5853419303894, 12.953977766785052}}}}
+	doc := document.NewDocument("polygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// not working envelope
+	envelope1 := [][][][]float64{{{{80.93696594238281, 26.33957605983274},
+		{81.28440856933594, 26.351267272877074}}}}
+	doc = document.NewDocument("envelope1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope1, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle1")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry", []uint64{},
+		[]float64{77.59137153625487, 12.952660333521468}, 900,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring := [][][][]float64{{{{77.5841188430786, 12.957093573282744},
+		{77.57776737213135, 12.952074805390097}}}}
+	doc = document.NewDocument("linestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring := [][][][]float64{{{{77.57227420806883, 12.948687079902895},
+		{77.57600784301758, 12.954165970968194}, {77.5779390335083, 12.94471376293191},
+		{77.57218837738037, 12.948268838994263}, {77.57781028747559, 12.951740217268595},
+		{77.5779390335083, 12.945006535817749}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{77.56618022918701, 12.958180959662695},
+		{77.56407737731932, 12.951614746607163}, {77.56922721862793, 12.956173473406446}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipoint1, "multipoint", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonWithHole1 := [][][][]float64{{
+		{{77.59991168975829, 12.972232910164502}, {77.6039457321167, 12.97582941279006},
+			{77.60424613952637, 12.98168407323241}, {77.59974002838135, 12.985489528568463},
+			{77.59321689605713, 12.979300406693417}, {77.59991168975829, 12.972232910164502}},
+		{{77.59682178497314, 12.975787593290978}, {77.60295867919922, 12.975787593290978},
+			{77.60295867919922, 12.98143316204164}, {77.59682178497314, 12.98143316204164},
+			{77.59682178497314, 12.975787593290978}}}}
+
+	doc = document.NewDocument("polygonWithHole1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygonWithHole1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
diff --git a/search/searcher/search_geoshape_polygon_test.go b/search/searcher/search_geoshape_polygon_test.go
new file mode 100644
index 00000000..2b24d3c9
--- /dev/null
+++ b/search/searcher/search_geoshape_polygon_test.go
@@ -0,0 +1,682 @@
+//  Copyright (c) 2022 Couchbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 		http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package searcher
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/blevesearch/bleve/v2/document"
+	"github.com/blevesearch/bleve/v2/geo"
+	"github.com/blevesearch/bleve/v2/index/scorch"
+	"github.com/blevesearch/bleve/v2/index/upsidedown/store/gtreap"
+	"github.com/blevesearch/bleve/v2/search"
+	index "github.com/blevesearch/bleve_index_api"
+)
+
+func TestGeoJsonPolygonIntersectsQuery(t *testing.T) {
+	tests := []struct {
+		polygon [][][]float64
+		field   string
+		want    []string
+	}{
+		// test intersecting query polygon for polygon1.
+		{[][][]float64{{{77.57926940917969, 12.945257483731918},
+			{77.57875442504883, 12.942036966318216}, {77.58278846740721, 12.9424970427816},
+			{77.57926940917969, 12.945257483731918}}}, "geometry", []string{"polygon1"}},
+
+		// test intersecting query polygon for polygon1, polygon2, circle1.
+		{[][][]float64{{{77.59562015533446, 12.94099133483504},
+			{77.59665012359619, 12.949356263896634}, {77.59313106536865, 12.951321981484776},
+			{77.59085655212402, 12.948477959536318}, {77.59562015533446, 12.94099133483504}}},
+			"geometry", []string{"polygon1", "polygon2", "circle1"}},
+
+		// test intersecting query polygon for polygon1, polygon2 and polygon3.
+		{[][][]float64{{{77.5929594039917, 12.939151012774925},
+			{77.58321762084961, 12.94546660680072}, {77.59737968444824, 12.931998723107322},
+			{77.60111331939697, 12.955169724209911}, {77.59592056274414, 12.936265025833965},
+			{77.5929594039917, 12.939151012774925},
+		}}, "geometry", []string{"polygon1", "polygon2", "polygon3"}},
+
+		// test intersecting query polygon for polygon2 and the circle1.
+		{[][][]float64{{{77.59012699127197, 12.959853852513307},
+			{77.59836673736572, 12.959853852513307}, {77.59836673736572, 12.965541604118611},
+			{77.59012699127197, 12.965541604118611}, {77.59012699127197, 12.959853852513307}}},
+			"geometry", []string{"polygon2", "circle1"}},
+
+		// test intersecting query polygon for linestring2 and multilinestring2.
+		{[][][]float64{{{77.59669303894043, 12.989504011681609},
+			{77.60699272155762, 12.983231353311314}, {77.60115623474121, 12.993183897537897},
+			{77.59669303894043, 12.989504011681609}}},
+			"geometry", []string{"linestring2", "multilinestring2"}},
+
+		// test intersecting query polygon for multilinestring2.
+		{[][][]float64{{{77.60124206542969, 12.987162237749484},
+			{77.60330200195312, 12.992849364713313}, {77.59514808654785, 12.989671280403403},
+			{77.60124206542969, 12.987162237749484}}},
+			"geometry", []string{"multilinestring2"}},
+
+		// test intersecting query polygon for multipoint1.
+		{[][][]float64{{{77.56648063659668, 12.956382587313202},
+			{77.56819725036621, 12.949523559614263}, {77.5718879699707, 12.958222782120954},
+			{77.56648063659668, 12.956382587313202}}},
+			"geometry", []string{"multipoint1"}},
+
+		// test intersecting query polygon for envelope1.
+		{[][][]float64{{{36.19986534118652, 50.00034673534484},
+			{36.19351387023926, 50.00464984215712},
+			{36.178321838378906, 49.991573824716205},
+			{36.19986534118652, 50.00034673534484}}}, "geometry", []string{"envelope1"}},
+
+		// test intersecting query polygon for envelope1.
+		{[][][]float64{{{36.170082092285156, 49.99229116680205},
+			{36.14982604980469, 49.99002874388075}, {36.227073669433594, 49.98754547425633},
+			{36.170082092285156, 49.99229116680205}}}, "geometry", []string{"envelope1"}},
+	}
+	i := setupGeoJsonShapesIndexForPolygonQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePolygonQueryWithRelation("intersects",
+			indexReader, test.polygon, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v", n,
+				test.want, got, test.polygon)
+		}
+	}
+}
+
+func TestGeoJsonPolygonContainsQuery(t *testing.T) {
+	tests := []struct {
+		polygon [][][]float64
+		field   string
+		want    []string
+	}{
+
+		// test containment query polygon for polygon1.
+		{[][][]float64{{{77.5843334197998, 12.952702156906767},
+			{77.58510589599608, 12.952702156906767}, {77.58510589599608, 12.953622269606669},
+			{77.5843334197998, 12.953622269606669}, {77.5843334197998, 12.952702156906767}}},
+			"geometry", []string{"polygon1"}},
+
+		// test containment query polygon for circle1.
+		{[][][]float64{{{77.59025573730469, 12.953810474058429},
+			{77.59145736694336, 12.953810474058429}, {77.59145736694336, 12.954918786278716},
+			{77.59025573730469, 12.954918786278716}, {77.59025573730469, 12.953810474058429}}},
+			"geometry", []string{"circle1"}},
+
+		// test containment query polygon for polygon2, polygon3.
+		{[][][]float64{{{77.60235786437988, 12.956884459972992},
+			{77.60124206542969, 12.956800814599926}, {77.6008129119873, 12.955713422193524},
+			{77.60244369506836, 12.955211547173878}, {77.60313034057617, 12.955880713641998},
+			{77.60235786437988, 12.956884459972992}}},
+			"geometry", []string{"polygon2", "polygon3"}},
+
+		// test containment query polygon which resides within a hole in polygonWithHole1.
+		{[][][]float64{{{77.60012626647949, 12.97963495776207},
+			{77.5978946685791, 12.978213112610835}, {77.60089874267577, 12.977962197916442},
+			{77.60012626647949, 12.97963495776207}}},
+			"geometry", nil},
+
+		// test containment query polygon which resides within polygonWithHole1.
+		{[][][]float64{{{77.59978294372559, 12.984067716910454},
+			{77.59780883789062, 12.982227713276774}, {77.60089874267577, 12.982227713276774},
+			{77.59978294372559, 12.984067716910454}}},
+			"geometry", []string{"polygonWithHole1"}},
+
+		// test with query polygon for polygon4 with a single vertex lying outside.
+		{[][][]float64{{{-121.48138761520384, 38.50964107572585},
+			{-121.48226737976073, 38.509238097766875}, {-121.48115158081055, 38.50781086602439},
+			{-121.48014307022095, 38.50806273250507}, {-121.48138761520384, 38.50964107572585}}},
+			"geometry", nil},
+
+		// test with query polygon for polygon4.
+		{[][][]float64{{{-121.48381233215332, 38.507974579337045},
+			{-121.48361384868622, 38.507869634948676}, {-121.48361921310425, 38.50765135013098},
+			{-121.48343682289122, 38.50797038156446}, {-121.48381233215332, 38.507974579337045}}},
+			"geometry", []string{"polygon4"}},
+
+		// test with query polygon for multipolygon1.
+		{[][][]float64{{{-121.47578716278075, 38.51617236229197},
+			{-121.47578716278075, 38.51566868518406}, {-121.47546529769896, 38.516105205547866},
+			{-121.47578716278075, 38.51617236229197}}}, "geometry", []string{"multipolygon1"}},
+
+		// test with query polygon for envelope1.
+		{[][][]float64{{{36.197547912597656, 49.99642946989866},
+			{36.18939399719238, 49.988649165474}, {36.20201110839844, 49.98853879749191},
+			{36.1970329284668, 49.980150089789376}, {36.205787658691406,
+				49.9885939815146}, {36.197547912597656, 49.99642946989866}}},
+			"geometry", []string{"envelope1"}},
+
+		// test with query polygon for no hits. (envelope1 has one vertex outside the polygon)
+		{[][][]float64{{{36.19832038879394, 49.99626394461266},
+			{36.19016647338867, 49.98439981533724}, {36.20698928833008, 49.98158510403259},
+			{36.19832038879394, 49.99626394461266}}}, "geometry", nil},
+	}
+	i := setupGeoJsonShapesIndexForPolygonQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePolygonQueryWithRelation("contains",
+			indexReader, test.polygon, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.polygon)
+		}
+	}
+}
+
+func TestGeoJsonPolygonWithInQuery(t *testing.T) {
+	tests := []struct {
+		polygon [][][]float64
+		field   string
+		want    []string
+	}{
+
+		// test with query polygon for polygon1.
+		{[][][]float64{{{77.58407592773438, 12.956382587313202},
+			{77.57746696472168, 12.943249893344905}, {77.5920581817627, 12.944086391304364},
+			{77.59454727172852, 12.95353862313803}, {77.58407592773438, 12.956382587313202}}},
+			"geometry", []string{"polygon1"}},
+
+		// test with query polygon for circle1 and polygon3.
+		{[][][]float64{{{77.59248733520508, 12.967841760870071},
+			{77.58261680603027, 12.968594534825176}, {77.57789611816406, 12.957302686416881},
+			{77.58896827697754, 12.945341132980488}, {77.60450363159178, 12.947599652080394},
+			{77.60673522949219, 12.96483064227584}, {77.59248733520508, 12.967841760870071}}},
+			"geometry", []string{"polygon3", "circle1"}},
+
+		// test with query polygon for linestring2, multilinestring2.
+		{[][][]float64{{{77.59909629821777, 12.998118204343788},
+			{77.58931159973145, 12.978882217224443}, {77.61128425598145, 12.983565899088745},
+			{77.59909629821777, 12.998118204343788}}}, "geometry",
+			[]string{"linestring2", "multilinestring2"}},
+
+		// test with query polygon for multipoint1.
+		{[][][]float64{{{77.55703926086426, 12.964245142762644},
+			{77.5631332397461, 12.944253690559432}, {77.57429122924805, 12.957720912158363},
+			{77.55703926086426, 12.964245142762644}}}, "geometry", []string{"multipoint1"}},
+
+		// test with query polygon with no results.
+		// (polygon4 has one vertex lying outside the query polygon).
+		{[][][]float64{{{-121.48812532424927, 38.51058134885975},
+			{-121.48258924484252, 38.500153704565065}, {-121.47492885589598, 38.50799556819636},
+			{-121.48630142211913, 38.51147123890908}, {-121.48812532424927, 38.51058134885975}}},
+			"geometry", nil},
+
+		// test with query polygon for polygon4.
+		{[][][]float64{{{-121.48366212844849, 38.510161585585045},
+			{-121.48533582687377, 38.50841534409804}, {-121.48376941680908, 38.507777283760426},
+			{-121.48370504379272, 38.50250467407243}, {-121.48010015487672, 38.50253825879518},
+			{-121.48018598556519, 38.504502937819765}, {-121.47756814956665, 38.50755899866278},
+			{-121.48113012313843, 38.50866720846446}, {-121.48115158081055, 38.51017837616302},
+			{-121.48366212844849, 38.510161585585045}}}, "geometry", []string{"polygon4"}},
+
+		// test with query polygon for envelope1.
+		{[][][]float64{{{36.20587348937988, 50.00470500769241},
+			{36.17969512939453, 49.993946530777606}, {36.19368553161621, 49.971870325635074},
+			{36.21119499206543, 49.983075265826656}, {36.20587348937988, 50.00470500769241}}},
+			"geometry", []string{"envelope1"}},
+
+		// test with query polygon for linestring2 which lies outside except the endpoints.
+		{[][][]float64{{{8.515305519104004, 47.392597129887},
+			{8.514232635498047, 47.38896544894171}, {8.507537841796875, 47.38815191810328},
+			{8.514318466186523, 47.38725120859953}, {8.516035079956053, 47.383357642070706},
+			{8.516979217529295, 47.38733837470806}, {8.522472381591797, 47.38794853343167},
+			{8.516507148742676, 47.388994503382285}, {8.515305519104004, 47.392597129887}}},
+			"geometry", nil},
+	}
+
+	i := setupGeoJsonShapesIndexForPolygonQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapePolygonQueryWithRelation("within",
+			indexReader, test.polygon, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.polygon)
+		}
+	}
+}
+
+func runGeoShapePolygonQueryWithRelation(relation string, i index.IndexReader,
+	points [][][]float64, field string) ([]string, error) {
+	var rv []string
+	s := geo.NewGeoJsonPolygon(points)
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation, field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForPolygonQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon1 := [][][][]float64{{{{77.5853419303894, 12.953977766785052},
+		{77.58405447006226, 12.95393594361393}, {77.5819730758667, 12.9495026476557},
+		{77.58068561553955, 12.94883346405509}, {77.58019208908081, 12.948331575175299},
+		{77.57991313934326, 12.943814529775414}, {77.58497714996338, 12.94394000436408},
+		{77.58517026901245, 12.9446301134728}, {77.58572816848755, 12.945508431393435},
+		{77.58785247802734, 12.946365833997325}, {77.58967638015747, 12.946428570657417},
+		{77.59070634841918, 12.947474179333993}, {77.59317398071289, 12.948875288082773},
+		{77.59167194366454, 12.949962710338657}, {77.59077072143555, 12.950276388953625},
+		{77.59098529815674, 12.951196510612728}, {77.58729457855225, 12.952472128200755},
+		{77.5853419303894, 12.953977766785052}}}}
+	doc := document.NewDocument("polygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon2 := [][][][]float64{{{{77.59527683258057, 12.951112863329588},
+		{77.59420394897461, 12.947976069940545}, {77.59579181671143, 12.946010325958518},
+		{77.60347366333008, 12.950401860289055}, {77.60673522949219, 12.95600618215462},
+		{77.60107040405273, 12.96345053407734}, {77.5984525680542, 12.961861309096507},
+		{77.59527683258057, 12.951112863329588}}}}
+	doc = document.NewDocument("polygon2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon2, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon3 := [][][][]float64{{{{77.59974002838135, 12.953789562459688},
+		{77.60347366333008, 12.953789562459688}, {77.60347366333008, 12.957720912158363},
+		{77.59974002838135, 12.957720912158363}, {77.59974002838135, 12.953789562459688}}}}
+	doc = document.NewDocument("polygon3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon3, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	/*polygon4 := [][][][]float64{{{{8.515305519104004, 47.392597129887},
+		{8.514232635498047, 47.38896544894171}, {8.507537841796875, 47.38815191810328},
+		{8.514318466186523, 47.38725120859953}, {8.516035079956053, 47.383357642070706},
+		{8.516979217529295, 47.38733837470806}, {8.522472381591797, 47.38794853343167},
+		{8.516507148742676, 47.388994503382285}, {8.515305519104004, 47.392597129887}}}}
+	doc = document.NewDocument("polygon4")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon4, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}*/
+
+	// not working envelope
+	envelope1 := [][][][]float64{{{{36.18896484375, 49.9799293145682},
+		{36.20613098144531, 49.99714673955337}}}}
+	doc = document.NewDocument("envelope1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		envelope1, "envelope", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	doc = document.NewDocument("circle1")
+	doc.AddField(document.NewGeoCircleFieldWithIndexingOptions("geometry",
+		[]uint64{}, []float64{77.59253025054932, 12.955587953533424}, 900,
+		document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring := [][][][]float64{{{{77.5841188430786, 12.957093573282744},
+		{77.57776737213135, 12.952074805390097}}}}
+	doc = document.NewDocument("linestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring1 := [][][][]float64{{{{77.60188579559325, 12.982604078764705},
+		{77.60557651519775, 12.987329508048184}}}}
+	doc = document.NewDocument("linestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring1, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	linestring3 := [][][][]float64{{{{8.51539134979248, 47.390592472948434},
+		{8.520884513854979, 47.388006643417924}}}}
+	doc = document.NewDocument("linestring3")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		linestring3, "linestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring := [][][][]float64{{{{77.57227420806883, 12.948687079902895},
+		{77.57600784301758, 12.954165970968194}},
+		{{77.5779390335083, 12.94471376293191}, {77.57218837738037, 12.948268838994263}},
+		{{77.57781028747559, 12.951740217268595}, {77.5779390335083, 12.945006535817749}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring1 := [][][][]float64{{{{77.6015853881836, 12.990089451715061},
+		{77.60476112365723, 12.987747683302153}},
+		{{77.59875297546387, 12.988751301039581}, {77.59446144104004, 12.98197680263484}},
+		{{77.60188579559325, 12.982604078764705}, {77.60557651519775, 12.987329508048184}}}}
+	doc = document.NewDocument("multilinestring2")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring1, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{77.56618022918701, 12.958180959662695},
+		{77.56407737731932, 12.951614746607163},
+		{77.56922721862793, 12.956173473406446}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipoint1, "multipoint", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygonWithHole1 := [][][][]float64{{
+		{{77.59991168975829, 12.972232910164502}, {77.6039457321167, 12.97582941279006},
+			{77.60424613952637, 12.98168407323241}, {77.59974002838135, 12.985489528568463},
+			{77.59321689605713, 12.979300406693417}, {77.59991168975829, 12.972232910164502}},
+		{{77.59682178497314, 12.975787593290978}, {77.60295867919922, 12.975787593290978},
+			{77.60295867919922, 12.98143316204164}, {77.59682178497314, 12.98143316204164},
+			{77.59682178497314, 12.975787593290978}}}}
+
+	doc = document.NewDocument("polygonWithHole1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygonWithHole1, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	polygon4 := [][][][]float64{{{{-121.48125886917113, 38.51009442323401},
+		{-121.48361921310425, 38.51012800441735}, {-121.48497104644774, 38.50858325377352},
+		{-121.48366212844849, 38.507861239391026}, {-121.48353338241577, 38.50277335141579},
+		{-121.4803147315979, 38.50267259752949}, {-121.48033618927, 38.5046204810195},
+		{-121.47771835327147, 38.50754220747402}, {-121.48123741149902, 38.508616835661655},
+		{-121.48125886917113, 38.51009442323401}}}}
+
+	doc = document.NewDocument("polygon4")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		polygon4, "polygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipolygon1 := [][][][]float64{{{{-121.49104356765746, 38.52149433504263},
+		{-121.47857666015625, 38.51592052417851}, {-121.47688150405884, 38.515970891871696},
+		{-121.4770746231079, 38.51714612804143}, {-121.49033546447754, 38.52221621271097},
+		{-121.49104356765746, 38.52149433504263}},
+		{{-121.47647380828859, 38.51714612804143}, {-121.47658109664916, 38.51477884701455},
+			{-121.4741563796997, 38.5159876810949}, {-121.47647380828859, 38.51714612804143}}}}
+
+	doc = document.NewDocument("multipolygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipolygon1, "multipolygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}
+
+func TestGeoJsonMultiPolygonWithInQuery(t *testing.T) {
+	tests := []struct {
+		polygon [][][][]float64
+		field   string
+		want    []string
+	}{
+		// test within multipolygon query for multipolygon1.
+		// (where each query polygon contains each of the indexed polygons)
+		{[][][][]float64{{
+			{{-121.49458408355713, 38.53270780324851}, {-121.48823261260985, 38.52533866992879},
+				{-121.48048639297485, 38.53253994984147}, {-121.49458408355713, 38.53270780324851}},
+			{{-121.48700952529907, 38.53306029412857}, {-121.48160219192505, 38.53306029412857},
+				{-121.48160219192505, 38.53829709805414}, {-121.48700952529907, 38.53829709805414},
+				{-121.48700952529907, 38.53306029412857}},
+			{{-121.47344827651976, 38.54475865436684}, {-121.46396398544312, 38.54475865436684},
+				{-121.46396398544312, 38.55366961462033},
+				{-121.47344827651976, 38.55366961462033}, {-121.47344827651976, 38.54475865436684}}}},
+			"geometry", []string{"multipolygon1"}},
+
+		// test within multipolygon query. (only partial containment of the three
+		// indexed polygons by the two query polygons)
+		{[][][][]float64{{
+			{{-121.49458408355713, 38.53270780324851}, {-121.48823261260985, 38.52533866992879},
+				{-121.48048639297485, 38.53253994984147}, {-121.49458408355713, 38.53270780324851}},
+			{{-121.48700952529907, 38.53306029412857}, {-121.48160219192505, 38.53306029412857},
+				{-121.48160219192505, 38.53829709805414}, {-121.48700952529907, 38.53829709805414},
+				{-121.48700952529907, 38.53306029412857}},
+			{{-121.4734697341919, 38.544825784372485}, {-121.4644145965576, 38.544825784372485},
+				{-121.4644145965576, 38.5537199558913}, {-121.4734697341919, 38.5537199558913},
+				{-121.4734697341919, 38.544825784372485}}}},
+			"geometry", nil},
+
+		// test within multipolygon query for multilinestring1.
+		{[][][][]float64{{{{-121.49876832962036, 38.551739839324334},
+			{-121.49814605712889, 38.54553064564853}, {-121.49158000946044, 38.54908841140355},
+			{-121.49876832962036, 38.551739839324334}},
+			{{-121.49258852005006, 38.54294612052762}, {-121.49117231369017, 38.54294612052762},
+				{-121.49117231369017, 38.54526212788182}, {-121.49258852005006, 38.54526212788182},
+				{-121.49258852005006, 38.54294612052762}},
+		}}, "geometry", []string{"multilinestring1"}},
+
+		// test within multipolygon query for multipoint1.
+		{[][][][]float64{{{{-121.50286674499512, 38.564810956372185},
+			{-121.49694442749023, 38.56226068115802}, {-121.48406982421875, 38.5675624676039},
+			{-121.4875030517578, 38.57514535565976}, {-121.50286674499512, 38.564810956372185}},
+			{{-121.48685932159422, 38.565163289911425}, {-121.48623704910278, 38.56283114531348},
+				{-121.48357629776001, 38.565129734410704}, {-121.48685932159422, 38.565163289911425}},
+			{{-121.49430513381958, 38.56195866888961}, {-121.4899492263794, 38.5584518779682},
+				{-121.48842573165892, 38.56194189039304}, {-121.49430513381958, 38.56195866888961}},
+		}}, "geometry", []string{"multipoint1"}},
+	}
+	i := setupGeoJsonShapesIndexForMultiPolygonQuery(t)
+	indexReader, err := i.Reader()
+	if err != nil {
+		t.Error(err)
+	}
+	defer func() {
+		err = indexReader.Close()
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	for n, test := range tests {
+		got, err := runGeoShapeMultiPolygonQueryWithRelation("within",
+			indexReader, test.polygon, test.field)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !reflect.DeepEqual(got, test.want) {
+			t.Errorf("test %d, expected %v, got %v for polygon: %+v",
+				n, test.want, got, test.polygon)
+		}
+	}
+}
+
+func runGeoShapeMultiPolygonQueryWithRelation(relation string,
+	i index.IndexReader,
+	points [][][][]float64, field string) ([]string, error) {
+	var rv []string
+	s := geo.NewGeoJsonMultiPolygon(points)
+
+	gbs, err := NewGeoShapeSearcher(i, s, relation,
+		field, 1.0, search.SearcherOptions{})
+	if err != nil {
+		return nil, err
+	}
+	ctx := &search.SearchContext{
+		DocumentMatchPool: search.NewDocumentMatchPool(gbs.DocumentMatchPoolSize(), 0),
+	}
+	docMatch, err := gbs.Next(ctx)
+	for docMatch != nil && err == nil {
+		docID, _ := i.ExternalID(docMatch.IndexInternalID)
+		rv = append(rv, docID)
+		docMatch, err = gbs.Next(ctx)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return rv, nil
+}
+
+func setupGeoJsonShapesIndexForMultiPolygonQuery(t *testing.T) index.Index {
+	analysisQueue := index.NewAnalysisQueue(1)
+	i, err := scorch.NewScorch(
+		gtreap.Name,
+		map[string]interface{}{
+			"path":          "",
+			"spatialPlugin": "s2",
+		},
+		analysisQueue)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = i.Open()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipolygon1 := [][][][]float64{{{{-121.49140834808348, 38.5320028163074},
+		{-121.49112939834593, 38.52916601331889}, {-121.48889780044556, 38.52913244101627},
+		{-121.4887261390686, 38.527655244193205}, {-121.48559331893921, 38.52794061412457},
+		{-121.48638725280762, 38.53213710006686}, {-121.49140834808348, 38.5320028163074}}, // polygon1
+		{{-121.48677349090575, 38.533194575914315}, {-121.48179531097412, 38.533194575914315},
+			{-121.48179531097412, 38.53814604174215}, {-121.48677349090575, 38.53814604174215},
+			{-121.48677349090575, 38.533194575914315}}, // polygon2
+		{{-121.47334098815918, 38.553485029658475}, {-121.47329807281494, 38.54485934935182},
+			{-121.46415710449219, 38.54526212788182}, {-121.47334098815918, 38.553485029658475}}}}
+	doc := document.NewDocument("multipolygon1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipolygon1, "multipolygon", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multilinestring1 := [][][][]float64{{{{-121.4983820915222, 38.55081688500274},
+		{-121.49649381637572, 38.550447699956685}},
+		{{-121.49655818939209, 38.548635309508775}, {-121.49370431900023, 38.54811507788636}},
+		{{-121.49134397506714, 38.54490969679143}, {-121.4919662475586, 38.54304681805045}}}}
+	doc = document.NewDocument("multilinestring1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multilinestring1, "multilinestring", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	multipoint1 := [][][][]float64{{{{-121.48960590362547, 38.56066671319285},
+		{-121.4933180809021, 38.56157276247755}, {-121.4973521232605, 38.56318348855919},
+		{-121.48582935333252, 38.56736114108619}, {-121.50104284286498, 38.56449217691959},
+		{-121.4881682395935, 38.57158887950165}}}}
+	doc = document.NewDocument("multipoint1")
+	doc.AddField(document.NewGeoShapeFieldWithIndexingOptions("geometry", []uint64{},
+		multipoint1, "multipoint", document.DefaultGeoShapeIndexingOptions))
+	err = i.Update(doc)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return i
+}