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>
1828 lines
42 KiB
Go
1828 lines
42 KiB
Go
// Copyright (c) 2022 Couchbase, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package geojson
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
index "github.com/blevesearch/bleve_index_api"
|
|
|
|
"github.com/blevesearch/geo/s2"
|
|
)
|
|
|
|
// s2Serializable is an optional interface for implementations
|
|
// supporting custom serialisation of data based out of s2's
|
|
// encode method.
|
|
type s2Serializable interface {
|
|
// Marshal implementation should encode the shape using the
|
|
// s2's encode methods with appropriate prefix bytes to
|
|
// identify the type of the contents.
|
|
Marshal() ([]byte, error)
|
|
}
|
|
|
|
const (
|
|
PointType = "point"
|
|
MultiPointType = "multipoint"
|
|
LineStringType = "linestring"
|
|
MultiLineStringType = "multilinestring"
|
|
PolygonType = "polygon"
|
|
MultiPolygonType = "multipolygon"
|
|
GeometryCollectionType = "geometrycollection"
|
|
CircleType = "circle"
|
|
EnvelopeType = "envelope"
|
|
)
|
|
|
|
// These are the byte prefixes for identifying the
|
|
// shape contained within the doc values byte slice
|
|
// while decoding the contents during the query
|
|
// filtering phase.
|
|
const (
|
|
PointTypePrefix = byte(1)
|
|
MultiPointTypePrefix = byte(2)
|
|
LineStringTypePrefix = byte(3)
|
|
MultiLineStringTypePrefix = byte(4)
|
|
PolygonTypePrefix = byte(5)
|
|
MultiPolygonTypePrefix = byte(6)
|
|
GeometryCollectionTypePrefix = byte(7)
|
|
CircleTypePrefix = byte(8)
|
|
EnvelopeTypePrefix = byte(9)
|
|
)
|
|
|
|
// compositeShape is an optional interface for the
|
|
// composite geoJSON shapes which is composed of
|
|
// multiple spatial shapes within it. Composite shapes
|
|
// like multipoint, multilinestring, multipolygon and
|
|
// geometrycollection shapes are supposed to implement
|
|
// this interface.
|
|
type compositeShape interface {
|
|
// Members implementation returns the
|
|
// geoJSON shapes composed within the shape.
|
|
Members() []index.GeoJSON
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Point represents the geoJSON point type and it
|
|
// implements the index.GeoJSON interface.
|
|
type Point struct {
|
|
Typ string `json:"type"`
|
|
Vertices []float64 `json:"coordinates"`
|
|
s2point *s2.Point
|
|
}
|
|
|
|
func (p *Point) Type() string {
|
|
return strings.ToLower(p.Typ)
|
|
}
|
|
|
|
func (p *Point) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(p)
|
|
}
|
|
|
|
func NewGeoJsonPoint(v []float64) index.GeoJSON {
|
|
rv := &Point{Typ: PointType, Vertices: v}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (p *Point) init() {
|
|
if p.s2point == nil {
|
|
s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
|
|
p.Vertices[1], p.Vertices[0]))
|
|
p.s2point = &s2point
|
|
}
|
|
}
|
|
|
|
func (p *Point) Marshal() ([]byte, error) {
|
|
p.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(32)
|
|
w := bufio.NewWriter(&b)
|
|
err := p.s2point.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{PointTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (p *Point) Intersects(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
return checkPointIntersectsShape(p.s2point, p, other)
|
|
}
|
|
|
|
func (p *Point) Contains(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
return checkPointContainsShape([]*s2.Point{p.s2point}, other)
|
|
}
|
|
|
|
func (p *Point) Coordinates() []float64 {
|
|
return p.Vertices
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// MultiPoint represents the geoJSON multipoint type and it
|
|
// implements the index.GeoJSON interface as well as the
|
|
// compositeShap interface.
|
|
type MultiPoint struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][]float64 `json:"coordinates"`
|
|
s2points []*s2.Point
|
|
}
|
|
|
|
func NewGeoJsonMultiPoint(v [][]float64) index.GeoJSON {
|
|
rv := &MultiPoint{Typ: MultiPointType, Vertices: v}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (mp *MultiPoint) init() {
|
|
if mp.s2points == nil {
|
|
mp.s2points = make([]*s2.Point, len(mp.Vertices))
|
|
for i, point := range mp.Vertices {
|
|
s2point := s2.PointFromLatLng(s2.LatLngFromDegrees(
|
|
point[1], point[0]))
|
|
mp.s2points[i] = &s2point
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *MultiPoint) Marshal() ([]byte, error) {
|
|
p.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(64)
|
|
w := bufio.NewWriter(&b)
|
|
|
|
// first write the number of points.
|
|
count := int32(len(p.s2points))
|
|
err := binary.Write(w, binary.BigEndian, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// write the points.
|
|
for _, s2point := range p.s2points {
|
|
err := s2point.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{MultiPointTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (p *MultiPoint) Type() string {
|
|
return strings.ToLower(p.Typ)
|
|
}
|
|
|
|
func (mp *MultiPoint) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(mp)
|
|
}
|
|
|
|
func (p *MultiPoint) Intersects(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
for _, s2point := range p.s2points {
|
|
rv, err := checkPointIntersectsShape(s2point, p, other)
|
|
if rv && err == nil {
|
|
return rv, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (p *MultiPoint) Contains(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
rv, err := checkPointContainsShape(p.s2points, other)
|
|
if rv && err == nil {
|
|
return rv, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (p *MultiPoint) Coordinates() [][]float64 {
|
|
return p.Vertices
|
|
}
|
|
|
|
func (p *MultiPoint) Members() []index.GeoJSON {
|
|
if len(p.Vertices) > 0 && len(p.s2points) == 0 {
|
|
points := make([]index.GeoJSON, len(p.Vertices))
|
|
for pos, vertices := range p.Vertices {
|
|
points[pos] = NewGeoJsonPoint(vertices)
|
|
}
|
|
return points
|
|
}
|
|
|
|
points := make([]index.GeoJSON, len(p.s2points))
|
|
for pos, point := range p.s2points {
|
|
points[pos] = &Point{s2point: point}
|
|
}
|
|
return points
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// LineString represents the geoJSON linestring type and it
|
|
// implements the index.GeoJSON interface.
|
|
type LineString struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][]float64 `json:"coordinates"`
|
|
pl *s2.Polyline
|
|
}
|
|
|
|
func NewGeoJsonLinestring(points [][]float64) index.GeoJSON {
|
|
rv := &LineString{Typ: LineStringType, Vertices: points}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (ls *LineString) init() {
|
|
if ls.pl == nil {
|
|
latlngs := make([]s2.LatLng, len(ls.Vertices))
|
|
for i, vertex := range ls.Vertices {
|
|
latlngs[i] = s2.LatLngFromDegrees(vertex[1], vertex[0])
|
|
}
|
|
ls.pl = s2.PolylineFromLatLngs(latlngs)
|
|
}
|
|
}
|
|
|
|
func (ls *LineString) Type() string {
|
|
return strings.ToLower(ls.Typ)
|
|
}
|
|
|
|
func (ls *LineString) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(ls)
|
|
}
|
|
|
|
func (ls *LineString) Marshal() ([]byte, error) {
|
|
ls.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(50)
|
|
w := bufio.NewWriter(&b)
|
|
err := ls.pl.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{LineStringTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (ls *LineString) Intersects(other index.GeoJSON) (bool, error) {
|
|
ls.init()
|
|
|
|
return checkLineStringsIntersectsShape([]*s2.Polyline{ls.pl}, ls, other)
|
|
}
|
|
|
|
func (ls *LineString) Contains(other index.GeoJSON) (bool, error) {
|
|
ls.init()
|
|
return checkLineStringsContainsShape([]*s2.Polyline{ls.pl}, other)
|
|
}
|
|
|
|
func (ls *LineString) Coordinates() [][]float64 {
|
|
return ls.Vertices
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// MultiLineString represents the geoJSON multilinestring type
|
|
// and it implements the index.GeoJSON interface as well as the
|
|
// compositeShap interface.
|
|
type MultiLineString struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][][]float64 `json:"coordinates"`
|
|
pls []*s2.Polyline
|
|
}
|
|
|
|
func NewGeoJsonMultilinestring(points [][][]float64) index.GeoJSON {
|
|
rv := &MultiLineString{Typ: MultiLineStringType, Vertices: points}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (mls *MultiLineString) init() {
|
|
if mls.pls == nil {
|
|
mls.pls = s2PolylinesFromCoordinates(mls.Vertices)
|
|
}
|
|
}
|
|
|
|
func (mls *MultiLineString) Type() string {
|
|
return strings.ToLower(mls.Typ)
|
|
}
|
|
|
|
func (mls *MultiLineString) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(mls)
|
|
}
|
|
|
|
func (mls *MultiLineString) Marshal() ([]byte, error) {
|
|
mls.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(256)
|
|
w := bufio.NewWriter(&b)
|
|
|
|
// first write the number of linestrings.
|
|
count := int32(len(mls.pls))
|
|
err := binary.Write(w, binary.BigEndian, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// write the lines.
|
|
for _, ls := range mls.pls {
|
|
err := ls.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{MultiLineStringTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (p *MultiLineString) Intersects(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
return checkLineStringsIntersectsShape(p.pls, p, other)
|
|
}
|
|
|
|
func (p *MultiLineString) Contains(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
return checkLineStringsContainsShape(p.pls, other)
|
|
}
|
|
|
|
func (p *MultiLineString) Coordinates() [][][]float64 {
|
|
return p.Vertices
|
|
}
|
|
|
|
func (p *MultiLineString) Members() []index.GeoJSON {
|
|
if len(p.Vertices) > 0 && len(p.pls) == 0 {
|
|
lines := make([]index.GeoJSON, len(p.Vertices))
|
|
for pos, vertices := range p.Vertices {
|
|
lines[pos] = NewGeoJsonLinestring(vertices)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
lines := make([]index.GeoJSON, len(p.pls))
|
|
for pos, pl := range p.pls {
|
|
lines[pos] = &LineString{pl: pl}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Polygon represents the geoJSON polygon type
|
|
// and it implements the index.GeoJSON interface.
|
|
type Polygon struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][][]float64 `json:"coordinates"`
|
|
s2pgn *s2.Polygon
|
|
}
|
|
|
|
func NewGeoJsonPolygon(points [][][]float64) index.GeoJSON {
|
|
rv := &Polygon{Typ: PolygonType, Vertices: points}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (p *Polygon) init() {
|
|
if p.s2pgn == nil {
|
|
p.s2pgn = s2PolygonFromCoordinates(p.Vertices)
|
|
}
|
|
}
|
|
|
|
func (p *Polygon) Type() string {
|
|
return strings.ToLower(p.Typ)
|
|
}
|
|
|
|
func (p *Polygon) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(p)
|
|
}
|
|
|
|
func (p *Polygon) Marshal() ([]byte, error) {
|
|
p.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(128)
|
|
w := bufio.NewWriter(&b)
|
|
err := p.s2pgn.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{PolygonTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (p *Polygon) Intersects(other index.GeoJSON) (bool, error) {
|
|
// make an s2polygon for reuse.
|
|
p.init()
|
|
|
|
return checkPolygonIntersectsShape(p.s2pgn, p, other)
|
|
}
|
|
|
|
func (p *Polygon) Contains(other index.GeoJSON) (bool, error) {
|
|
// make an s2polygon for reuse.
|
|
p.init()
|
|
|
|
return checkMultiPolygonContainsShape([]*s2.Polygon{p.s2pgn}, p, other)
|
|
}
|
|
|
|
func (p *Polygon) Coordinates() [][][]float64 {
|
|
return p.Vertices
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// MultiPolygon represents the geoJSON multipolygon type
|
|
// and it implements the index.GeoJSON interface as well as the
|
|
// compositeShap interface.
|
|
type MultiPolygon struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][][][]float64 `json:"coordinates"`
|
|
s2pgns []*s2.Polygon
|
|
}
|
|
|
|
func NewGeoJsonMultiPolygon(points [][][][]float64) index.GeoJSON {
|
|
rv := &MultiPolygon{Typ: MultiPolygonType, Vertices: points}
|
|
rv.init()
|
|
return rv
|
|
}
|
|
|
|
func (p *MultiPolygon) init() {
|
|
if p.s2pgns == nil {
|
|
p.s2pgns = make([]*s2.Polygon, len(p.Vertices))
|
|
for i, vertices := range p.Vertices {
|
|
pgn := s2PolygonFromCoordinates(vertices)
|
|
p.s2pgns[i] = pgn
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *MultiPolygon) Type() string {
|
|
return strings.ToLower(p.Typ)
|
|
}
|
|
|
|
func (p *MultiPolygon) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(p)
|
|
}
|
|
|
|
func (p *MultiPolygon) Marshal() ([]byte, error) {
|
|
p.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(512)
|
|
w := bufio.NewWriter(&b)
|
|
|
|
// first write the number of polygons.
|
|
count := int32(len(p.s2pgns))
|
|
err := binary.Write(w, binary.BigEndian, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// write the polygons.
|
|
for _, pgn := range p.s2pgns {
|
|
err := pgn.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{MultiPolygonTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (p *MultiPolygon) Intersects(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
for _, pgn := range p.s2pgns {
|
|
rv, err := checkPolygonIntersectsShape(pgn, p, other)
|
|
if rv && err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (p *MultiPolygon) Contains(other index.GeoJSON) (bool, error) {
|
|
p.init()
|
|
|
|
return checkMultiPolygonContainsShape(p.s2pgns, p, other)
|
|
}
|
|
|
|
func (p *MultiPolygon) Coordinates() [][][][]float64 {
|
|
return p.Vertices
|
|
}
|
|
|
|
func (p *MultiPolygon) Members() []index.GeoJSON {
|
|
if len(p.Vertices) > 0 && len(p.s2pgns) == 0 {
|
|
polygons := make([]index.GeoJSON, len(p.Vertices))
|
|
for pos, vertices := range p.Vertices {
|
|
polygons[pos] = NewGeoJsonPolygon(vertices)
|
|
}
|
|
return polygons
|
|
}
|
|
|
|
polygons := make([]index.GeoJSON, len(p.s2pgns))
|
|
for pos, pgn := range p.s2pgns {
|
|
polygons[pos] = &Polygon{s2pgn: pgn}
|
|
}
|
|
return polygons
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// GeometryCollection represents the geoJSON geometryCollection type
|
|
// and it implements the index.GeoJSON interface as well as the
|
|
// compositeShap interface.
|
|
type GeometryCollection struct {
|
|
Typ string `json:"type"`
|
|
Shapes []index.GeoJSON `json:"geometries"`
|
|
}
|
|
|
|
func (gc *GeometryCollection) Type() string {
|
|
return strings.ToLower(gc.Typ)
|
|
}
|
|
|
|
func (gc *GeometryCollection) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(gc)
|
|
}
|
|
|
|
func (gc *GeometryCollection) Members() []index.GeoJSON {
|
|
shapes := make([]index.GeoJSON, 0, len(gc.Shapes))
|
|
for _, shape := range gc.Shapes {
|
|
if cs, ok := shape.(compositeShape); ok {
|
|
shapes = append(shapes, cs.Members()...)
|
|
} else {
|
|
shapes = append(shapes, shape)
|
|
}
|
|
}
|
|
return shapes
|
|
}
|
|
|
|
func (gc *GeometryCollection) Marshal() ([]byte, error) {
|
|
var b bytes.Buffer
|
|
b.Grow(512)
|
|
w := bufio.NewWriter(&b)
|
|
|
|
// first write the number of shapes.
|
|
count := int32(len(gc.Shapes))
|
|
err := binary.Write(w, binary.BigEndian, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var res []byte
|
|
for _, shape := range gc.Shapes {
|
|
if s, ok := shape.(s2Serializable); ok {
|
|
sb, err := s.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// write the length of each shape.
|
|
err = binary.Write(w, binary.BigEndian, int32(len(sb)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// track the shape contents.
|
|
res = append(res, sb...)
|
|
}
|
|
}
|
|
w.Flush()
|
|
|
|
return append([]byte{GeometryCollectionTypePrefix}, append(b.Bytes(), res...)...), nil
|
|
}
|
|
|
|
func (gc *GeometryCollection) Intersects(other index.GeoJSON) (bool, error) {
|
|
for _, shape := range gc.Members() {
|
|
|
|
intersects, err := shape.Intersects(other)
|
|
if intersects && err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (gc *GeometryCollection) Contains(other index.GeoJSON) (bool, error) {
|
|
// handle composite target shapes explicitly
|
|
if cs, ok := other.(compositeShape); ok {
|
|
otherShapes := cs.Members()
|
|
shapesFoundWithIn := make(map[int]struct{})
|
|
|
|
nextShape:
|
|
for pos, shapeInDoc := range otherShapes {
|
|
for _, shape := range gc.Members() {
|
|
within, err := shape.Contains(shapeInDoc)
|
|
if within && err == nil {
|
|
shapesFoundWithIn[pos] = struct{}{}
|
|
continue nextShape
|
|
}
|
|
}
|
|
}
|
|
|
|
return len(shapesFoundWithIn) == len(otherShapes), nil
|
|
}
|
|
|
|
for _, shape := range gc.Members() {
|
|
within, err := shape.Contains(other)
|
|
if within && err == nil {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (gc *GeometryCollection) UnmarshalJSON(data []byte) error {
|
|
tmp := struct {
|
|
Typ string `json:"type"`
|
|
Shapes []json.RawMessage `json:"geometries"`
|
|
}{}
|
|
|
|
err := jsoniter.Unmarshal(data, &tmp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gc.Typ = tmp.Typ
|
|
|
|
for _, shape := range tmp.Shapes {
|
|
var t map[string]interface{}
|
|
err := jsoniter.Unmarshal(shape, &t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var typ string
|
|
|
|
if val, ok := t["type"]; ok {
|
|
typ = strings.ToLower(val.(string))
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
switch typ {
|
|
case PointType:
|
|
var p Point
|
|
err := jsoniter.Unmarshal(shape, &p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p.init()
|
|
gc.Shapes = append(gc.Shapes, &p)
|
|
|
|
case MultiPointType:
|
|
var mp MultiPoint
|
|
err := jsoniter.Unmarshal(shape, &mp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mp.init()
|
|
gc.Shapes = append(gc.Shapes, &mp)
|
|
|
|
case LineStringType:
|
|
var ls LineString
|
|
err := jsoniter.Unmarshal(shape, &ls)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ls.init()
|
|
gc.Shapes = append(gc.Shapes, &ls)
|
|
|
|
case MultiLineStringType:
|
|
var mls MultiLineString
|
|
err := jsoniter.Unmarshal(shape, &mls)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mls.init()
|
|
gc.Shapes = append(gc.Shapes, &mls)
|
|
|
|
case PolygonType:
|
|
var pgn Polygon
|
|
err := jsoniter.Unmarshal(shape, &pgn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pgn.init()
|
|
gc.Shapes = append(gc.Shapes, &pgn)
|
|
|
|
case MultiPolygonType:
|
|
var pgn MultiPolygon
|
|
err := jsoniter.Unmarshal(shape, &pgn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pgn.init()
|
|
gc.Shapes = append(gc.Shapes, &pgn)
|
|
case CircleType:
|
|
var cir Circle
|
|
err := jsoniter.Unmarshal(shape, &cir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cir.init()
|
|
gc.Shapes = append(gc.Shapes, &cir)
|
|
case EnvelopeType:
|
|
var env Envelope
|
|
err := jsoniter.Unmarshal(shape, &env)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
env.init()
|
|
gc.Shapes = append(gc.Shapes, &env)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Circle represents a custom circle type and it
|
|
// implements the index.GeoJSON interface.
|
|
type Circle struct {
|
|
Typ string `json:"type"`
|
|
Vertices []float64 `json:"coordinates"`
|
|
Radius string `json:"radius"`
|
|
radiusInMeters float64
|
|
s2cap *s2.Cap
|
|
}
|
|
|
|
func NewGeoCircle(points []float64,
|
|
radius string) index.GeoJSON {
|
|
r, err := ParseDistance(radius)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
rv := &Circle{
|
|
Typ: CircleType,
|
|
Vertices: points,
|
|
Radius: radius,
|
|
radiusInMeters: r,
|
|
}
|
|
rv.init()
|
|
|
|
return rv
|
|
}
|
|
|
|
func (c *Circle) Type() string {
|
|
return strings.ToLower(c.Typ)
|
|
}
|
|
|
|
func (c *Circle) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(c)
|
|
}
|
|
|
|
func (c *Circle) init() {
|
|
if c.s2cap == nil {
|
|
c.s2cap = s2Cap(c.Vertices, c.radiusInMeters)
|
|
}
|
|
}
|
|
|
|
func (c *Circle) Marshal() ([]byte, error) {
|
|
c.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(40)
|
|
w := bufio.NewWriter(&b)
|
|
err := c.s2cap.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{CircleTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (c *Circle) Intersects(other index.GeoJSON) (bool, error) {
|
|
c.init()
|
|
|
|
return checkCircleIntersectsShape(c.s2cap, c, other)
|
|
}
|
|
|
|
func (c *Circle) Contains(other index.GeoJSON) (bool, error) {
|
|
c.init()
|
|
return checkCircleContainsShape(c.s2cap, c, other)
|
|
}
|
|
|
|
func (c *Circle) UnmarshalJSON(data []byte) error {
|
|
tmp := struct {
|
|
Typ string `json:"type"`
|
|
Vertices []float64 `json:"coordinates"`
|
|
Radius string `json:"radius"`
|
|
}{}
|
|
|
|
err := jsoniter.Unmarshal(data, &tmp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.Typ = tmp.Typ
|
|
c.Vertices = tmp.Vertices
|
|
c.Radius = tmp.Radius
|
|
if tmp.Radius != "" {
|
|
c.radiusInMeters, err = ParseDistance(tmp.Radius)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
// Envelope represents the envelope/bounding box type and it
|
|
// implements the index.GeoJSON interface.
|
|
type Envelope struct {
|
|
Typ string `json:"type"`
|
|
Vertices [][]float64 `json:"coordinates"`
|
|
r *s2.Rect
|
|
}
|
|
|
|
func NewGeoEnvelope(points [][]float64) index.GeoJSON {
|
|
rv := &Envelope{Vertices: points, Typ: EnvelopeType}
|
|
rv.init()
|
|
|
|
return rv
|
|
}
|
|
|
|
func (e *Envelope) Type() string {
|
|
return strings.ToLower(e.Typ)
|
|
}
|
|
|
|
func (e *Envelope) Value() ([]byte, error) {
|
|
return jsoniter.Marshal(e)
|
|
}
|
|
|
|
func (e *Envelope) init() {
|
|
if e.r == nil {
|
|
e.r = s2RectFromBounds(e.Vertices[0], e.Vertices[1])
|
|
}
|
|
}
|
|
|
|
func (e *Envelope) Marshal() ([]byte, error) {
|
|
e.init()
|
|
|
|
var b bytes.Buffer
|
|
b.Grow(50)
|
|
w := bufio.NewWriter(&b)
|
|
err := e.r.Encode(w)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Flush()
|
|
return append([]byte{EnvelopeTypePrefix}, b.Bytes()...), nil
|
|
}
|
|
|
|
func (e *Envelope) Intersects(other index.GeoJSON) (bool, error) {
|
|
e.init()
|
|
|
|
return checkEnvelopeIntersectsShape(e.r, e, other)
|
|
}
|
|
|
|
func (e *Envelope) Contains(other index.GeoJSON) (bool, error) {
|
|
e.init()
|
|
|
|
return checkEnvelopeContainsShape(e.r, e, other)
|
|
}
|
|
|
|
//--------------------------------------------------------
|
|
|
|
// checkPointIntersectsShape checks for intersection between
|
|
// the point and the shape in the document.
|
|
func checkPointIntersectsShape(point *s2.Point, shapeIn, other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
// check if the points are equal
|
|
if point.ApproxEqual(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check if any of the points are equal
|
|
for _, p := range p2.s2points {
|
|
if point.ApproxEqual(*p) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
// check if the point is contained within the polygon.
|
|
if polygonsIntersectsPoint([]*s2.Polygon{p2.s2pgn}, point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check if the point is contained within any of the polygons
|
|
if polygonsIntersectsPoint(p2.s2pgns, point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if p2, ok := other.(*LineString); ok {
|
|
// project the point to the linestring and check if
|
|
// the projection is equal to the point.
|
|
if polylineIntersectsPoint([]*s2.Polyline{p2.pl}, point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if p2, ok := other.(*MultiLineString); ok {
|
|
// check the intersection for any linestring in the array.
|
|
if polylineIntersectsPoint(p2.pls, point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a geometrycollection.
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
// check for intersection across every member shape.
|
|
if geometryCollectionIntersectsShape(gc, shapeIn) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
// check if the point is contained within the circle
|
|
// by calculating the distance between the point and the
|
|
// center of the circle.
|
|
if c.s2cap.ContainsPoint(*point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is an envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
// check if the point is contained by the envelope
|
|
// by checking if the point is within its bounds
|
|
if e.r.ContainsPoint(*point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s "+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// checkPointContainsShape checks whether the given shape in
|
|
// in the document is approximately contained with the point.
|
|
func checkPointContainsShape(points []*s2.Point,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
for _, point := range points {
|
|
if point.ApproxEqual(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint, if so containment is
|
|
// checked for every point in the multipoint with every given point.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the containment for every point in the collection.
|
|
lookup := make(map[int]struct{})
|
|
for _, qpoint := range points {
|
|
for pos, dpoint := range p2.s2points {
|
|
if _, done := lookup[pos]; done {
|
|
continue
|
|
}
|
|
// already processed all the points in the multipoint.
|
|
if len(lookup) == len(p2.s2points) {
|
|
return true, nil
|
|
}
|
|
|
|
if qpoint.ApproxEqual(*dpoint) {
|
|
lookup[pos] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return len(lookup) == len(p2.s2points), nil
|
|
}
|
|
|
|
// as point is a non closed shape, containment isn't feasible
|
|
// for other higher dimensions.
|
|
return false, nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// checkLineStringsIntersectsShape checks whether the given linestrings
|
|
// intersects with the shape in the document.
|
|
func checkLineStringsIntersectsShape(pls []*s2.Polyline, shapeIn,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if polylineIntersectsPoint(pls, p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the intersection for any point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if polylineIntersectsPoint(pls, point) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
if polylineIntersectsPolygons(pls, []*s2.Polygon{p2.s2pgn}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the intersection for any polygon in the collection.
|
|
if polylineIntersectsPolygons(pls, p2.s2pgns) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if ls, ok := other.(*LineString); ok {
|
|
for _, pl := range pls {
|
|
if ls.pl.Intersects(pl) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if mls, ok := other.(*MultiLineString); ok {
|
|
for _, ls := range pls {
|
|
for _, docLineString := range mls.pls {
|
|
if ls.Intersects(docLineString) {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
// check whether the linestring intersects with any of the
|
|
// shapes Contains a geometrycollection.
|
|
if geometryCollectionIntersectsShape(gc, shapeIn) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
centre := c.s2cap.Center()
|
|
for _, pl := range pls {
|
|
for i := 0; i < pl.NumEdges(); i++ {
|
|
edge := pl.Edge(i)
|
|
distance := s2.DistanceFromSegment(centre, edge.V0, edge.V1)
|
|
if distance <= c.s2cap.Radius() {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
res := rectangleIntersectsWithLineStrings(e.r, pls)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s "+
|
|
"found in document", other.Type())
|
|
}
|
|
|
|
// checkLineStringsContainsShape checks the containment for
|
|
// points and multipoints for the linestring vertices.
|
|
func checkLineStringsContainsShape(pls []*s2.Polyline,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if polylineIntersectsPoint(pls, p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the containment for every point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if !polylineIntersectsPoint(pls, point) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// checkPolygonIntersectsShape checks the intersection between the
|
|
// s2 polygon and the other shapes in the documents.
|
|
func checkPolygonIntersectsShape(s2pgn *s2.Polygon, shapeIn,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if polygonsIntersectsPoint([]*s2.Polygon{s2pgn}, p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
for _, s2point := range p2.s2points {
|
|
if polygonsIntersectsPoint([]*s2.Polygon{s2pgn}, s2point) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
if s2pgn.Intersects(p2.s2pgn) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the intersection for any polygon in the collection.
|
|
for _, s2pgn1 := range p2.s2pgns {
|
|
if s2pgn.Intersects(s2pgn1) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if ls, ok := other.(*LineString); ok {
|
|
if polylineIntersectsPolygons([]*s2.Polyline{ls.pl},
|
|
[]*s2.Polygon{s2pgn}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if mls, ok := other.(*MultiLineString); ok {
|
|
if polylineIntersectsPolygons(mls.pls, []*s2.Polygon{s2pgn}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
// check whether the polygon intersects with any of the
|
|
// member shapes of the geometry collection.
|
|
if geometryCollectionIntersectsShape(gc, shapeIn) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
cp := c.s2cap.Center()
|
|
radius := c.s2cap.Radius()
|
|
|
|
projected := s2pgn.Project(&cp)
|
|
distance := projected.Distance(cp)
|
|
|
|
return distance <= radius, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
s2pgnInDoc := s2PolygonFromS2Rectangle(e.r)
|
|
if s2pgn.Intersects(s2pgnInDoc) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s "+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// checkMultiPolygonContainsShape checks whether the given polygons
|
|
// collectively contains the shape in the document.
|
|
func checkMultiPolygonContainsShape(s2pgns []*s2.Polygon,
|
|
shapeIn, other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if polygonsIntersectsPoint(s2pgns, p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the containment for every point in the collection.
|
|
idx := s2.NewShapeIndex()
|
|
for _, s2pgn := range s2pgns {
|
|
idx.Add(s2pgn)
|
|
}
|
|
|
|
for _, point := range p2.s2points {
|
|
if !s2.NewContainsPointQuery(idx, s2.VertexModelClosed).Contains(*point) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
for _, s2pgn := range s2pgns {
|
|
if s2pgn.Contains(p2.s2pgn) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the intersection for every polygon in the collection.
|
|
polygonsWithIn := make(map[int]struct{})
|
|
nextPolygon:
|
|
for pgnIndex, pgn := range p2.s2pgns {
|
|
for _, s2pgn := range s2pgns {
|
|
if s2pgn.Contains(pgn) {
|
|
polygonsWithIn[pgnIndex] = struct{}{}
|
|
continue nextPolygon
|
|
}
|
|
}
|
|
}
|
|
|
|
return len(p2.s2pgns) == len(polygonsWithIn), nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if ls, ok := other.(*LineString); ok {
|
|
if polygonsContainsLineStrings(s2pgns,
|
|
[]*s2.Polyline{ls.pl}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if mls, ok := other.(*MultiLineString); ok {
|
|
// check whether any of the linestring is inside the polygon.
|
|
if polygonsContainsLineStrings(s2pgns, mls.pls) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
shapesWithIn := make(map[int]struct{})
|
|
nextShape:
|
|
for pos, shape := range gc.Members() {
|
|
for _, s2pgn := range s2pgns {
|
|
contains, err := checkMultiPolygonContainsShape(
|
|
[]*s2.Polygon{s2pgn}, shapeIn, shape)
|
|
if err == nil && contains {
|
|
shapesWithIn[pos] = struct{}{}
|
|
continue nextShape
|
|
}
|
|
}
|
|
}
|
|
return len(shapesWithIn) == len(gc.Members()), nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
cp := c.s2cap.Center()
|
|
radius := c.s2cap.Radius()
|
|
|
|
for _, s2pgn := range s2pgns {
|
|
if s2pgn.ContainsPoint(cp) {
|
|
projected := s2pgn.ProjectToBoundary(&cp)
|
|
distance := projected.Distance(cp)
|
|
if distance >= radius {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
// create a polygon from the rectangle and checks the containment.
|
|
s2pgnInDoc := s2PolygonFromS2Rectangle(e.r)
|
|
for _, s2pgn := range s2pgns {
|
|
if s2pgn.Contains(s2pgnInDoc) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s"+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// checkCircleIntersectsShape checks for intersection of the
|
|
// shape in the document with the circle.
|
|
func checkCircleIntersectsShape(s2cap *s2.Cap, shapeIn,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if s2cap.ContainsPoint(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the intersection for any point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if s2cap.ContainsPoint(*point) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
centerPoint := s2cap.Center()
|
|
projected := p2.s2pgn.Project(¢erPoint)
|
|
distance := projected.Distance(centerPoint)
|
|
return distance <= s2cap.Radius(), nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the intersection for any polygon in the collection.
|
|
for _, s2pgn := range p2.s2pgns {
|
|
centerPoint := s2cap.Center()
|
|
projected := s2pgn.Project(¢erPoint)
|
|
distance := projected.Distance(centerPoint)
|
|
if distance <= s2cap.Radius() {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if p2, ok := other.(*LineString); ok {
|
|
projected, _ := p2.pl.Project(s2cap.Center())
|
|
distance := projected.Distance(s2cap.Center())
|
|
return distance <= s2cap.Radius(), nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if p2, ok := other.(*MultiLineString); ok {
|
|
for _, pl := range p2.pls {
|
|
projected, _ := pl.Project(s2cap.Center())
|
|
distance := projected.Distance(s2cap.Center())
|
|
if distance <= s2cap.Radius() {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
// check whether the circle intersects with any of the
|
|
// member shapes Contains the geometrycollection.
|
|
if geometryCollectionIntersectsShape(gc, shapeIn) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
if s2cap.Intersects(*c.s2cap) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
if e.r.ContainsPoint(s2cap.Center()) {
|
|
return true, nil
|
|
}
|
|
|
|
latlngs := []s2.LatLng{e.r.Vertex(0), e.r.Vertex(1),
|
|
e.r.Vertex(2), e.r.Vertex(3), e.r.Vertex(0)}
|
|
pl := s2.PolylineFromLatLngs(latlngs)
|
|
projected, _ := pl.Project(s2cap.Center())
|
|
distance := projected.Distance(s2cap.Center())
|
|
if distance <= s2cap.Radius() {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s"+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// checkCircleContainsShape checks for containment of the
|
|
// shape in the document with the circle.
|
|
func checkCircleContainsShape(s2cap *s2.Cap,
|
|
shapeIn, other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if s2cap.ContainsPoint(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the intersection for every point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if !s2cap.ContainsPoint(*point) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
for i := 0; i < p2.s2pgn.NumEdges(); i++ {
|
|
edge := p2.s2pgn.Edge(i)
|
|
if !s2cap.ContainsPoint(edge.V0) ||
|
|
!s2cap.ContainsPoint(edge.V1) {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the containment for every polygon in the collection.
|
|
for _, s2pgn := range p2.s2pgns {
|
|
for i := 0; i < s2pgn.NumEdges(); i++ {
|
|
edge := s2pgn.Edge(i)
|
|
if !s2cap.ContainsPoint(edge.V0) ||
|
|
!s2cap.ContainsPoint(edge.V1) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if p2, ok := other.(*LineString); ok {
|
|
for i := 0; i < p2.pl.NumEdges(); i++ {
|
|
edge := p2.pl.Edge(i)
|
|
// check whether both the end vertices are inside the circle.
|
|
if s2cap.ContainsPoint(edge.V0) &&
|
|
s2cap.ContainsPoint(edge.V1) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if p2, ok := other.(*MultiLineString); ok {
|
|
for _, pl := range p2.pls {
|
|
for i := 0; i < pl.NumEdges(); i++ {
|
|
edge := pl.Edge(i)
|
|
// check whether both the end vertices are inside the circle.
|
|
if !(s2cap.ContainsPoint(edge.V0) && s2cap.ContainsPoint(edge.V1)) {
|
|
return false, nil
|
|
}
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
for _, shape := range gc.Members() {
|
|
contains, err := shapeIn.Contains(shape)
|
|
if err == nil && !contains {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
if s2cap.Contains(*c.s2cap) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
for i := 0; i < 4; i++ {
|
|
if !s2cap.ContainsPoint(
|
|
s2.PointFromLatLng(e.r.Vertex(i))) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s"+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// checkEnvelopeIntersectsShape checks whether the given shape in
|
|
// the document is intersecting Contains the envelope/rectangle.
|
|
func checkEnvelopeIntersectsShape(s2rect *s2.Rect, shapeIn,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if s2rect.ContainsPoint(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the intersection for any point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if s2rect.ContainsPoint(*point) {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if pgn, ok := other.(*Polygon); ok {
|
|
if rectangleIntersectsWithPolygons(s2rect,
|
|
[]*s2.Polygon{pgn.s2pgn}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if mpgn, ok := other.(*MultiPolygon); ok {
|
|
// check the intersection for any polygon in the collection.
|
|
if rectangleIntersectsWithPolygons(s2rect, mpgn.s2pgns) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if ls, ok := other.(*LineString); ok {
|
|
if rectangleIntersectsWithLineStrings(s2rect,
|
|
[]*s2.Polyline{ls.pl}) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if mls, ok := other.(*MultiLineString); ok {
|
|
if rectangleIntersectsWithLineStrings(s2rect, mls.pls) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
// check for the intersection of every member shape
|
|
// within the geometrycollection.
|
|
if geometryCollectionIntersectsShape(gc, shapeIn) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
s2pgn := s2PolygonFromS2Rectangle(s2rect)
|
|
cp := c.s2cap.Center()
|
|
projected := s2pgn.Project(&cp)
|
|
distance := projected.Distance(cp)
|
|
return distance <= c.s2cap.Radius(), nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
if s2rect.Intersects(*e.r) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s"+
|
|
" found in document", other.Type())
|
|
}
|
|
|
|
// checkEnvelopeContainsShape checks whether the given shape in
|
|
// the document is contained Contains the envelope/rectangle.
|
|
func checkEnvelopeContainsShape(s2rect *s2.Rect, shapeIn,
|
|
other index.GeoJSON) (bool, error) {
|
|
// check if the other shape is a point.
|
|
if p2, ok := other.(*Point); ok {
|
|
if s2rect.ContainsPoint(*p2.s2point) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a multipoint.
|
|
if p2, ok := other.(*MultiPoint); ok {
|
|
// check the intersection for any point in the collection.
|
|
for _, point := range p2.s2points {
|
|
if !s2rect.ContainsPoint(*point) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a polygon.
|
|
if p2, ok := other.(*Polygon); ok {
|
|
return s2rect.Contains(p2.s2pgn.RectBound()), nil
|
|
}
|
|
|
|
// check if the other shape is a multipolygon.
|
|
if p2, ok := other.(*MultiPolygon); ok {
|
|
// check the containment for every polygon in the collection.
|
|
for _, s2pgn := range p2.s2pgns {
|
|
if !s2rect.Contains(s2pgn.RectBound()) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a linestring.
|
|
if p2, ok := other.(*LineString); ok {
|
|
return s2rect.Contains(p2.pl.RectBound()), nil
|
|
}
|
|
|
|
// check if the other shape is a multilinestring.
|
|
if p2, ok := other.(*MultiLineString); ok {
|
|
for _, pl := range p2.pls {
|
|
if !s2rect.Contains(pl.RectBound()) {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
if gc, ok := other.(*GeometryCollection); ok {
|
|
for _, shape := range gc.Members() {
|
|
contains, err := shapeIn.Contains(shape)
|
|
if err == nil && !contains {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// check if the other shape is a circle.
|
|
if c, ok := other.(*Circle); ok {
|
|
if s2rect.Contains(c.s2cap.RectBound()) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// check if the other shape is a envelope.
|
|
if e, ok := other.(*Envelope); ok {
|
|
if s2rect.Contains(*e.r) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return false, fmt.Errorf("unknown geojson type: %s"+
|
|
" found in document", other.Type())
|
|
}
|