1//  Copyright (c) 2017 Couchbase, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// 		http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package document
16
17import (
18	"fmt"
19
20	"github.com/blevesearch/bleve/analysis"
21	"github.com/blevesearch/bleve/geo"
22	"github.com/blevesearch/bleve/numeric"
23)
24
25var GeoPrecisionStep uint = 9
26
27type GeoPointField struct {
28	name              string
29	arrayPositions    []uint64
30	options           IndexingOptions
31	value             numeric.PrefixCoded
32	numPlainTextBytes uint64
33}
34
35func (n *GeoPointField) Name() string {
36	return n.name
37}
38
39func (n *GeoPointField) ArrayPositions() []uint64 {
40	return n.arrayPositions
41}
42
43func (n *GeoPointField) Options() IndexingOptions {
44	return n.options
45}
46
47func (n *GeoPointField) Analyze() (int, analysis.TokenFrequencies) {
48	tokens := make(analysis.TokenStream, 0)
49	tokens = append(tokens, &analysis.Token{
50		Start:    0,
51		End:      len(n.value),
52		Term:     n.value,
53		Position: 1,
54		Type:     analysis.Numeric,
55	})
56
57	original, err := n.value.Int64()
58	if err == nil {
59
60		shift := GeoPrecisionStep
61		for shift < 64 {
62			shiftEncoded, err := numeric.NewPrefixCodedInt64(original, shift)
63			if err != nil {
64				break
65			}
66			token := analysis.Token{
67				Start:    0,
68				End:      len(shiftEncoded),
69				Term:     shiftEncoded,
70				Position: 1,
71				Type:     analysis.Numeric,
72			}
73			tokens = append(tokens, &token)
74			shift += GeoPrecisionStep
75		}
76	}
77
78	fieldLength := len(tokens)
79	tokenFreqs := analysis.TokenFrequency(tokens, n.arrayPositions, n.options.IncludeTermVectors())
80	return fieldLength, tokenFreqs
81}
82
83func (n *GeoPointField) Value() []byte {
84	return n.value
85}
86
87func (n *GeoPointField) Lon() (float64, error) {
88	i64, err := n.value.Int64()
89	if err != nil {
90		return 0.0, err
91	}
92	return geo.MortonUnhashLon(uint64(i64)), nil
93}
94
95func (n *GeoPointField) Lat() (float64, error) {
96	i64, err := n.value.Int64()
97	if err != nil {
98		return 0.0, err
99	}
100	return geo.MortonUnhashLat(uint64(i64)), nil
101}
102
103func (n *GeoPointField) GoString() string {
104	return fmt.Sprintf("&document.GeoPointField{Name:%s, Options: %s, Value: %s}", n.name, n.options, n.value)
105}
106
107func (n *GeoPointField) NumPlainTextBytes() uint64 {
108	return n.numPlainTextBytes
109}
110
111func NewGeoPointFieldFromBytes(name string, arrayPositions []uint64, value []byte) *GeoPointField {
112	return &GeoPointField{
113		name:              name,
114		arrayPositions:    arrayPositions,
115		value:             value,
116		options:           DefaultNumericIndexingOptions,
117		numPlainTextBytes: uint64(len(value)),
118	}
119}
120
121func NewGeoPointField(name string, arrayPositions []uint64, lon, lat float64) *GeoPointField {
122	return NewGeoPointFieldWithIndexingOptions(name, arrayPositions, lon, lat, DefaultNumericIndexingOptions)
123}
124
125func NewGeoPointFieldWithIndexingOptions(name string, arrayPositions []uint64, lon, lat float64, options IndexingOptions) *GeoPointField {
126	mhash := geo.MortonHash(lon, lat)
127	prefixCoded := numeric.MustNewPrefixCodedInt64(int64(mhash), 0)
128	return &GeoPointField{
129		name:           name,
130		arrayPositions: arrayPositions,
131		value:          prefixCoded,
132		options:        options,
133		// not correct, just a place holder until we revisit how fields are
134		// represented and can fix this better
135		numPlainTextBytes: uint64(8),
136	}
137}
138