several more items on the geo checklist
- added readme pointing back to lucene origins - improved documentation of exported methods in geo package - improved test coverage to 100% on geo package - added support for parsing geojson style points - removed some duplicated code in the geo bounding box searcher
This commit is contained in:
parent
a16efa5e78
commit
fdbe669fd5
|
@ -0,0 +1,9 @@
|
||||||
|
# geo support in bleve
|
||||||
|
|
||||||
|
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.
|
65
geo/geo.go
65
geo/geo.go
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -6,13 +20,18 @@ import (
|
||||||
"github.com/blevesearch/bleve/numeric"
|
"github.com/blevesearch/bleve/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 minLon = -180.0
|
||||||
var minLat = -90.0
|
var minLat = -90.0
|
||||||
var GeoBits uint = 32
|
|
||||||
var geoTolerance = 1E-6
|
var geoTolerance = 1E-6
|
||||||
var lonScale = float64((uint64(0x1)<<GeoBits)-1) / 360.0
|
var lonScale = float64((uint64(0x1)<<GeoBits)-1) / 360.0
|
||||||
var latScale = float64((uint64(0x1)<<GeoBits)-1) / 180.0
|
var latScale = float64((uint64(0x1)<<GeoBits)-1) / 180.0
|
||||||
|
|
||||||
|
// MortonHash computes the morton hash value for the provided geo point
|
||||||
|
// This point is ordered as lon, lat.
|
||||||
func MortonHash(lon, lat float64) uint64 {
|
func MortonHash(lon, lat float64) uint64 {
|
||||||
return numeric.Interleave(scaleLon(lon), scaleLat(lat))
|
return numeric.Interleave(scaleLon(lon), scaleLat(lat))
|
||||||
}
|
}
|
||||||
|
@ -27,10 +46,12 @@ func scaleLat(lat float64) uint64 {
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MortonUnhashLon extracts the longitude value from the provided morton hash.
|
||||||
func MortonUnhashLon(hash uint64) float64 {
|
func MortonUnhashLon(hash uint64) float64 {
|
||||||
return unscaleLon(numeric.Deinterleave(hash))
|
return unscaleLon(numeric.Deinterleave(hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MortonUnhashLat extracts the latitude value from the provided morton hash.
|
||||||
func MortonUnhashLat(hash uint64) float64 {
|
func MortonUnhashLat(hash uint64) float64 {
|
||||||
return unscaleLat(numeric.Deinterleave(hash >> 1))
|
return unscaleLat(numeric.Deinterleave(hash >> 1))
|
||||||
}
|
}
|
||||||
|
@ -43,6 +64,8 @@ func unscaleLat(lat uint64) float64 {
|
||||||
return (float64(lat) / latScale) + minLat
|
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 {
|
func compareGeo(a, b float64) float64 {
|
||||||
compare := a - b
|
compare := a - b
|
||||||
if math.Abs(compare) <= geoTolerance {
|
if math.Abs(compare) <= geoTolerance {
|
||||||
|
@ -51,25 +74,34 @@ func compareGeo(a, b float64) float64 {
|
||||||
return compare
|
return compare
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RectIntersects checks whether rectangles a and b intersect
|
||||||
func RectIntersects(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY float64) bool {
|
func RectIntersects(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY float64) bool {
|
||||||
return !(aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY)
|
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 {
|
func RectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY float64) bool {
|
||||||
rv := !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY)
|
rv := !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY)
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BoundingBoxContains checks whether the lon/lat point is within the box
|
||||||
func BoundingBoxContains(lon, lat, minLon, minLat, maxLon, maxLat float64) bool {
|
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
|
return compareGeo(lon, minLon) >= 0 && compareGeo(lon, maxLon) <= 0 &&
|
||||||
|
compareGeo(lat, minLat) >= 0 && compareGeo(lat, maxLat) <= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComputeBoundingBox(centerLon, centerLat, radius float64) (upperLeftLon float64, upperLeftLat float64, lowerRightLon float64, lowerRightLat float64) {
|
// ComputeBoundingBox will compute a bounding box around the provided point
|
||||||
|
// which surrounds a circle of the provided radius (in meters).
|
||||||
|
func ComputeBoundingBox(centerLon, centerLat,
|
||||||
|
radius float64) (upperLeftLon float64, upperLeftLat float64,
|
||||||
|
lowerRightLon float64, lowerRightLat float64) {
|
||||||
_, tlat := pointFromLonLatBearing(centerLon, centerLat, 0, radius)
|
_, tlat := pointFromLonLatBearing(centerLon, centerLat, 0, radius)
|
||||||
rlon, _ := pointFromLonLatBearing(centerLon, centerLat, 90, radius)
|
rlon, _ := pointFromLonLatBearing(centerLon, centerLat, 90, radius)
|
||||||
_, blat := pointFromLonLatBearing(centerLon, centerLat, 180, radius)
|
_, blat := pointFromLonLatBearing(centerLon, centerLat, 180, radius)
|
||||||
llon, _ := pointFromLonLatBearing(centerLon, centerLat, 270, radius)
|
llon, _ := pointFromLonLatBearing(centerLon, centerLat, 270, radius)
|
||||||
return normalizeLon(llon), normalizeLat(tlat), normalizeLon(rlon), normalizeLat(blat)
|
return normalizeLon(llon), normalizeLat(tlat),
|
||||||
|
normalizeLon(rlon), normalizeLat(blat)
|
||||||
}
|
}
|
||||||
|
|
||||||
const degreesToRadian = math.Pi / 180
|
const degreesToRadian = math.Pi / 180
|
||||||
|
@ -80,15 +112,22 @@ const semiMinorAxis = semiMajorAxis * (1.0 - flattening)
|
||||||
const semiMajorAxis2 = semiMajorAxis * semiMajorAxis
|
const semiMajorAxis2 = semiMajorAxis * semiMajorAxis
|
||||||
const semiMinorAxis2 = semiMinorAxis * semiMinorAxis
|
const semiMinorAxis2 = semiMinorAxis * semiMinorAxis
|
||||||
|
|
||||||
|
// DegreesToRadians converts an angle in degrees to radians
|
||||||
func DegreesToRadians(d float64) float64 {
|
func DegreesToRadians(d float64) float64 {
|
||||||
return d * degreesToRadian
|
return d * degreesToRadian
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RadiansToDegrees converts an angle in radians to degress
|
||||||
func RadiansToDegrees(r float64) float64 {
|
func RadiansToDegrees(r float64) float64 {
|
||||||
return r * radiansToDegrees
|
return r * radiansToDegrees
|
||||||
}
|
}
|
||||||
|
|
||||||
func pointFromLonLatBearing(lon, lat, bearing, dist float64) (float64, float64) {
|
// pointFromLonLatBearing starts that the provide lon,lat
|
||||||
|
// then moves in the bearing direction (in degrees)
|
||||||
|
// this move continues for the provided distance (in meters)
|
||||||
|
// The lon, lat of this destination location is returned.
|
||||||
|
func pointFromLonLatBearing(lon, lat, bearing,
|
||||||
|
dist float64) (float64, float64) {
|
||||||
|
|
||||||
alpha1 := DegreesToRadians(bearing)
|
alpha1 := DegreesToRadians(bearing)
|
||||||
cosA1 := math.Cos(alpha1)
|
cosA1 := math.Cos(alpha1)
|
||||||
|
@ -108,23 +147,29 @@ func pointFromLonLatBearing(lon, lat, bearing, dist float64) (float64, float64)
|
||||||
cos25SigmaM := math.Cos(2*sig1 + sigma)
|
cos25SigmaM := math.Cos(2*sig1 + sigma)
|
||||||
sinSigma := math.Sin(sigma)
|
sinSigma := math.Sin(sigma)
|
||||||
cosSigma := math.Cos(sigma)
|
cosSigma := math.Cos(sigma)
|
||||||
deltaSigma := B * sinSigma * (cos25SigmaM + (B/4)*(cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)-(B/6)*cos25SigmaM*(-1+4*sinSigma*sinSigma)*(-3+4*cos25SigmaM*cos25SigmaM)))
|
deltaSigma := B * sinSigma * (cos25SigmaM + (B/4)*
|
||||||
|
(cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)-(B/6)*cos25SigmaM*
|
||||||
|
(-1+4*sinSigma*sinSigma)*(-3+4*cos25SigmaM*cos25SigmaM)))
|
||||||
sigmaP := sigma
|
sigmaP := sigma
|
||||||
sigma = dist/(semiMinorAxis*A) + deltaSigma
|
sigma = dist/(semiMinorAxis*A) + deltaSigma
|
||||||
for math.Abs(sigma-sigmaP) > 1E-12 {
|
for math.Abs(sigma-sigmaP) > 1E-12 {
|
||||||
cos25SigmaM = math.Cos(2*sig1 + sigma)
|
cos25SigmaM = math.Cos(2*sig1 + sigma)
|
||||||
sinSigma = math.Sin(sigma)
|
sinSigma = math.Sin(sigma)
|
||||||
cosSigma = math.Cos(sigma)
|
cosSigma = math.Cos(sigma)
|
||||||
deltaSigma = B * sinSigma * (cos25SigmaM + (B/4)*(cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)-(B/6)*cos25SigmaM*(-1+4*sinSigma*sinSigma)*(-3+4*cos25SigmaM*cos25SigmaM)))
|
deltaSigma = B * sinSigma * (cos25SigmaM + (B/4)*
|
||||||
|
(cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)-(B/6)*cos25SigmaM*
|
||||||
|
(-1+4*sinSigma*sinSigma)*(-3+4*cos25SigmaM*cos25SigmaM)))
|
||||||
sigmaP = sigma
|
sigmaP = sigma
|
||||||
sigma = dist/(semiMinorAxis*A) + deltaSigma
|
sigma = dist/(semiMinorAxis*A) + deltaSigma
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := sinU1*sinSigma - cosU1*cosSigma*cosA1
|
tmp := sinU1*sinSigma - cosU1*cosSigma*cosA1
|
||||||
lat2 := math.Atan2(sinU1*cosSigma+cosU1*sinSigma*cosA1, (1-flattening)*math.Sqrt(sinAlpha*sinAlpha+tmp*tmp))
|
lat2 := math.Atan2(sinU1*cosSigma+cosU1*sinSigma*cosA1,
|
||||||
|
(1-flattening)*math.Sqrt(sinAlpha*sinAlpha+tmp*tmp))
|
||||||
lamda := math.Atan2(sinSigma*sinA1, cosU1*cosSigma-sinU1*sinSigma*cosA1)
|
lamda := math.Atan2(sinSigma*sinA1, cosU1*cosSigma-sinU1*sinSigma*cosA1)
|
||||||
c := flattening / 16 * cosSqAlpha * (4 + flattening*(4-3*cosSqAlpha))
|
c := flattening / 16 * cosSqAlpha * (4 + flattening*(4-3*cosSqAlpha))
|
||||||
lam := lamda - (1-c)*flattening*sinAlpha*(sigma+c*sinSigma*(cos25SigmaM+c*cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)))
|
lam := lamda - (1-c)*flattening*sinAlpha*
|
||||||
|
(sigma+c*sinSigma*(cos25SigmaM+c*cosSigma*(-1+2*cos25SigmaM*cos25SigmaM)))
|
||||||
|
|
||||||
rvlon := lon + RadiansToDegrees(lam)
|
rvlon := lon + RadiansToDegrees(lam)
|
||||||
rvlat := RadiansToDegrees(lat2)
|
rvlat := RadiansToDegrees(lat2)
|
||||||
|
@ -132,6 +177,7 @@ func pointFromLonLatBearing(lon, lat, bearing, dist float64) (float64, float64)
|
||||||
return rvlon, rvlat
|
return rvlon, rvlat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeLon normalizes a longitude value within the -180 to 180 range
|
||||||
func normalizeLon(lonDeg float64) float64 {
|
func normalizeLon(lonDeg float64) float64 {
|
||||||
if lonDeg >= -180 && lonDeg <= 180 {
|
if lonDeg >= -180 && lonDeg <= 180 {
|
||||||
return lonDeg
|
return lonDeg
|
||||||
|
@ -146,6 +192,7 @@ func normalizeLon(lonDeg float64) float64 {
|
||||||
return -180 + off
|
return -180 + off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalizeLat normalizes a latitude value within the -90 to 90 range
|
||||||
func normalizeLat(latDeg float64) float64 {
|
func normalizeLat(latDeg float64) float64 {
|
||||||
if latDeg >= -90 && latDeg <= 90 {
|
if latDeg >= -90 && latDeg <= 90 {
|
||||||
return latDeg
|
return latDeg
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -25,7 +39,14 @@ var distanceUnits = []*distanceUnit{
|
||||||
&inch, &yard, &feet, &kilom, &nauticalm, &millim, ¢im, &miles, &meters,
|
&inch, &yard, &feet, &kilom, &nauticalm, &millim, ¢im, &miles, &meters,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseDistance attempts to parse a distance, return distance in 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) {
|
func ParseDistance(d string) (float64, error) {
|
||||||
for _, unit := range distanceUnits {
|
for _, unit := range distanceUnits {
|
||||||
for _, unitSuffix := range unit.suffixes {
|
for _, unitSuffix := range unit.suffixes {
|
||||||
|
@ -46,6 +67,9 @@ func ParseDistance(d string) (float64, error) {
|
||||||
return parsedNum, nil
|
return parsedNum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Haversin computes the distance between two points.
|
||||||
|
// This implemenation uses the sloppy math implemenations which trade off
|
||||||
|
// accuracy for performance.
|
||||||
func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
|
func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
|
||||||
x1 := lat1 * degreesToRadian
|
x1 := lat1 * degreesToRadian
|
||||||
x2 := lat2 * degreesToRadian
|
x2 := lat2 * degreesToRadian
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
141
geo/geo_test.go
141
geo/geo_test.go
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -79,3 +93,130 @@ func TestComputeBoundingBoxCheckLatitudeAtEquator(t *testing.T) {
|
||||||
t.Errorf("expected bounding box lower right lat to be almost -1, got %f", lowerRightLat)
|
t.Errorf("expected bounding box lower right lat to be almost -1, got %f", lowerRightLat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRectIntersects(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
aMinX float64
|
||||||
|
aMinY float64
|
||||||
|
aMaxX float64
|
||||||
|
aMaxY float64
|
||||||
|
bMinX float64
|
||||||
|
bMinY float64
|
||||||
|
bMaxX float64
|
||||||
|
bMaxY float64
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// clearly overlap
|
||||||
|
{0, 0, 2, 2, 1, 1, 3, 3, true},
|
||||||
|
// clearly do not overalp
|
||||||
|
{0, 0, 1, 1, 2, 2, 3, 3, false},
|
||||||
|
// share common point
|
||||||
|
{0, 0, 1, 1, 1, 1, 2, 2, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := RectIntersects(test.aMinX, test.aMinY, test.aMaxX, test.aMaxY, test.bMinX, test.bMinY, test.bMaxX, test.bMaxY)
|
||||||
|
if test.want != got {
|
||||||
|
t.Errorf("expected intersects %t, got %t for %f %f %f %f %f %f %f %f", test.want, got, test.aMinX, test.aMinY, test.aMaxX, test.aMaxY, test.bMinX, test.bMinY, test.bMaxX, test.bMaxY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRectWithin(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
aMinX float64
|
||||||
|
aMinY float64
|
||||||
|
aMaxX float64
|
||||||
|
aMaxY float64
|
||||||
|
bMinX float64
|
||||||
|
bMinY float64
|
||||||
|
bMaxX float64
|
||||||
|
bMaxY float64
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// clearly within
|
||||||
|
{1, 1, 2, 2, 0, 0, 3, 3, true},
|
||||||
|
// clearly not within
|
||||||
|
{0, 0, 1, 1, 2, 2, 3, 3, false},
|
||||||
|
// overlapping
|
||||||
|
{0, 0, 2, 2, 1, 1, 3, 3, false},
|
||||||
|
// share common point
|
||||||
|
{0, 0, 1, 1, 1, 1, 2, 2, false},
|
||||||
|
// within, but boxes reversed (b is within a, but not a within b)
|
||||||
|
{0, 0, 3, 3, 1, 1, 2, 2, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := RectWithin(test.aMinX, test.aMinY, test.aMaxX, test.aMaxY, test.bMinX, test.bMinY, test.bMaxX, test.bMaxY)
|
||||||
|
if test.want != got {
|
||||||
|
t.Errorf("expected within %t, got %t for %f %f %f %f %f %f %f %f", test.want, got, test.aMinX, test.aMinY, test.aMaxX, test.aMaxY, test.bMinX, test.bMinY, test.bMaxX, test.bMaxY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoundingBoxContains(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
lon float64
|
||||||
|
lat float64
|
||||||
|
minX float64
|
||||||
|
minY float64
|
||||||
|
maxX float64
|
||||||
|
maxY float64
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// clearly contains
|
||||||
|
{1, 1, 0, 0, 2, 2, true},
|
||||||
|
// clearly does not contain
|
||||||
|
{0, 0, 1, 1, 2, 2, false},
|
||||||
|
// on corner
|
||||||
|
{0, 0, 0, 0, 2, 2, true},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
got := BoundingBoxContains(test.lon, test.lat, test.minX, test.minY, test.maxX, test.maxY)
|
||||||
|
if test.want != got {
|
||||||
|
t.Errorf("expected box contains %t, got %t for %f,%f in %f %f %f %f ", test.want, got, test.lon, test.lat, test.minX, test.minY, test.maxX, test.maxY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeLon(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
lon float64
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{-180, -180},
|
||||||
|
{0, 0},
|
||||||
|
{180, 180},
|
||||||
|
{181, -179},
|
||||||
|
{-181, 179},
|
||||||
|
{540, 180},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := normalizeLon(test.lon)
|
||||||
|
if test.want != got {
|
||||||
|
t.Errorf("expected normalizedLon %f, got %f for %f", test.want, got, test.lon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizeLat(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
lat float64
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{-90, -90},
|
||||||
|
{0, 0},
|
||||||
|
{90, 90},
|
||||||
|
// somewhat unexpected, but double-checked against lucene
|
||||||
|
{91, 89},
|
||||||
|
{-91, -89},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := normalizeLat(test.lat)
|
||||||
|
if test.want != got {
|
||||||
|
t.Errorf("expected normalizedLat %f, got %f for %f", test.want, got, test.lat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
65
geo/parse.go
65
geo/parse.go
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -6,9 +20,41 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtractGeoPoint takes an arbitrary interface{} and tries it's best to
|
// ExtractGeoPoint takes an arbitrary interface{} and tries it's best to
|
||||||
// interpret it is as geo point
|
// interpret it is as geo point. Supportd formats:
|
||||||
|
// Container:
|
||||||
|
// slice length 2 (GeoJSON)
|
||||||
|
// first element lon, second element lat
|
||||||
|
// 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) {
|
func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||||
var foundLon, foundLat bool
|
var foundLon, foundLat bool
|
||||||
|
|
||||||
|
thingVal := reflect.ValueOf(thing)
|
||||||
|
thingTyp := thingVal.Type()
|
||||||
|
|
||||||
|
// is it a slice
|
||||||
|
if thingVal.IsValid() && thingVal.Kind() == reflect.Slice {
|
||||||
|
// must be length 2
|
||||||
|
if thingVal.Len() == 2 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// is it a map
|
// is it a map
|
||||||
if l, ok := thing.(map[string]interface{}); ok {
|
if l, ok := thing.(map[string]interface{}); ok {
|
||||||
if lval, ok := l["lon"]; ok {
|
if lval, ok := l["lon"]; ok {
|
||||||
|
@ -23,8 +69,6 @@ func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now try reflection on struct fields
|
// now try reflection on struct fields
|
||||||
thingVal := reflect.ValueOf(thing)
|
|
||||||
thingTyp := thingVal.Type()
|
|
||||||
if thingVal.IsValid() && thingVal.Kind() == reflect.Struct {
|
if thingVal.IsValid() && thingVal.Kind() == reflect.Struct {
|
||||||
for i := 0; i < thingVal.NumField(); i++ {
|
for i := 0; i < thingVal.NumField(); i++ {
|
||||||
field := thingTyp.Field(i)
|
field := thingTyp.Field(i)
|
||||||
|
@ -70,12 +114,17 @@ func ExtractGeoPoint(thing interface{}) (lon, lat float64, success bool) {
|
||||||
|
|
||||||
// extract numeric value (if possible) and returna s float64
|
// extract numeric value (if possible) and returna s float64
|
||||||
func extractNumericVal(v interface{}) (float64, bool) {
|
func extractNumericVal(v interface{}) (float64, bool) {
|
||||||
switch v := v.(type) {
|
val := reflect.ValueOf(v)
|
||||||
case float64:
|
typ := val.Type()
|
||||||
return v, true
|
switch typ.Kind() {
|
||||||
case float32:
|
case reflect.Float32, reflect.Float64:
|
||||||
return float64(v), true
|
return val.Float(), true
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return float64(val.Int()), true
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return float64(val.Uint()), true
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
// 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 "testing"
|
||||||
|
|
||||||
|
func TestExtractGeoPoint(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in interface{}
|
||||||
|
lon float64
|
||||||
|
lat float64
|
||||||
|
success bool
|
||||||
|
}{
|
||||||
|
// values are ints
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"lat": 5,
|
||||||
|
"lon": 5,
|
||||||
|
},
|
||||||
|
lon: 5,
|
||||||
|
lat: 5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// values are uints
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"lat": uint(5),
|
||||||
|
"lon": uint(5),
|
||||||
|
},
|
||||||
|
lon: 5,
|
||||||
|
lat: 5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// values float64 as with parsed JSON
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"lat": 5.0,
|
||||||
|
"lon": 5.0,
|
||||||
|
},
|
||||||
|
lon: 5,
|
||||||
|
lat: 5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// values are bool (not supported)
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"lat": true,
|
||||||
|
"lon": false,
|
||||||
|
},
|
||||||
|
lon: 0,
|
||||||
|
lat: 0,
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
// using lng variant of lon
|
||||||
|
{
|
||||||
|
in: map[string]interface{}{
|
||||||
|
"lat": 5.0,
|
||||||
|
"lng": 5.0,
|
||||||
|
},
|
||||||
|
lon: 5,
|
||||||
|
lat: 5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// using struct
|
||||||
|
{
|
||||||
|
in: struct {
|
||||||
|
Lon float64
|
||||||
|
Lat float64
|
||||||
|
}{
|
||||||
|
Lon: 3.0,
|
||||||
|
Lat: 7.5,
|
||||||
|
},
|
||||||
|
lon: 3.0,
|
||||||
|
lat: 7.5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// struct with lng alterante
|
||||||
|
{
|
||||||
|
in: struct {
|
||||||
|
Lng float64
|
||||||
|
Lat float64
|
||||||
|
}{
|
||||||
|
Lng: 3.0,
|
||||||
|
Lat: 7.5,
|
||||||
|
},
|
||||||
|
lon: 3.0,
|
||||||
|
lat: 7.5,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// test going throug interface
|
||||||
|
{
|
||||||
|
in: &s1{
|
||||||
|
lon: 4.0,
|
||||||
|
lat: 6.9,
|
||||||
|
},
|
||||||
|
lon: 4.0,
|
||||||
|
lat: 6.9,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// test going throug interface with lng variant
|
||||||
|
{
|
||||||
|
in: &s2{
|
||||||
|
lng: 4.0,
|
||||||
|
lat: 6.9,
|
||||||
|
},
|
||||||
|
lon: 4.0,
|
||||||
|
lat: 6.9,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// try GeoJSON slice
|
||||||
|
{
|
||||||
|
in: []interface{}{3.4, 5.9},
|
||||||
|
lon: 3.4,
|
||||||
|
lat: 5.9,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
// try GeoJSON slice too long
|
||||||
|
{
|
||||||
|
in: []interface{}{3.4, 5.9, 9.4},
|
||||||
|
lon: 0,
|
||||||
|
lat: 0,
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
// slice of floats
|
||||||
|
{
|
||||||
|
in: []float64{3.4, 5.9},
|
||||||
|
lon: 3.4,
|
||||||
|
lat: 5.9,
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
lon, lat, success := ExtractGeoPoint(test.in)
|
||||||
|
if success != test.success {
|
||||||
|
t.Errorf("expected extract geo point %t, got %t for %v", test.success, success, test.in)
|
||||||
|
}
|
||||||
|
if lon != test.lon {
|
||||||
|
t.Errorf("expected lon %f, got %f for %v", test.lon, lon, test.in)
|
||||||
|
}
|
||||||
|
if lat != test.lat {
|
||||||
|
t.Errorf("expected lat %f, got %f for %v", test.lat, lat, test.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type s1 struct {
|
||||||
|
lon float64
|
||||||
|
lat float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s1) Lon() float64 {
|
||||||
|
return s.lon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s1) Lat() float64 {
|
||||||
|
return s.lat
|
||||||
|
}
|
||||||
|
|
||||||
|
type s2 struct {
|
||||||
|
lng float64
|
||||||
|
lat float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s2) Lng() float64 {
|
||||||
|
return s.lng
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s2) Lat() float64 {
|
||||||
|
return s.lat
|
||||||
|
}
|
|
@ -1,6 +1,22 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import "math"
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
var earthDiameterPerLatitude []float64
|
var earthDiameterPerLatitude []float64
|
||||||
var sinTab []float64
|
var sinTab []float64
|
||||||
|
@ -51,6 +67,7 @@ var asinDelta = asinMaxValueForTabs / (asinTabsSize - 1)
|
||||||
var asinIndexer = 1 / asinDelta
|
var asinIndexer = 1 / asinDelta
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// initializes the tables used for the sloppy math functions
|
||||||
|
|
||||||
// sin and cos
|
// sin and cos
|
||||||
sinTab = make([]float64, sinCosTabsSize)
|
sinTab = make([]float64, sinCosTabsSize)
|
||||||
|
@ -119,6 +136,8 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// earthDiameter returns an estimation of the earth's diameter at the specified
|
||||||
|
// latitude
|
||||||
func earthDiameter(lat float64) float64 {
|
func earthDiameter(lat float64) float64 {
|
||||||
index := math.Mod(math.Abs(lat)*radiusIndexer+0.5, float64(len(earthDiameterPerLatitude)))
|
index := math.Mod(math.Abs(lat)*radiusIndexer+0.5, float64(len(earthDiameterPerLatitude)))
|
||||||
if math.IsNaN(index) {
|
if math.IsNaN(index) {
|
||||||
|
@ -127,6 +146,7 @@ func earthDiameter(lat float64) float64 {
|
||||||
return earthDiameterPerLatitude[int(index)]
|
return earthDiameterPerLatitude[int(index)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cos is a sloppy math (faster) implementation of math.Cos
|
||||||
func cos(a float64) float64 {
|
func cos(a float64) float64 {
|
||||||
if a < 0.0 {
|
if a < 0.0 {
|
||||||
a = -a
|
a = -a
|
||||||
|
@ -145,6 +165,7 @@ func cos(a float64) float64 {
|
||||||
return indexCos + delta*(-indexSin+delta*(-indexCos*oneDivF2+delta*(indexSin*oneDivF3+delta*indexCos*oneDivF4)))
|
return indexCos + delta*(-indexSin+delta*(-indexCos*oneDivF2+delta*(indexSin*oneDivF3+delta*indexCos*oneDivF4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// asin is a sloppy math (faster) implementation of math.Asin
|
||||||
func asin(a float64) float64 {
|
func asin(a float64) float64 {
|
||||||
var negateResult bool
|
var negateResult bool
|
||||||
if a < 0 {
|
if a < 0 {
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// 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
|
package geo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -59,6 +73,9 @@ func TestAsin(t *testing.T) {
|
||||||
{0.7071068, math.Pi / 4},
|
{0.7071068, math.Pi / 4},
|
||||||
{0.8660254, math.Pi / 3},
|
{0.8660254, math.Pi / 3},
|
||||||
{1, math.Pi / 2},
|
{1, math.Pi / 2},
|
||||||
|
// these last two cases test the code outside tabular range
|
||||||
|
{0.999999999999999, math.Pi / 2},
|
||||||
|
{-0.999999999999999, -math.Pi / 2},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -67,7 +84,7 @@ func TestAsin(t *testing.T) {
|
||||||
t.Errorf("wanted NaN, got %f for asin(%f)", got, test.in)
|
t.Errorf("wanted NaN, got %f for asin(%f)", got, test.in)
|
||||||
}
|
}
|
||||||
if !math.IsNaN(test.want) && math.Abs(got-test.want) > asinDelta {
|
if !math.IsNaN(test.want) && math.Abs(got-test.want) > asinDelta {
|
||||||
t.Errorf("wanted: %f, got %f for asin(%f) diff %f", test.want, got, test.in, math.Abs(got-test.want))
|
t.Errorf("wanted: %f, got %f for asin(%f) diff %.16f", test.want, got, test.in, math.Abs(got-test.want))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ type GeoBoundingBoxSearcher struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, maxLon, maxLat float64, field string, boost float64, options search.SearcherOptions) (*GeoBoundingBoxSearcher, error) {
|
func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, maxLon, maxLat float64, field string, boost float64, options search.SearcherOptions) (*GeoBoundingBoxSearcher, error) {
|
||||||
|
var openedSearchers []search.Searcher
|
||||||
|
cleanupOpenedSearchers := func() {
|
||||||
|
for _, s := range openedSearchers {
|
||||||
|
_ = s.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
rv := &GeoBoundingBoxSearcher{
|
rv := &GeoBoundingBoxSearcher{
|
||||||
indexReader: indexReader,
|
indexReader: indexReader,
|
||||||
minLon: minLon,
|
minLon: minLon,
|
||||||
|
@ -55,12 +61,7 @@ func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, ma
|
||||||
for _, r := range rv.rangeBounds {
|
for _, r := range rv.rangeBounds {
|
||||||
ts, err := NewTermSearcher(indexReader, string(r.cell), field, 1.0, options)
|
ts, err := NewTermSearcher(indexReader, string(r.cell), field, 1.0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, s := range termsOnBoundary {
|
cleanupOpenedSearchers()
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
for _, s := range termsNotOnBoundary {
|
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if r.boundary {
|
if r.boundary {
|
||||||
|
@ -68,15 +69,11 @@ func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, ma
|
||||||
} else {
|
} else {
|
||||||
termsNotOnBoundary = append(termsNotOnBoundary, ts)
|
termsNotOnBoundary = append(termsNotOnBoundary, ts)
|
||||||
}
|
}
|
||||||
|
openedSearchers = append(openedSearchers)
|
||||||
}
|
}
|
||||||
onBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsOnBoundary, 0, options)
|
onBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsOnBoundary, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, s := range termsOnBoundary {
|
cleanupOpenedSearchers()
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
for _, s := range termsNotOnBoundary {
|
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
filterOnBoundarySearcher := NewFilteringSearcher(onBoundarySearcher, func(d *search.DocumentMatch) bool {
|
filterOnBoundarySearcher := NewFilteringSearcher(onBoundarySearcher, func(d *search.DocumentMatch) bool {
|
||||||
|
@ -102,28 +99,17 @@ func NewGeoBoundingBoxSearcher(indexReader index.IndexReader, minLon, minLat, ma
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
openedSearchers = append(openedSearchers, filterOnBoundarySearcher)
|
||||||
notOnBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsNotOnBoundary, 0, options)
|
notOnBoundarySearcher, err := NewDisjunctionSearcher(indexReader, termsNotOnBoundary, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, s := range termsOnBoundary {
|
cleanupOpenedSearchers()
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
for _, s := range termsNotOnBoundary {
|
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
_ = filterOnBoundarySearcher.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
openedSearchers = append(openedSearchers, notOnBoundarySearcher)
|
||||||
|
|
||||||
rv.searcher, err = NewDisjunctionSearcher(indexReader, []search.Searcher{filterOnBoundarySearcher, notOnBoundarySearcher}, 0, options)
|
rv.searcher, err = NewDisjunctionSearcher(indexReader, []search.Searcher{filterOnBoundarySearcher, notOnBoundarySearcher}, 0, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, s := range termsOnBoundary {
|
cleanupOpenedSearchers()
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
for _, s := range termsNotOnBoundary {
|
|
||||||
_ = s.Close()
|
|
||||||
}
|
|
||||||
_ = filterOnBoundarySearcher.Close()
|
|
||||||
_ = notOnBoundarySearcher.Close()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rv, nil
|
return rv, nil
|
||||||
|
@ -185,26 +171,14 @@ func (s *GeoBoundingBoxSearcher) relateAndRecurse(start, end uint64, res uint) {
|
||||||
|
|
||||||
level := ((geo.GeoBits << 1) - res) >> 1
|
level := ((geo.GeoBits << 1) - res) >> 1
|
||||||
|
|
||||||
within := res%document.GeoPrecisionStep == 0 && s.cellWithin(minLon, minLat, maxLon, maxLat)
|
within := res%document.GeoPrecisionStep == 0 && geo.RectWithin(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)
|
||||||
if within || (level == geoDetailLevel && s.cellIntersectShape(minLon, minLat, maxLon, maxLat)) {
|
if within || (level == geoDetailLevel && geo.RectIntersects(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)) {
|
||||||
s.rangeBounds = append(s.rangeBounds, newGeoRange(start, res, level, !within))
|
s.rangeBounds = append(s.rangeBounds, newGeoRange(start, res, level, !within))
|
||||||
} else if level < geoDetailLevel && s.cellIntersectsMBR(minLon, minLat, maxLon, maxLat) {
|
} else if level < geoDetailLevel && geo.RectIntersects(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat) {
|
||||||
s.computeRange(start, res-1)
|
s.computeRange(start, res-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GeoBoundingBoxSearcher) cellWithin(minLon, minLat, maxLon, maxLat float64) bool {
|
|
||||||
return geo.RectWithin(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GeoBoundingBoxSearcher) cellIntersectShape(minLon, minLat, maxLon, maxLat float64) bool {
|
|
||||||
return s.cellIntersectsMBR(minLon, minLat, maxLon, maxLat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GeoBoundingBoxSearcher) cellIntersectsMBR(minLon, minLat, maxLon, maxLat float64) bool {
|
|
||||||
return geo.RectIntersects(minLon, minLat, maxLon, maxLat, s.minLon, s.minLat, s.maxLon, s.maxLat)
|
|
||||||
}
|
|
||||||
|
|
||||||
type geoRange struct {
|
type geoRange struct {
|
||||||
cell []byte
|
cell []byte
|
||||||
level uint
|
level uint
|
||||||
|
|
Loading…
Reference in New Issue