Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
312
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
Normal file
312
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
# Geo spatial search support in bleve
|
||||
|
||||
Latest bleve spatial capabilities are powered by spatial hierarchical tokens generated from s2geometry.
|
||||
You can find more details about the [s2geometry basics here](http://s2geometry.io/), and explore the extended functionality of our forked golang port of [s2geometry lib here](https://github.com/blevesearch/geo).
|
||||
|
||||
Users can continue to index and query `geopoint` field type and the existing queries like,
|
||||
|
||||
- Point Distance
|
||||
- Bounded Rectangle
|
||||
- Bounded Polygon
|
||||
|
||||
as before.
|
||||
|
||||
## New Spatial Field Type - geoshape
|
||||
|
||||
We have introduced a field type (`geoshape`) for representing the new spatial types.
|
||||
|
||||
Using the new `geoshape` field type, users can unblock the spatial capabilities
|
||||
for the [geojson](https://datatracker.ietf.org/doc/html/rfc7946) shapes like,
|
||||
|
||||
- Point
|
||||
- LineString
|
||||
- Polygon
|
||||
- MultiPoint
|
||||
- MultiLineString
|
||||
- MultiPolygon
|
||||
- GeometryCollection
|
||||
|
||||
In addition to these shapes, bleve will also support additional shapes like,
|
||||
|
||||
- Circle
|
||||
- Envelope (Bounded box)
|
||||
|
||||
To specify GeoJSON data, use a nested field with:
|
||||
|
||||
- a field named type that specifies the GeoJSON object type and the type value will be case-insensitive.
|
||||
- a field named coordinates that specifies the object's coordinates.
|
||||
|
||||
```text
|
||||
"fieldName": {
|
||||
"type": "GeoJSON Type",
|
||||
"coordinates": <coordinates>
|
||||
}
|
||||
```
|
||||
|
||||
- If specifying latitude and longitude coordinates, list the longitude first and then latitude.
|
||||
- Valid longitude values are between -180 and 180, both inclusive.
|
||||
- Valid latitude values are between -90 and 90, both inclusive.
|
||||
- Shapes would be internally represented as geodesics.
|
||||
- The GeoJSON specification strongly suggests splitting geometries so that neither of their parts crosses the antimeridian.
|
||||
|
||||
Examples for the various geojson shapes representations are as below.
|
||||
|
||||
## Point
|
||||
|
||||
The following specifies a [Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "point",
|
||||
"coordinates": [75.05687713623047, 22.53539059204079]
|
||||
}
|
||||
```
|
||||
|
||||
## Linestring
|
||||
|
||||
The following specifies a [Linestring](https://tools.ietf.org/html/rfc7946#section-3.1.4) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "linestring",
|
||||
"coordinates": [
|
||||
[77.01416015625, 23.0797317624497],
|
||||
[78.134765625, 20.385825381874263]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Polygon
|
||||
|
||||
The following specifies a [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[85.605, 57.207],
|
||||
[86.396, 55.998],
|
||||
[87.033, 56.716],
|
||||
[85.605, 57.207]
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The first and last coordinates must match in order to close the polygon.
|
||||
And the exterior coordinates have to be in Counter Clockwise Order in a polygon. (CCW)
|
||||
|
||||
## MultiPoint
|
||||
|
||||
The following specifies a [Multipoint](https://tools.ietf.org/html/rfc7946#section-3.1.3) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "multipoint",
|
||||
"coordinates": [
|
||||
[-115.8343505859375, 38.45789034424927],
|
||||
[-115.81237792968749, 38.19502155795575],
|
||||
[-120.80017089843749, 36.54053616262899],
|
||||
[-120.67932128906249, 36.33725319397006]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## MultiLineString
|
||||
|
||||
The following specifies a [MultiLineString](https://tools.ietf.org/html/rfc7946#section-3.1.5) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "multilinestring",
|
||||
"coordinates": [
|
||||
[
|
||||
[-118.31726074, 35.250105158],
|
||||
[-117.509765624, 35.3756141]
|
||||
],
|
||||
[
|
||||
[-118.696289, 34.624167789],
|
||||
[-118.317260742, 35.03899204]
|
||||
],
|
||||
[
|
||||
[-117.9492187, 35.146862906],
|
||||
[-117.6745605, 34.41144164]
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## MultiPolygon
|
||||
|
||||
The following specifies a [MultiPolygon](https://tools.ietf.org/html/rfc7946#section-3.1.7) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "multipolygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.9814, 40.7681],
|
||||
[-73.958, 40.8003]
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.958, 40.8003]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## GeometryCollection
|
||||
|
||||
The following specifies a [GeometryCollection](https://tools.ietf.org/html/rfc7946#section-3.1.8) field in a document:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "geometrycollection",
|
||||
"geometries": [
|
||||
{
|
||||
"type": "multipoint",
|
||||
"coordinates": [
|
||||
[-73.958, 40.8003],
|
||||
[-73.9498, 40.7968],
|
||||
[-73.9737, 40.7648],
|
||||
[-73.9814, 40.7681]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "multilinestring",
|
||||
"coordinates": [
|
||||
[
|
||||
[-73.96943, 40.78519],
|
||||
[-73.96082, 40.78095]
|
||||
],
|
||||
[
|
||||
[-73.96415, 40.79229],
|
||||
[-73.95544, 40.78854]
|
||||
],
|
||||
[
|
||||
[-73.97162, 40.78205],
|
||||
[-73.96374, 40.77715]
|
||||
],
|
||||
[
|
||||
[-73.9788, 40.77247],
|
||||
[-73.97036, 40.76811]
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[0, 0],
|
||||
[3, 6],
|
||||
[6, 1],
|
||||
[0, 0]
|
||||
],
|
||||
[
|
||||
[2, 2],
|
||||
[3, 3],
|
||||
[4, 2],
|
||||
[2, 2]
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Circle
|
||||
|
||||
If the user wishes to cover a circular region over the earth's surface, then they could use this shape.
|
||||
A sample circular shape is as below.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "circle",
|
||||
"coordinates": [75.05687713623047, 22.53539059204079],
|
||||
"radius": "1000m"
|
||||
}
|
||||
```
|
||||
|
||||
Circle is specified over the center point coordinates along with the radius.
|
||||
Example formats supported for radius are:
|
||||
"5in" , "5inch" , "7yd" , "7yards", "9ft" , "9feet", "11km", "11kilometers", "3nm", "3nauticalmiles", "13mm" , "13millimeters", "15cm", "15centimeters", "17mi", "17miles", "19m" or "19meters".
|
||||
|
||||
If the unit cannot be determined, the entire string is parsed and the unit of meters is assumed.
|
||||
|
||||
## Envelope
|
||||
|
||||
Envelope type, which consists of coordinates for upper left and lower right points of the shape to represent a bounding rectangle in the format [[minLon, maxLat], [maxLon, minLat]].
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "envelope",
|
||||
"coordinates": [
|
||||
[72.83, 18.979],
|
||||
[78.508, 17.4555]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## GeoShape Query
|
||||
|
||||
Geoshape query support three types/filters of spatial querying capability across those heterogeneous types of documents indexed.
|
||||
|
||||
### Query Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"query": {
|
||||
"geometry": {
|
||||
"shape": {
|
||||
"type": "<shapeType>",
|
||||
"coordinates": [
|
||||
[[]]
|
||||
]
|
||||
},
|
||||
"relation": "<<filterName>>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*shapeType* => can be any of the aforementioned types like Point, LineString, Polygon, MultiPoint,
|
||||
Geometrycollection, MultiLineString, MultiPolygon, Circle and Envelope.
|
||||
|
||||
*filterName* => can be any of the 3 types like *intersects*, *contains* and *within*.
|
||||
|
||||
### Relation
|
||||
|
||||
| FilterName | Description |
|
||||
| :-----------:| :-----------------------------------------------------------------: |
|
||||
| `intersects` | Return all documents whose shape field intersects the query geometry. |
|
||||
| `contains` | Return all documents whose shape field contains the query geometry |
|
||||
| `within` | Return all documents whose shape field is within the query geometry. |
|
||||
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
### Older Implementation
|
||||
|
||||
First, all of this geo code is a Go adaptation of the [Lucene 5.3.2 sandbox geo support](https://lucene.apache.org/core/5_3_2/sandbox/org/apache/lucene/util/package-summary.html).
|
||||
|
||||
## Notes
|
||||
|
||||
- All of the APIs will use float64 for lon/lat values.
|
||||
- When describing a point in function arguments or return values, we always use the order lon, lat.
|
||||
- High level APIs will use TopLeft and BottomRight to describe bounding boxes. This may not map cleanly to min/max lon/lat when crossing the dateline. The lower level APIs will use min/max lon/lat and require the higher-level code to split boxes accordingly.
|
||||
- Points and MultiPoints may only contain Points and MultiPoints.
|
||||
- LineStrings and MultiLineStrings may only contain Points and MultiPoints.
|
||||
- Polygons or MultiPolygons intersecting Polygons and MultiPolygons may return arbitrary results when the overlap is only an edge or a vertex.
|
||||
- Circles containing polygon will return a false positive result if all of the vertices of the polygon are within the circle, but the orientation of those points are clock-wise.
|
||||
- The edges of an Envelope follows the latitude and logitude lines instead of the shortest path on a globe.
|
||||
- Envelope intersecting queries with LineStrings, MultiLineStrings, Polygons and MultiPolygons implicitly converts the Envelope into a Polygon which changes the curvature of the edges causing inaccurate results for few edge cases.
|
||||
210
vendor/github.com/blevesearch/bleve/v2/geo/geo.go
generated
vendored
Normal file
210
vendor/github.com/blevesearch/bleve/v2/geo/geo.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) 2017 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/numeric"
|
||||
)
|
||||
|
||||
// GeoBits is the number of bits used for a single geo point
|
||||
// Currently this is 32bits for lon and 32bits for lat
|
||||
var GeoBits uint = 32
|
||||
|
||||
var minLon = -180.0
|
||||
var minLat = -90.0
|
||||
var maxLon = 180.0
|
||||
var maxLat = 90.0
|
||||
var minLonRad = minLon * degreesToRadian
|
||||
var minLatRad = minLat * degreesToRadian
|
||||
var maxLonRad = maxLon * degreesToRadian
|
||||
var maxLatRad = maxLat * degreesToRadian
|
||||
var geoTolerance = 1e-6
|
||||
var lonScale = float64((uint64(0x1)<<GeoBits)-1) / 360.0
|
||||
var latScale = float64((uint64(0x1)<<GeoBits)-1) / 180.0
|
||||
|
||||
var geoHashMaxLength = 12
|
||||
|
||||
// Point represents a geo point.
|
||||
type Point struct {
|
||||
Lon float64 `json:"lon"`
|
||||
Lat float64 `json:"lat"`
|
||||
}
|
||||
|
||||
// MortonHash computes the morton hash value for the provided geo point
|
||||
// This point is ordered as lon, lat.
|
||||
func MortonHash(lon, lat float64) uint64 {
|
||||
return numeric.Interleave(scaleLon(lon), scaleLat(lat))
|
||||
}
|
||||
|
||||
func scaleLon(lon float64) uint64 {
|
||||
rv := uint64((lon - minLon) * lonScale)
|
||||
return rv
|
||||
}
|
||||
|
||||
func scaleLat(lat float64) uint64 {
|
||||
rv := uint64((lat - minLat) * latScale)
|
||||
return rv
|
||||
}
|
||||
|
||||
// MortonUnhashLon extracts the longitude value from the provided morton hash.
|
||||
func MortonUnhashLon(hash uint64) float64 {
|
||||
return unscaleLon(numeric.Deinterleave(hash))
|
||||
}
|
||||
|
||||
// MortonUnhashLat extracts the latitude value from the provided morton hash.
|
||||
func MortonUnhashLat(hash uint64) float64 {
|
||||
return unscaleLat(numeric.Deinterleave(hash >> 1))
|
||||
}
|
||||
|
||||
func unscaleLon(lon uint64) float64 {
|
||||
return (float64(lon) / lonScale) + minLon
|
||||
}
|
||||
|
||||
func unscaleLat(lat uint64) float64 {
|
||||
return (float64(lat) / latScale) + minLat
|
||||
}
|
||||
|
||||
// compareGeo will compare two float values and see if they are the same
|
||||
// taking into consideration a known geo tolerance.
|
||||
func compareGeo(a, b float64) float64 {
|
||||
compare := a - b
|
||||
if math.Abs(compare) <= geoTolerance {
|
||||
return 0
|
||||
}
|
||||
return compare
|
||||
}
|
||||
|
||||
// RectIntersects checks whether rectangles a and b intersect
|
||||
func RectIntersects(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY float64) bool {
|
||||
return !(aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY)
|
||||
}
|
||||
|
||||
// RectWithin checks whether box a is within box b
|
||||
func RectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY float64) bool {
|
||||
rv := !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY)
|
||||
return rv
|
||||
}
|
||||
|
||||
// BoundingBoxContains checks whether the lon/lat point is within the box
|
||||
func BoundingBoxContains(lon, lat, minLon, minLat, maxLon, maxLat float64) bool {
|
||||
return compareGeo(lon, minLon) >= 0 && compareGeo(lon, maxLon) <= 0 &&
|
||||
compareGeo(lat, minLat) >= 0 && compareGeo(lat, maxLat) <= 0
|
||||
}
|
||||
|
||||
const degreesToRadian = math.Pi / 180
|
||||
const radiansToDegrees = 180 / math.Pi
|
||||
|
||||
// DegreesToRadians converts an angle in degrees to radians
|
||||
func DegreesToRadians(d float64) float64 {
|
||||
return d * degreesToRadian
|
||||
}
|
||||
|
||||
// RadiansToDegrees converts an angle in radians to degress
|
||||
func RadiansToDegrees(r float64) float64 {
|
||||
return r * radiansToDegrees
|
||||
}
|
||||
|
||||
var earthMeanRadiusMeters = 6371008.7714
|
||||
|
||||
func RectFromPointDistance(lon, lat, dist float64) (float64, float64, float64, float64, error) {
|
||||
err := checkLongitude(lon)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
err = checkLatitude(lat)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
radLon := DegreesToRadians(lon)
|
||||
radLat := DegreesToRadians(lat)
|
||||
radDistance := (dist + 7e-2) / earthMeanRadiusMeters
|
||||
|
||||
minLatL := radLat - radDistance
|
||||
maxLatL := radLat + radDistance
|
||||
|
||||
var minLonL, maxLonL float64
|
||||
if minLatL > minLatRad && maxLatL < maxLatRad {
|
||||
deltaLon := math.Asin(math.Sin(radDistance) / math.Cos(radLat))
|
||||
minLonL = radLon - deltaLon
|
||||
if minLonL < minLonRad {
|
||||
minLonL += 2 * math.Pi
|
||||
}
|
||||
maxLonL = radLon + deltaLon
|
||||
if maxLonL > maxLonRad {
|
||||
maxLonL -= 2 * math.Pi
|
||||
}
|
||||
} else {
|
||||
// pole is inside distance
|
||||
minLatL = math.Max(minLatL, minLatRad)
|
||||
maxLatL = math.Min(maxLatL, maxLatRad)
|
||||
minLonL = minLonRad
|
||||
maxLonL = maxLonRad
|
||||
}
|
||||
|
||||
return RadiansToDegrees(minLonL),
|
||||
RadiansToDegrees(maxLatL),
|
||||
RadiansToDegrees(maxLonL),
|
||||
RadiansToDegrees(minLatL),
|
||||
nil
|
||||
}
|
||||
|
||||
func checkLatitude(latitude float64) error {
|
||||
if math.IsNaN(latitude) || latitude < minLat || latitude > maxLat {
|
||||
return fmt.Errorf("invalid latitude %f; must be between %f and %f", latitude, minLat, maxLat)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkLongitude(longitude float64) error {
|
||||
if math.IsNaN(longitude) || longitude < minLon || longitude > maxLon {
|
||||
return fmt.Errorf("invalid longitude %f; must be between %f and %f", longitude, minLon, maxLon)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BoundingRectangleForPolygon(polygon []Point) (
|
||||
float64, float64, float64, float64, error) {
|
||||
err := checkLongitude(polygon[0].Lon)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
err = checkLatitude(polygon[0].Lat)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
maxY, minY := polygon[0].Lat, polygon[0].Lat
|
||||
maxX, minX := polygon[0].Lon, polygon[0].Lon
|
||||
for i := 1; i < len(polygon); i++ {
|
||||
err := checkLongitude(polygon[i].Lon)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
err = checkLatitude(polygon[i].Lat)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, err
|
||||
}
|
||||
|
||||
maxY = math.Max(maxY, polygon[i].Lat)
|
||||
minY = math.Min(minY, polygon[i].Lat)
|
||||
|
||||
maxX = math.Max(maxX, polygon[i].Lon)
|
||||
minX = math.Min(minX, polygon[i].Lon)
|
||||
}
|
||||
|
||||
return minX, maxY, maxX, minY, nil
|
||||
}
|
||||
98
vendor/github.com/blevesearch/bleve/v2/geo/geo_dist.go
generated
vendored
Normal file
98
vendor/github.com/blevesearch/bleve/v2/geo/geo_dist.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2017 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type distanceUnit struct {
|
||||
conv float64
|
||||
suffixes []string
|
||||
}
|
||||
|
||||
var inch = distanceUnit{0.0254, []string{"in", "inch"}}
|
||||
var yard = distanceUnit{0.9144, []string{"yd", "yards"}}
|
||||
var feet = distanceUnit{0.3048, []string{"ft", "feet"}}
|
||||
var kilom = distanceUnit{1000, []string{"km", "kilometers"}}
|
||||
var nauticalm = distanceUnit{1852.0, []string{"nm", "nauticalmiles"}}
|
||||
var millim = distanceUnit{0.001, []string{"mm", "millimeters"}}
|
||||
var centim = distanceUnit{0.01, []string{"cm", "centimeters"}}
|
||||
var miles = distanceUnit{1609.344, []string{"mi", "miles"}}
|
||||
var meters = distanceUnit{1, []string{"m", "meters"}}
|
||||
|
||||
var distanceUnits = []*distanceUnit{
|
||||
&inch, &yard, &feet, &kilom, &nauticalm, &millim, ¢im, &miles, &meters,
|
||||
}
|
||||
|
||||
// ParseDistance attempts to parse a distance string and return distance in
|
||||
// meters. Example formats supported:
|
||||
// "5in" "5inch" "7yd" "7yards" "9ft" "9feet" "11km" "11kilometers"
|
||||
// "3nm" "3nauticalmiles" "13mm" "13millimeters" "15cm" "15centimeters"
|
||||
// "17mi" "17miles" "19m" "19meters"
|
||||
// If the unit cannot be determined, the entire string is parsed and the
|
||||
// unit of meters is assumed.
|
||||
// If the number portion cannot be parsed, 0 and the parse error are returned.
|
||||
func ParseDistance(d string) (float64, error) {
|
||||
for _, unit := range distanceUnits {
|
||||
for _, unitSuffix := range unit.suffixes {
|
||||
if strings.HasSuffix(d, unitSuffix) {
|
||||
parsedNum, err := strconv.ParseFloat(d[0:len(d)-len(unitSuffix)], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsedNum * unit.conv, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// no unit matched, try assuming meters?
|
||||
parsedNum, err := strconv.ParseFloat(d, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsedNum, nil
|
||||
}
|
||||
|
||||
// ParseDistanceUnit attempts to parse a distance unit and return the
|
||||
// multiplier for converting this to meters. If the unit cannot be parsed
|
||||
// then 0 and the error message is returned.
|
||||
func ParseDistanceUnit(u string) (float64, error) {
|
||||
for _, unit := range distanceUnits {
|
||||
for _, unitSuffix := range unit.suffixes {
|
||||
if u == unitSuffix {
|
||||
return unit.conv, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("unknown distance unit: %s", u)
|
||||
}
|
||||
|
||||
// Haversin computes the distance between two points.
|
||||
// This implemenation uses the sloppy math implemenations which trade off
|
||||
// accuracy for performance. The distance returned is in kilometers.
|
||||
func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
|
||||
x1 := lat1 * degreesToRadian
|
||||
x2 := lat2 * degreesToRadian
|
||||
h1 := 1 - math.Cos(x1-x2)
|
||||
h2 := 1 - math.Cos((lon1-lon2)*degreesToRadian)
|
||||
h := (h1 + math.Cos(x1)*math.Cos(x2)*h2) / 2
|
||||
avgLat := (x1 + x2) / 2
|
||||
diameter := earthDiameter(avgLat)
|
||||
|
||||
return diameter * math.Asin(math.Min(1, math.Sqrt(h)))
|
||||
}
|
||||
463
vendor/github.com/blevesearch/bleve/v2/geo/geo_s2plugin_impl.go
generated
vendored
Normal file
463
vendor/github.com/blevesearch/bleve/v2/geo/geo_s2plugin_impl.go
generated
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
// 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"
|
||||
"sync"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/util"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
"github.com/blevesearch/geo/geojson"
|
||||
"github.com/blevesearch/geo/s2"
|
||||
)
|
||||
|
||||
const (
|
||||
PointType = "point"
|
||||
MultiPointType = "multipoint"
|
||||
LineStringType = "linestring"
|
||||
MultiLineStringType = "multilinestring"
|
||||
PolygonType = "polygon"
|
||||
MultiPolygonType = "multipolygon"
|
||||
GeometryCollectionType = "geometrycollection"
|
||||
CircleType = "circle"
|
||||
EnvelopeType = "envelope"
|
||||
)
|
||||
|
||||
// spatialPluginsMap is spatial plugin cache.
|
||||
var (
|
||||
spatialPluginsMap = make(map[string]index.SpatialAnalyzerPlugin)
|
||||
pluginsMapLock = sync.RWMutex{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerS2RegionTermIndexer()
|
||||
}
|
||||
|
||||
func registerS2RegionTermIndexer() {
|
||||
spatialPlugin := S2SpatialAnalyzerPlugin{
|
||||
s2Indexer: s2.NewRegionTermIndexerWithOptions(initS2IndexerOptions()),
|
||||
s2Searcher: s2.NewRegionTermIndexerWithOptions(initS2SearcherOptions()),
|
||||
s2GeoPointsRegionTermIndexer: s2.NewRegionTermIndexerWithOptions(initS2OptionsForGeoPoints()),
|
||||
}
|
||||
|
||||
RegisterSpatialAnalyzerPlugin(&spatialPlugin)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// initS2IndexerOptions returns the options for s2's region
|
||||
// term indexer for the index time tokens of geojson shapes.
|
||||
func initS2IndexerOptions() s2.Options {
|
||||
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(2)
|
||||
|
||||
// levelMod value greater than 1 increases the effective branching
|
||||
// factor of the S2Cell hierarchy by skipping some levels.
|
||||
options.SetLevelMod(1)
|
||||
|
||||
// maxCells controls the maximum number of cells
|
||||
// when approximating each s2 region.
|
||||
options.SetMaxCells(20)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// initS2SearcherOptions returns the options for s2's region
|
||||
// term indexer for the query time tokens of geojson shapes.
|
||||
func initS2SearcherOptions() s2.Options {
|
||||
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(2)
|
||||
|
||||
// levelMod value greater than 1 increases the effective branching
|
||||
// factor of the S2Cell hierarchy by skipping some levels.
|
||||
options.SetLevelMod(1)
|
||||
|
||||
// maxCells controls the maximum number of cells
|
||||
// when approximating each s2 region.
|
||||
options.SetMaxCells(8)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// initS2OptionsForGeoPoints returns the options for
|
||||
// s2's region term indexer for the original geopoints.
|
||||
func initS2OptionsForGeoPoints() s2.Options {
|
||||
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)
|
||||
|
||||
// explicit for geo points.
|
||||
options.SetPointsOnly(true)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
// S2SpatialAnalyzerPlugin is an implementation of
|
||||
// the index.SpatialAnalyzerPlugin interface.
|
||||
type S2SpatialAnalyzerPlugin struct {
|
||||
s2Indexer *s2.RegionTermIndexer
|
||||
s2Searcher *s2.RegionTermIndexer
|
||||
s2GeoPointsRegionTermIndexer *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.(*geojson.GeometryCollection); ok {
|
||||
shapes = gc.Shapes
|
||||
}
|
||||
|
||||
for _, shape := range shapes {
|
||||
if s2t, ok := shape.(s2Tokenizable); ok {
|
||||
rv = append(rv, s2t.IndexTokens(s.s2Indexer)...)
|
||||
} else if s2t, ok := shape.(s2TokenizableEx); ok {
|
||||
rv = append(rv, s2t.IndexTokens(s)...)
|
||||
}
|
||||
}
|
||||
|
||||
return geojson.DeduplicateTerms(rv)
|
||||
}
|
||||
|
||||
func (s *S2SpatialAnalyzerPlugin) GetQueryTokens(queryShape index.GeoJSON) []string {
|
||||
var rv []string
|
||||
shapes := []index.GeoJSON{queryShape}
|
||||
if gc, ok := queryShape.(*geojson.GeometryCollection); ok {
|
||||
shapes = gc.Shapes
|
||||
}
|
||||
|
||||
for _, shape := range shapes {
|
||||
if s2t, ok := shape.(s2Tokenizable); ok {
|
||||
rv = append(rv, s2t.QueryTokens(s.s2Searcher)...)
|
||||
} else if s2t, ok := shape.(s2TokenizableEx); ok {
|
||||
rv = append(rv, s2t.QueryTokens(s)...)
|
||||
}
|
||||
}
|
||||
|
||||
return geojson.DeduplicateTerms(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(*s2.RegionTermIndexer) []string
|
||||
|
||||
// QueryTokens returns the tokens for searching.
|
||||
QueryTokens(*s2.RegionTermIndexer) []string
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// s2TokenizableEx is an optional interface for shapes that support
|
||||
// the generation of s2 based tokens that can be used for both
|
||||
// indexing and querying. This is intended for the older geopoint
|
||||
// indexing and querying.
|
||||
type s2TokenizableEx interface {
|
||||
// IndexTokens returns the tokens for indexing.
|
||||
IndexTokens(*S2SpatialAnalyzerPlugin) []string
|
||||
|
||||
// QueryTokens returns the tokens for searching.
|
||||
QueryTokens(*S2SpatialAnalyzerPlugin) []string
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
func (p *Point) Type() string {
|
||||
return PointType
|
||||
}
|
||||
|
||||
func (p *Point) Value() ([]byte, error) {
|
||||
return util.MarshalJSON(p)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (p *Point) IndexTokens(s *S2SpatialAnalyzerPlugin) []string {
|
||||
return s.s2GeoPointsRegionTermIndexer.GetIndexTermsForPoint(s2.PointFromLatLng(
|
||||
s2.LatLngFromDegrees(p.Lat, p.Lon)), "")
|
||||
}
|
||||
|
||||
func (p *Point) QueryTokens(s *S2SpatialAnalyzerPlugin) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
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 (br *boundedRectangle) Value() ([]byte, error) {
|
||||
return util.MarshalJSON(br)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.s2GeoPointsRegionTermIndexer.GetQueryTermsForRegion(rect, "")
|
||||
|
||||
return geojson.StripCoveringTerms(terms)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
type boundedPolygon struct {
|
||||
coordinates []Point
|
||||
}
|
||||
|
||||
func NewBoundedPolygon(coordinates []Point) *boundedPolygon {
|
||||
return &boundedPolygon{coordinates: coordinates}
|
||||
}
|
||||
|
||||
func (bp *boundedPolygon) Type() string {
|
||||
// placeholder implementation
|
||||
return "boundedPolygon"
|
||||
}
|
||||
|
||||
func (bp *boundedPolygon) Value() ([]byte, error) {
|
||||
return util.MarshalJSON(bp)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.s2GeoPointsRegionTermIndexer.GetQueryTermsForRegion(
|
||||
s2polygon.CapBound(), "")
|
||||
|
||||
return geojson.StripCoveringTerms(terms)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
type pointDistance struct {
|
||||
dist float64
|
||||
centerLat float64
|
||||
centerLon float64
|
||||
}
|
||||
|
||||
func (p *pointDistance) Type() string {
|
||||
// placeholder implementation
|
||||
return "pointDistance"
|
||||
}
|
||||
|
||||
func (p *pointDistance) Value() ([]byte, error) {
|
||||
return util.MarshalJSON(p)
|
||||
}
|
||||
|
||||
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 (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.s2GeoPointsRegionTermIndexer.GetQueryTermsForRegion(queryRegion, "")
|
||||
|
||||
return geojson.StripCoveringTerms(terms)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// 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([]*geojson.GeoShape, len(coordinates))
|
||||
for i := range coordinates {
|
||||
shapes[i] = &geojson.GeoShape{
|
||||
Coordinates: coordinates[i],
|
||||
Type: typs[i],
|
||||
}
|
||||
}
|
||||
|
||||
return geojson.NewGeometryCollection(shapes)
|
||||
}
|
||||
|
||||
func NewGeometryCollectionFromShapes(shapes []*geojson.GeoShape) (
|
||||
index.GeoJSON, []byte, error) {
|
||||
|
||||
return geojson.NewGeometryCollection(shapes)
|
||||
}
|
||||
|
||||
// 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,
|
||||
radius string) (index.GeoJSON, []byte, error) {
|
||||
return geojson.NewGeoCircleShape(cp, radius)
|
||||
}
|
||||
|
||||
func NewGeoJsonShape(coordinates [][][][]float64, typ string) (
|
||||
index.GeoJSON, []byte, error) {
|
||||
return geojson.NewGeoJsonShape(coordinates, typ)
|
||||
}
|
||||
|
||||
func NewGeoJsonPoint(points []float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonPoint(points)
|
||||
}
|
||||
|
||||
func NewGeoJsonMultiPoint(points [][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonMultiPoint(points)
|
||||
}
|
||||
|
||||
func NewGeoJsonLinestring(points [][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonLinestring(points)
|
||||
}
|
||||
|
||||
func NewGeoJsonMultilinestring(points [][][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonMultilinestring(points)
|
||||
}
|
||||
|
||||
func NewGeoJsonPolygon(points [][][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonPolygon(points)
|
||||
}
|
||||
|
||||
func NewGeoJsonMultiPolygon(points [][][][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoJsonMultiPolygon(points)
|
||||
}
|
||||
|
||||
func NewGeoCircle(points []float64, radius string) index.GeoJSON {
|
||||
return geojson.NewGeoCircle(points, radius)
|
||||
}
|
||||
|
||||
func NewGeoEnvelope(points [][]float64) index.GeoJSON {
|
||||
return geojson.NewGeoEnvelope(points)
|
||||
}
|
||||
|
||||
func ParseGeoJSONShape(input json.RawMessage) (index.GeoJSON, error) {
|
||||
return geojson.ParseGeoJSONShape(input)
|
||||
}
|
||||
111
vendor/github.com/blevesearch/bleve/v2/geo/geohash.go
generated
vendored
Normal file
111
vendor/github.com/blevesearch/bleve/v2/geo/geohash.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2019 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.
|
||||
// This implementation is inspired from the geohash-js
|
||||
// ref: https://github.com/davetroy/geohash-js
|
||||
|
||||
package geo
|
||||
|
||||
// encoding encapsulates an encoding defined by a given base32 alphabet.
|
||||
type encoding struct {
|
||||
enc string
|
||||
dec [256]byte
|
||||
}
|
||||
|
||||
// newEncoding constructs a new encoding defined by the given alphabet,
|
||||
// which must be a 32-byte string.
|
||||
func newEncoding(encoder string) *encoding {
|
||||
e := new(encoding)
|
||||
e.enc = encoder
|
||||
for i := 0; i < len(e.dec); i++ {
|
||||
e.dec[i] = 0xff
|
||||
}
|
||||
for i := 0; i < len(encoder); i++ {
|
||||
e.dec[encoder[i]] = byte(i)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// base32encoding with the Geohash alphabet.
|
||||
var base32encoding = newEncoding("0123456789bcdefghjkmnpqrstuvwxyz")
|
||||
|
||||
var masks = []uint64{16, 8, 4, 2, 1}
|
||||
|
||||
// DecodeGeoHash decodes the string geohash faster with
|
||||
// higher precision. This api is in experimental phase.
|
||||
func DecodeGeoHash(geoHash string) (float64, float64) {
|
||||
even := true
|
||||
lat := []float64{-90.0, 90.0}
|
||||
lon := []float64{-180.0, 180.0}
|
||||
|
||||
for i := 0; i < len(geoHash); i++ {
|
||||
cd := uint64(base32encoding.dec[geoHash[i]])
|
||||
for j := 0; j < 5; j++ {
|
||||
if even {
|
||||
if cd&masks[j] > 0 {
|
||||
lon[0] = (lon[0] + lon[1]) / 2
|
||||
} else {
|
||||
lon[1] = (lon[0] + lon[1]) / 2
|
||||
}
|
||||
} else {
|
||||
if cd&masks[j] > 0 {
|
||||
lat[0] = (lat[0] + lat[1]) / 2
|
||||
} else {
|
||||
lat[1] = (lat[0] + lat[1]) / 2
|
||||
}
|
||||
}
|
||||
even = !even
|
||||
}
|
||||
}
|
||||
|
||||
return (lat[0] + lat[1]) / 2, (lon[0] + lon[1]) / 2
|
||||
}
|
||||
|
||||
func EncodeGeoHash(lat, lon float64) string {
|
||||
even := true
|
||||
lats := []float64{-90.0, 90.0}
|
||||
lons := []float64{-180.0, 180.0}
|
||||
precision := 12
|
||||
var ch, bit uint64
|
||||
var geoHash string
|
||||
|
||||
for len(geoHash) < precision {
|
||||
if even {
|
||||
mid := (lons[0] + lons[1]) / 2
|
||||
if lon > mid {
|
||||
ch |= masks[bit]
|
||||
lons[0] = mid
|
||||
} else {
|
||||
lons[1] = mid
|
||||
}
|
||||
} else {
|
||||
mid := (lats[0] + lats[1]) / 2
|
||||
if lat > mid {
|
||||
ch |= masks[bit]
|
||||
lats[0] = mid
|
||||
} else {
|
||||
lats[1] = mid
|
||||
}
|
||||
}
|
||||
even = !even
|
||||
if bit < 4 {
|
||||
bit++
|
||||
} else {
|
||||
geoHash += string(base32encoding.enc[ch])
|
||||
ch = 0
|
||||
bit = 0
|
||||
}
|
||||
}
|
||||
|
||||
return geoHash
|
||||
}
|
||||
465
vendor/github.com/blevesearch/bleve/v2/geo/parse.go
generated
vendored
Normal file
465
vendor/github.com/blevesearch/bleve/v2/geo/parse.go
generated
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
// Copyright (c) 2017 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 (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/util"
|
||||
"github.com/blevesearch/geo/geojson"
|
||||
)
|
||||
|
||||
// ExtractGeoPoint takes an arbitrary interface{} and tries it's best to
|
||||
// interpret it is as geo point. Supported formats:
|
||||
// Container:
|
||||
// slice length 2 (GeoJSON)
|
||||
//
|
||||
// first element lon, second element lat
|
||||
//
|
||||
// string (coordinates separated by comma, or a geohash)
|
||||
//
|
||||
// first element lat, second element lon
|
||||
//
|
||||
// map[string]interface{}
|
||||
//
|
||||
// exact keys lat and lon or lng
|
||||
//
|
||||
// struct
|
||||
//
|
||||
// w/exported fields case-insensitive match on lat and lon or lng
|
||||
//
|
||||
// struct
|
||||
//
|
||||
// satisfying Later and Loner or Lnger interfaces
|
||||
//
|
||||
// in all cases values must be some sort of numeric-like thing: int/uint/float
|
||||
func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||
var foundLon, foundLat bool
|
||||
|
||||
thingVal := reflect.ValueOf(thing)
|
||||
if !thingVal.IsValid() {
|
||||
return lon, lat, false
|
||||
}
|
||||
|
||||
thingTyp := thingVal.Type()
|
||||
|
||||
// is it a slice
|
||||
if thingVal.Kind() == reflect.Slice {
|
||||
// must be length 2
|
||||
if thingVal.Len() == 2 {
|
||||
first := thingVal.Index(0)
|
||||
if first.CanInterface() {
|
||||
firstVal := first.Interface()
|
||||
lon, foundLon = util.ExtractNumericValFloat64(firstVal)
|
||||
}
|
||||
second := thingVal.Index(1)
|
||||
if second.CanInterface() {
|
||||
secondVal := second.Interface()
|
||||
lat, foundLat = util.ExtractNumericValFloat64(secondVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is it a string
|
||||
if thingVal.Kind() == reflect.String {
|
||||
geoStr := thingVal.Interface().(string)
|
||||
if strings.Contains(geoStr, ",") {
|
||||
// geo point with coordinates split by comma
|
||||
points := strings.Split(geoStr, ",")
|
||||
for i, point := range points {
|
||||
// trim any leading or trailing white spaces
|
||||
points[i] = strings.TrimSpace(point)
|
||||
}
|
||||
if len(points) == 2 {
|
||||
var err error
|
||||
lat, err = strconv.ParseFloat(points[0], 64)
|
||||
if err == nil {
|
||||
foundLat = true
|
||||
}
|
||||
lon, err = strconv.ParseFloat(points[1], 64)
|
||||
if err == nil {
|
||||
foundLon = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// geohash
|
||||
if len(geoStr) <= geoHashMaxLength {
|
||||
lat, lon = DecodeGeoHash(geoStr)
|
||||
foundLat = true
|
||||
foundLon = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is it a map
|
||||
if l, ok := thing.(map[string]interface{}); ok {
|
||||
if lval, ok := l["lon"]; ok {
|
||||
lon, foundLon = util.ExtractNumericValFloat64(lval)
|
||||
} else if lval, ok := l["lng"]; ok {
|
||||
lon, foundLon = util.ExtractNumericValFloat64(lval)
|
||||
}
|
||||
if lval, ok := l["lat"]; ok {
|
||||
lat, foundLat = util.ExtractNumericValFloat64(lval)
|
||||
}
|
||||
}
|
||||
|
||||
// now try reflection on struct fields
|
||||
if thingVal.Kind() == reflect.Struct {
|
||||
for i := 0; i < thingVal.NumField(); i++ {
|
||||
fieldName := thingTyp.Field(i).Name
|
||||
if strings.HasPrefix(strings.ToLower(fieldName), "lon") {
|
||||
if thingVal.Field(i).CanInterface() {
|
||||
fieldVal := thingVal.Field(i).Interface()
|
||||
lon, foundLon = util.ExtractNumericValFloat64(fieldVal)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(fieldName), "lng") {
|
||||
if thingVal.Field(i).CanInterface() {
|
||||
fieldVal := thingVal.Field(i).Interface()
|
||||
lon, foundLon = util.ExtractNumericValFloat64(fieldVal)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(fieldName), "lat") {
|
||||
if thingVal.Field(i).CanInterface() {
|
||||
fieldVal := thingVal.Field(i).Interface()
|
||||
lat, foundLat = util.ExtractNumericValFloat64(fieldVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last hope, some interfaces
|
||||
// lon
|
||||
if l, ok := thing.(loner); ok {
|
||||
lon = l.Lon()
|
||||
foundLon = true
|
||||
} else if l, ok := thing.(lnger); ok {
|
||||
lon = l.Lng()
|
||||
foundLon = true
|
||||
}
|
||||
// lat
|
||||
if l, ok := thing.(later); ok {
|
||||
lat = l.Lat()
|
||||
foundLat = true
|
||||
}
|
||||
|
||||
return lon, lat, foundLon && foundLat
|
||||
}
|
||||
|
||||
// various support interfaces which can be used to find lat/lon
|
||||
type loner interface {
|
||||
Lon() float64
|
||||
}
|
||||
|
||||
type later interface {
|
||||
Lat() float64
|
||||
}
|
||||
|
||||
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 = util.ExtractNumericValFloat64(firstVal)
|
||||
}
|
||||
second := thingVal.Index(1)
|
||||
if second.CanInterface() {
|
||||
secondVal := second.Interface()
|
||||
lat, foundLat = util.ExtractNumericValFloat64(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 {
|
||||
v := extractCoordinates(es)
|
||||
if len(v) == 2 {
|
||||
rv = append(rv, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extract3DCoordinates(thing interface{}) (c [][][]float64) {
|
||||
coords := reflect.ValueOf(thing)
|
||||
if !coords.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if coords.Kind() == reflect.Slice {
|
||||
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 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 extractGeoShape(thing interface{}) (*geojson.GeoShape, bool) {
|
||||
|
||||
coordValue, typ, err := ParseGeoShapeField(thing)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if typ == CircleType {
|
||||
return ExtractCircle(thing)
|
||||
}
|
||||
|
||||
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{}) ([]*geojson.GeoShape, bool) {
|
||||
thingVal := reflect.ValueOf(thing)
|
||||
if !thingVal.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
var rv []*geojson.GeoShape
|
||||
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++ {
|
||||
shape, found := extractGeoShape(items.Index(j).Interface())
|
||||
if found {
|
||||
f = found
|
||||
rv = append(rv, shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv, 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{}) (*geojson.GeoShape, bool) {
|
||||
thingVal := reflect.ValueOf(thing)
|
||||
if !thingVal.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rv := &geojson.GeoShape{
|
||||
Type: CircleType,
|
||||
Center: make([]float64, 0, 2),
|
||||
}
|
||||
|
||||
if thingVal.Kind() == reflect.Map {
|
||||
iter := thingVal.MapRange()
|
||||
for iter.Next() {
|
||||
|
||||
if iter.Key().String() == "radius" {
|
||||
rv.Radius = iter.Value().Interface().(string)
|
||||
continue
|
||||
}
|
||||
|
||||
if iter.Key().String() == "coordinates" {
|
||||
lng, lat, found := ExtractGeoPoint(iter.Value().Interface())
|
||||
if !found {
|
||||
return nil, false
|
||||
}
|
||||
rv.Center = append(rv.Center, lng, lat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rv, 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) (*geojson.GeoShape, bool) {
|
||||
rv := &geojson.GeoShape{
|
||||
Type: typ,
|
||||
}
|
||||
|
||||
if typ == PointType {
|
||||
point := extractCoordinates(coordValue)
|
||||
|
||||
// ignore the contents with invalid entry.
|
||||
if len(point) < 2 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rv.Coordinates = [][][][]float64{{{point}}}
|
||||
return rv, true
|
||||
}
|
||||
|
||||
if typ == MultiPointType || typ == LineStringType ||
|
||||
typ == EnvelopeType {
|
||||
coords := extract2DCoordinates(coordValue)
|
||||
|
||||
// ignore the contents with invalid entry.
|
||||
if len(coords) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if typ == EnvelopeType && len(coords) != 2 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if typ == LineStringType && len(coords) < 2 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rv.Coordinates = [][][][]float64{{coords}}
|
||||
return rv, true
|
||||
}
|
||||
|
||||
if typ == PolygonType || typ == MultiLineStringType {
|
||||
coords := extract3DCoordinates(coordValue)
|
||||
|
||||
// ignore the contents with invalid entry.
|
||||
if len(coords) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if typ == PolygonType && len(coords[0]) < 3 ||
|
||||
typ == MultiLineStringType && len(coords[0]) < 2 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rv.Coordinates = [][][][]float64{coords}
|
||||
return rv, true
|
||||
}
|
||||
|
||||
if typ == MultiPolygonType {
|
||||
coords := extract4DCoordinates(coordValue)
|
||||
|
||||
// ignore the contents with invalid entry.
|
||||
if len(coords) == 0 || len(coords[0]) == 0 {
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
if len(coords[0][0]) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
rv.Coordinates = coords
|
||||
return rv, true
|
||||
}
|
||||
|
||||
return rv, false
|
||||
}
|
||||
59
vendor/github.com/blevesearch/bleve/v2/geo/sloppy.go
generated
vendored
Normal file
59
vendor/github.com/blevesearch/bleve/v2/geo/sloppy.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2017 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 (
|
||||
"math"
|
||||
)
|
||||
|
||||
var earthDiameterPerLatitude []float64
|
||||
|
||||
const (
|
||||
radiusTabsSize = (1 << 10) + 1
|
||||
radiusDelta = (math.Pi / 2) / (radiusTabsSize - 1)
|
||||
radiusIndexer = 1 / radiusDelta
|
||||
)
|
||||
|
||||
func init() {
|
||||
// initializes the tables used for the sloppy math functions
|
||||
|
||||
// earth radius
|
||||
a := 6378137.0
|
||||
b := 6356752.31420
|
||||
a2 := a * a
|
||||
b2 := b * b
|
||||
earthDiameterPerLatitude = make([]float64, radiusTabsSize)
|
||||
earthDiameterPerLatitude[0] = 2.0 * a / 1000
|
||||
earthDiameterPerLatitude[radiusTabsSize-1] = 2.0 * b / 1000
|
||||
for i := 1; i < radiusTabsSize-1; i++ {
|
||||
lat := math.Pi * float64(i) / (2*radiusTabsSize - 1)
|
||||
one := math.Pow(a2*math.Cos(lat), 2)
|
||||
two := math.Pow(b2*math.Sin(lat), 2)
|
||||
three := math.Pow(float64(a)*math.Cos(lat), 2)
|
||||
four := math.Pow(b*math.Sin(lat), 2)
|
||||
radius := math.Sqrt((one + two) / (three + four))
|
||||
earthDiameterPerLatitude[i] = 2 * radius / 1000
|
||||
}
|
||||
}
|
||||
|
||||
// earthDiameter returns an estimation of the earth's diameter at the specified
|
||||
// latitude in kilometers
|
||||
func earthDiameter(lat float64) float64 {
|
||||
index := math.Mod(math.Abs(lat)*radiusIndexer+0.5, float64(len(earthDiameterPerLatitude)))
|
||||
if math.IsNaN(index) {
|
||||
return 0
|
||||
}
|
||||
return earthDiameterPerLatitude[int(index)]
|
||||
}
|
||||
Reference in New Issue
Block a user