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 }