265 lines
7.2 KiB
Go
265 lines
7.2 KiB
Go
// 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"
|
|
"github.com/blevesearch/geo/geojson"
|
|
)
|
|
|
|
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
|
|
encodedValue []byte
|
|
value []byte
|
|
|
|
frequencies index.TokenFrequencies
|
|
}
|
|
|
|
func (n *GeoShapeField) Size() int {
|
|
var freqSize int
|
|
if n.frequencies != nil {
|
|
freqSize = n.frequencies.Size()
|
|
}
|
|
return reflectStaticSizeGeoShapeField + size.SizeOfPtr +
|
|
len(n.name) +
|
|
len(n.arrayPositions)*size.SizeOfUint64 +
|
|
len(n.encodedValue) +
|
|
len(n.value) +
|
|
freqSize
|
|
}
|
|
|
|
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)
|
|
|
|
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 (n *GeoShapeField) EncodedShape() []byte {
|
|
return n.encodedValue
|
|
}
|
|
|
|
func NewGeoShapeField(name string, arrayPositions []uint64,
|
|
coordinates [][][][]float64, typ string) *GeoShapeField {
|
|
return NewGeoShapeFieldWithIndexingOptions(name, arrayPositions,
|
|
coordinates, typ, DefaultGeoShapeIndexingOptions)
|
|
}
|
|
|
|
func NewGeoShapeFieldFromBytes(name string, arrayPositions []uint64,
|
|
value []byte) *GeoShapeField {
|
|
return &GeoShapeField{
|
|
name: name,
|
|
arrayPositions: arrayPositions,
|
|
value: value,
|
|
options: DefaultGeoShapeIndexingOptions,
|
|
numPlainTextBytes: uint64(len(value)),
|
|
}
|
|
}
|
|
|
|
func NewGeoShapeFieldWithIndexingOptions(name string, arrayPositions []uint64,
|
|
coordinates [][][][]float64, typ string,
|
|
options index.FieldIndexingOptions) *GeoShapeField {
|
|
shape := &geojson.GeoShape{
|
|
Coordinates: coordinates,
|
|
Type: typ,
|
|
}
|
|
|
|
return NewGeoShapeFieldFromShapeWithIndexingOptions(name,
|
|
arrayPositions, shape, options)
|
|
}
|
|
|
|
func NewGeoShapeFieldFromShapeWithIndexingOptions(name string, arrayPositions []uint64,
|
|
geoShape *geojson.GeoShape, options index.FieldIndexingOptions) *GeoShapeField {
|
|
|
|
var shape index.GeoJSON
|
|
var encodedValue []byte
|
|
var err error
|
|
|
|
if geoShape.Type == geo.CircleType {
|
|
shape, encodedValue, err = geo.NewGeoCircleShape(geoShape.Center, geoShape.Radius)
|
|
} else {
|
|
shape, encodedValue, err = geo.NewGeoJsonShape(geoShape.Coordinates, geoShape.Type)
|
|
}
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// extra glue bytes to work around the term splitting logic from interfering
|
|
// the custom encoding of the geoshape coordinates inside the docvalues.
|
|
encodedValue = append(geo.GlueBytes, append(encodedValue, geo.GlueBytes...)...)
|
|
|
|
// get the byte value for the geoshape.
|
|
value, err := shape.Value()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// docvalues are always enabled for geoshape fields, even if the
|
|
// indexing options are set to not include docvalues.
|
|
options = options | index.DocValues
|
|
|
|
return &GeoShapeField{
|
|
shape: shape,
|
|
name: name,
|
|
arrayPositions: arrayPositions,
|
|
options: options,
|
|
encodedValue: encodedValue,
|
|
value: value,
|
|
numPlainTextBytes: uint64(len(value)),
|
|
}
|
|
}
|
|
|
|
func NewGeometryCollectionFieldWithIndexingOptions(name string,
|
|
arrayPositions []uint64, coordinates [][][][][]float64, types []string,
|
|
options index.FieldIndexingOptions) *GeoShapeField {
|
|
if len(coordinates) != len(types) {
|
|
return nil
|
|
}
|
|
|
|
shapes := make([]*geojson.GeoShape, len(types))
|
|
for i := range coordinates {
|
|
shapes[i] = &geojson.GeoShape{
|
|
Coordinates: coordinates[i],
|
|
Type: types[i],
|
|
}
|
|
}
|
|
|
|
return NewGeometryCollectionFieldFromShapesWithIndexingOptions(name,
|
|
arrayPositions, shapes, options)
|
|
}
|
|
|
|
func NewGeometryCollectionFieldFromShapesWithIndexingOptions(name string,
|
|
arrayPositions []uint64, geoShapes []*geojson.GeoShape,
|
|
options index.FieldIndexingOptions) *GeoShapeField {
|
|
shape, encodedValue, err := geo.NewGeometryCollectionFromShapes(geoShapes)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// extra glue bytes to work around the term splitting logic from interfering
|
|
// the custom encoding of the geoshape coordinates inside the docvalues.
|
|
encodedValue = append(geo.GlueBytes, append(encodedValue, geo.GlueBytes...)...)
|
|
|
|
// get the byte value for the geometryCollection.
|
|
value, err := shape.Value()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// docvalues are always enabled for geoshape fields, even if the
|
|
// indexing options are set to not include docvalues.
|
|
options = options | index.DocValues
|
|
|
|
return &GeoShapeField{
|
|
shape: shape,
|
|
name: name,
|
|
arrayPositions: arrayPositions,
|
|
options: options,
|
|
encodedValue: encodedValue,
|
|
value: value,
|
|
numPlainTextBytes: uint64(len(value)),
|
|
}
|
|
}
|
|
|
|
func NewGeoCircleFieldWithIndexingOptions(name string, arrayPositions []uint64,
|
|
centerPoint []float64, radius string,
|
|
options index.FieldIndexingOptions) *GeoShapeField {
|
|
|
|
shape := &geojson.GeoShape{
|
|
Center: centerPoint,
|
|
Radius: radius,
|
|
Type: geo.CircleType,
|
|
}
|
|
|
|
return NewGeoShapeFieldFromShapeWithIndexingOptions(name,
|
|
arrayPositions, shape, options)
|
|
}
|
|
|
|
// GeoShape is an implementation of the index.GeoShapeField interface.
|
|
func (n *GeoShapeField) GeoShape() (index.GeoJSON, error) {
|
|
return geojson.ParseGeoJSONShape(n.value)
|
|
}
|