github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/types/s2index.go (about)

     1  /*
     2   * Copyright 2016-2018 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package types
    18  
    19  import (
    20  	"log"
    21  
    22  	"github.com/golang/geo/s2"
    23  	geom "github.com/twpayne/go-geom"
    24  
    25  	"github.com/dgraph-io/dgraph/x"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  func parentCoverTokens(parents s2.CellUnion, cover s2.CellUnion) []string {
    30  	// We index parents and cover using different prefix, that makes it more
    31  	// performant at query time to only look up parents/cover depending on what
    32  	// kind of query it is.
    33  	tokens := make([]string, 0, len(parents)+len(cover))
    34  	tokens = append(tokens, createTokens(parents, parentPrefix)...)
    35  	tokens = append(tokens, createTokens(cover, coverPrefix)...)
    36  	x.AssertTruef(len(tokens) == len(parents)+len(cover), "%d %d %d",
    37  		len(tokens), len(parents), len(cover))
    38  	return tokens
    39  }
    40  
    41  // IndexGeoTokens returns the tokens to be used in a geospatial index for the
    42  // given geometry. If the geometry is not supported it returns an error.
    43  func IndexGeoTokens(g geom.T) ([]string, error) {
    44  	parents, cover, err := indexCells(g)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  	return parentCoverTokens(parents, cover), nil
    49  }
    50  
    51  // IndexKeysForCap returns the keys to be used in a geospatial index for a Cap.
    52  /*
    53  func indexCellsForCap(c s2.Cap) s2.CellUnion {
    54  	rc := &s2.RegionCoverer{
    55  		MinLevel: MinCellLevel,
    56  		MaxLevel: MaxCellLevel,
    57  		LevelMod: 0,
    58  		MaxCells: MaxCells,
    59  	}
    60  	return rc.Covering(c)
    61  }
    62  */
    63  
    64  const (
    65  	parentPrefix = "p/"
    66  	coverPrefix  = "c/"
    67  )
    68  
    69  // IndexCells returns two cellunions. The first is a list of parents, which are all the cells upto
    70  // the min level that contain this geometry. The second is the cover, which are the smallest
    71  // possible cells required to cover the region. This makes it easier at query time to query only the
    72  // parents or only the cover or both depending on whether it is a within, contains or intersects
    73  // query.
    74  func indexCells(g geom.T) (parents, cover s2.CellUnion, err error) {
    75  	if g.Stride() != 2 {
    76  		return nil, nil, errors.Errorf("Covering only available for 2D co-ordinates.")
    77  	}
    78  	switch v := g.(type) {
    79  	case *geom.Point:
    80  		p, c := indexCellsForPoint(v, MinCellLevel, MaxCellLevel)
    81  		return p, c, nil
    82  	case *geom.Polygon:
    83  		l, err := loopFromPolygon(v)
    84  		if err != nil {
    85  			return nil, nil, err
    86  		}
    87  		cover := coverLoop(l, MinCellLevel, MaxCellLevel, MaxCells)
    88  		parents := getParentCells(cover, MinCellLevel)
    89  		return parents, cover, nil
    90  	case *geom.MultiPolygon:
    91  		var cover s2.CellUnion
    92  		// Convert each polygon to loop. Get cover for each and append to cover.
    93  		for i := 0; i < v.NumPolygons(); i++ {
    94  			p := v.Polygon(i)
    95  			l, err := loopFromPolygon(p)
    96  			if err != nil {
    97  				return nil, nil, err
    98  			}
    99  			cover = append(cover, coverLoop(l, MinCellLevel, MaxCellLevel, MaxCells)...)
   100  		}
   101  		// Get parents for all cells in cover.
   102  		parents := getParentCells(cover, MinCellLevel)
   103  		return parents, cover, nil
   104  	default:
   105  		return nil, nil, errors.Errorf("Cannot index geometry of type %T", v)
   106  	}
   107  }
   108  
   109  const (
   110  	// MinCellLevel is the smallest cell level (largest cell size) used by indexing
   111  	MinCellLevel = 5 // Approx 250km x 380km
   112  	// MaxCellLevel is the largest cell level (smallest cell size) used by indexing
   113  	MaxCellLevel = 16 // Approx 120m x 180m
   114  	// MaxCells is the maximum number of cells to use when indexing regions.
   115  	MaxCells = 18
   116  )
   117  
   118  func pointFromCoord(r geom.Coord) s2.Point {
   119  	// The geojson spec says that coordinates are specified as [long, lat]
   120  	// We assume that any data encoded in the database follows that format.
   121  	ll := s2.LatLngFromDegrees(r.Y(), r.X())
   122  	return s2.PointFromLatLng(ll)
   123  }
   124  
   125  // PointFromPoint converts a geom.Point to a s2.Point
   126  func pointFromPoint(p *geom.Point) s2.Point {
   127  	return pointFromCoord(p.Coords())
   128  }
   129  
   130  // loopFromPolygon converts a geom.Polygon to a s2.Loop. We use loops instead of s2.Polygon as the
   131  // s2.Polygon implemention is incomplete.
   132  func loopFromPolygon(p *geom.Polygon) (*s2.Loop, error) {
   133  	// go implementation of s2 does not support more than one loop (and will panic if the size of
   134  	// the loops array > 1). So we will skip the holes in the polygon and just use the outer loop.
   135  	r := p.LinearRing(0)
   136  	n := r.NumCoords()
   137  	if n < 4 {
   138  		return nil, errors.Errorf("Can't convert ring with less than 4 pts")
   139  	}
   140  	if !r.Coord(0).Equal(geom.XY, r.Coord(n-1)) {
   141  		return nil, errors.Errorf("Last coordinate not same as first for polygon: %+v\n", p)
   142  	}
   143  	// S2 specifies that the orientation of the polygons should be CCW. However there is no
   144  	// restriction on the orientation in WKB (or geojson). To get the correct orientation we assume
   145  	// that the polygons are always less than one hemisphere. If they are bigger, we flip the
   146  	// orientation.
   147  	reverse := isClockwise(r)
   148  	l := loopFromRing(r, reverse)
   149  
   150  	// Since our clockwise check was approximate, we check the cap and reverse if needed.
   151  	if l.CapBound().Radius().Degrees() > 90 {
   152  		l = loopFromRing(r, !reverse)
   153  	}
   154  	return l, nil
   155  }
   156  
   157  // Checks if a ring is clockwise or counter-clockwise. Note: This uses the algorithm for planar
   158  // polygons and doesn't work for spherical polygons that contain the poles or the antimeridan
   159  // discontinuity. We use this as a fast approximation instead.
   160  func isClockwise(r *geom.LinearRing) bool {
   161  	// The algorithm is described here https://en.wikipedia.org/wiki/Shoelace_formula
   162  	var a float64
   163  	n := r.NumCoords()
   164  	for i := 0; i < n; i++ {
   165  		p1 := r.Coord(i)
   166  		p2 := r.Coord((i + 1) % n)
   167  		a += (p2.X() - p1.X()) * (p1.Y() + p2.Y())
   168  	}
   169  	return a > 0
   170  }
   171  
   172  func loopFromRing(r *geom.LinearRing, reverse bool) *s2.Loop {
   173  	// In WKB, the last coordinate is repeated for a ring to form a closed loop. For s2 the points
   174  	// aren't allowed to repeat and the loop is assumed to be closed, so we skip the last point.
   175  	n := r.NumCoords()
   176  	pts := make([]s2.Point, n-1)
   177  	for i := 0; i < n-1; i++ {
   178  		var c geom.Coord
   179  		if reverse {
   180  			c = r.Coord(n - 1 - i)
   181  		} else {
   182  			c = r.Coord(i)
   183  		}
   184  		p := pointFromCoord(c)
   185  		pts[i] = p
   186  	}
   187  	return s2.LoopFromPoints(pts)
   188  }
   189  
   190  // create cells for point from the minLevel to maxLevel both inclusive.
   191  func indexCellsForPoint(p *geom.Point, minLevel, maxLevel int) (s2.CellUnion, s2.CellUnion) {
   192  	if maxLevel < minLevel {
   193  		log.Fatalf("Maxlevel should be greater than minLevel")
   194  	}
   195  	ll := s2.LatLngFromDegrees(p.Y(), p.X())
   196  	c := s2.CellIDFromLatLng(ll)
   197  	cells := make([]s2.CellID, maxLevel-minLevel+1)
   198  	for l := minLevel; l <= maxLevel; l++ {
   199  		cells[l-minLevel] = c.Parent(l)
   200  	}
   201  	return cells, []s2.CellID{c.Parent(maxLevel)}
   202  }
   203  
   204  func getParentCells(cu s2.CellUnion, minLevel int) s2.CellUnion {
   205  	parents := make(map[s2.CellID]bool)
   206  	for _, c := range cu {
   207  		for l := c.Level(); l >= minLevel; l-- {
   208  			parents[c.Parent(l)] = true
   209  		}
   210  	}
   211  	// convert the parents map to an array
   212  	cells := make([]s2.CellID, len(parents))
   213  	i := 0
   214  	for k := range parents {
   215  		cells[i] = k
   216  		i++
   217  	}
   218  	return cells
   219  }
   220  
   221  func coverLoop(l *s2.Loop, minLevel int, maxLevel int, maxCells int) s2.CellUnion {
   222  	rc := &s2.RegionCoverer{
   223  		MinLevel: minLevel,
   224  		MaxLevel: maxLevel,
   225  		LevelMod: 0,
   226  		MaxCells: maxCells,
   227  	}
   228  	return rc.Covering(l)
   229  }
   230  
   231  // appendTokens creates tokens with a certain prefix and append.
   232  func createTokens(cu s2.CellUnion, prefix string) (toks []string) {
   233  	for _, c := range cu {
   234  		toks = append(toks, prefix+c.ToToken())
   235  	}
   236  	return toks
   237  }