github.com/liucxer/courier@v1.7.1/h3/linked_geo.go (about) 1 package h3 2 3 const NORMALIZATION_SUCCESS = 0 4 const NORMALIZATION_ERR_MULTIPLE_POLYGONS = 1 5 const NORMALIZATION_ERR_UNASSIGNED_HOLES = 2 6 7 /** 8 * Add a linked polygon to the current polygon 9 * @param polygon Polygon to add link to 10 * @return Pointer to new polygon 11 */ 12 func addNewLinkedPolygon(polygon *LinkedGeoPolygon) *LinkedGeoPolygon { 13 next := &LinkedGeoPolygon{} 14 polygon.next = next 15 return next 16 } 17 18 /** 19 * Add a new linked loop to the current polygon 20 * @param polygon Polygon to add loop to 21 * @return Pointer to loop 22 */ 23 func addNewLinkedLoop(polygon *LinkedGeoPolygon) *LinkedGeoLoop { 24 loop := &LinkedGeoLoop{} 25 return addLinkedLoop(polygon, loop) 26 } 27 28 /** 29 * Add an existing linked loop to the current polygon 30 * @param polygon Polygon to add loop to 31 * @return Pointer to loop 32 */ 33 func addLinkedLoop(polygon *LinkedGeoPolygon, loop *LinkedGeoLoop) *LinkedGeoLoop { 34 last := polygon.last 35 if last == nil { 36 polygon.first = loop 37 } else { 38 last.next = loop 39 } 40 41 polygon.last = loop 42 return loop 43 } 44 45 /** 46 * Add a new linked coordinate to the current loop 47 * @param loop Loop to add coordinate to 48 * @param vertex Coordinate to add 49 * @return Pointer to the coordinate 50 */ 51 func addLinkedCoord(loop *LinkedGeoLoop, vertex *GeoCoord) *LinkedGeoCoord { 52 coord := &LinkedGeoCoord{ 53 vertex: *vertex, 54 } 55 56 last := loop.last 57 if last == nil { 58 loop.first = coord 59 } else { 60 last.next = coord 61 } 62 loop.last = coord 63 64 return coord 65 } 66 67 /** 68 * Free all allocated memory for a linked geo loop. The caller is 69 * responsible for freeing memory allocated to input loop struct. 70 * @param loop Loop to free 71 */ 72 func destroyLinkedGeoLoop(loop *LinkedGeoLoop) { 73 var nextCoord *LinkedGeoCoord 74 75 for currentCoord := loop.first; currentCoord != nil; currentCoord = nextCoord { 76 nextCoord = currentCoord.next 77 currentCoord = nil 78 } 79 } 80 81 /** 82 * Free all allocated memory for a linked geo structure. The caller is 83 * responsible for freeing memory allocated to input polygon struct. 84 * @param polygon Pointer to the first polygon in the structure 85 */ 86 func destroyLinkedPolygon(polygon *LinkedGeoPolygon) { 87 // flag to skip the input polygon 88 skip := true 89 var nextPolygon *LinkedGeoPolygon 90 var nextLoop *LinkedGeoLoop 91 for currentPolygon := polygon; currentPolygon != nil; currentPolygon = nextPolygon { 92 for currentLoop := currentPolygon.first; currentLoop != nil; currentLoop = nextLoop { 93 destroyLinkedGeoLoop(currentLoop) 94 nextLoop = currentLoop.next 95 currentLoop = nil 96 } 97 nextPolygon = currentPolygon.next 98 if skip { 99 // do not free the input polygon 100 skip = false 101 } else { 102 currentPolygon = nil 103 } 104 } 105 } 106 107 /** 108 * Count the number of polygons in a linked list 109 * @param polygon Starting polygon 110 * @return Count 111 */ 112 func countLinkedPolygons(polygon *LinkedGeoPolygon) int { 113 count := 0 114 for polygon != nil { 115 count++ 116 polygon = polygon.next 117 } 118 return count 119 } 120 121 /** 122 * Count the number of linked loops in a polygon 123 * @param polygon Polygon to count loops for 124 * @return Count 125 */ 126 func countLinkedLoops(polygon *LinkedGeoPolygon) int { 127 loop := polygon.first 128 count := 0 129 for loop != nil { 130 count++ 131 loop = loop.next 132 } 133 return count 134 } 135 136 /** 137 * Count the number of coordinates in a loop 138 * @param loop Loop to count coordinates for 139 * @return Count 140 */ 141 func countLinkedCoords(loop *LinkedGeoLoop) int { 142 coord := loop.first 143 count := 0 144 for coord != nil { 145 count++ 146 coord = coord.next 147 } 148 return count 149 } 150 151 /** 152 * Count the number of polygons containing a given loop. 153 * @param loop Loop to count containers for 154 * @param polygons Polygons to test 155 * @param bboxes Bounding boxes for polygons, used in point-in-poly check 156 * @param polygonCount Number of polygons in the test array 157 * @return Number of polygons containing the loop 158 */ 159 func countContainers(loop *LinkedGeoLoop, polygons []*LinkedGeoPolygon, bboxes []*BBox, polygonCount int) int { 160 containerCount := 0 161 for i := 0; i < polygonCount; i++ { 162 if loop != polygons[i].first && pointInside(polygons[i].first, bboxes[i], &loop.first.vertex) { 163 containerCount++ 164 } 165 } 166 return containerCount 167 } 168 169 /** 170 * Given a list of nested containers, find the one most deeply nested. 171 * @param polygons Polygon containers to check 172 * @param bboxes Bounding boxes for polygons, used in point-in-poly check 173 * @param polygonCount Number of polygons in the list 174 * @return Deepest container, or null if list is empty 175 */ 176 func findDeepestContainer(polygons []*LinkedGeoPolygon, bboxes []*BBox, polygonCount int) *LinkedGeoPolygon { 177 // Set the initial return value to the first candidate 178 var parent *LinkedGeoPolygon 179 if polygonCount > 0 { 180 parent = polygons[0] 181 } 182 183 // If we have multiple polygons, they must be nested inside each other. 184 // Find the innermost polygon by taking the one with the most containers 185 // in the list. 186 if polygonCount > 1 { 187 max := -1 188 for i := 0; i < polygonCount; i++ { 189 count := countContainers(polygons[i].first, polygons, bboxes, 190 polygonCount) 191 if count > max { 192 parent = polygons[i] 193 max = count 194 } 195 } 196 } 197 198 return parent 199 } 200 201 /** 202 * Find the polygon to which a given hole should be allocated. Note that this 203 * function will return null if no parent is found. 204 * @param loop Inner loop describing a hole 205 * @param polygon Head of a linked list of polygons to check 206 * @param bboxes Bounding boxes for polygons, used in point-in-poly check 207 * @param polygonCount Number of polygons to check 208 * @return Pointer to parent polygon, or null if not found 209 */ 210 func findPolygonForHole(loop *LinkedGeoLoop, polygon *LinkedGeoPolygon, bboxes []BBox, polygonCount int) *LinkedGeoPolygon { 211 // Early exit with no polygons 212 if polygonCount == 0 { 213 return nil 214 } 215 // Initialize arrays for candidate loops and their bounding boxes 216 candidates := make([]*LinkedGeoPolygon, polygonCount) 217 candidateBBoxes := make([]*BBox, polygonCount) 218 219 // Find all polygons that contain the loop 220 candidateCount := 0 221 index := 0 222 for polygon != nil { 223 // We are guaranteed not to overlap, so just test the first point 224 if pointInside(polygon.first, &bboxes[index], &loop.first.vertex) { 225 candidates[candidateCount] = polygon 226 candidateBBoxes[candidateCount] = &bboxes[index] 227 candidateCount++ 228 } 229 polygon = polygon.next 230 index++ 231 } 232 233 // The most deeply nested container is the immediate parent 234 parent := findDeepestContainer(candidates, candidateBBoxes, candidateCount) 235 236 // Free allocated memory 237 candidates = nil 238 candidateBBoxes = nil 239 240 return parent 241 } 242 243 /** 244 * Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON 245 * MultiPolygon rules: Each polygon must have exactly one outer loop, which 246 * must be first in the list, followed by any holes. Holes in this algorithm 247 * are identified by winding order (holes are clockwise), which is guaranteed 248 * by the h3SetToVertexGraph algorithm. 249 * 250 * Input to this function is assumed to be a single polygon including all 251 * loops to normalize. It's assumed that a valid arrangement is possible. 252 * 253 * @param root Root polygon including all loops 254 * @return 0 on success, or an error code > 0 for invalid input 255 */ 256 func normalizeMultiPolygon(root *LinkedGeoPolygon) int { 257 // We assume that the input is a single polygon with loops; 258 // if it has multiple polygons, don't touch it 259 if root.next != nil { 260 return NORMALIZATION_ERR_MULTIPLE_POLYGONS 261 } 262 263 // Count loops, exiting early if there's only one 264 loopCount := countLinkedLoops(root) 265 if loopCount <= 1 { 266 return NORMALIZATION_SUCCESS 267 } 268 269 resultCode := NORMALIZATION_SUCCESS 270 var polygon *LinkedGeoPolygon 271 var next *LinkedGeoLoop 272 273 innerCount, outerCount := 0, 0 274 275 // Create an array to hold all of the inner loops. Note that 276 // this array will never be full, as there will always be fewer 277 // inner loops than outer loops. 278 innerLoops := make([]*LinkedGeoLoop, loopCount) 279 bboxes := make([]BBox, loopCount) 280 281 // Get the first loop and unlink it from root 282 loop := root.first 283 284 *root = LinkedGeoPolygon{} 285 286 // Iterate over all loops, moving inner loops into an array and 287 // assigning outer loops to new polygons 288 for loop != nil { 289 if isClockwise(loop) { 290 innerLoops[innerCount] = loop 291 innerCount++ 292 } else { 293 if polygon == nil { 294 polygon = root 295 } else { 296 polygon = addNewLinkedPolygon(polygon) 297 } 298 addLinkedLoop(polygon, loop) 299 bboxFrom(loop, &bboxes[outerCount]) 300 outerCount++ 301 } 302 // get the next loop and unlink it from this one 303 next = loop.next 304 loop.next = nil 305 loop = next 306 } 307 308 // Find polygon for each inner loop and assign the hole to it 309 for i := 0; i < innerCount; i++ { 310 polygon = findPolygonForHole(innerLoops[i], root, bboxes, outerCount) 311 if polygon != nil { 312 addLinkedLoop(polygon, innerLoops[i]) 313 } else { 314 // If we can't find a polygon (possible with invalid input), then 315 // we need to release the memory for the hole, because the loop has 316 // been unlinked from the root and the caller will no longer have 317 // a way to destroy it with destroyLinkedPolygon. 318 destroyLinkedGeoLoop(innerLoops[i]) 319 innerLoops[i] = nil 320 resultCode = NORMALIZATION_ERR_UNASSIGNED_HOLES 321 } 322 } 323 324 // Free allocated memory 325 innerLoops = nil 326 bboxes = nil 327 328 return resultCode 329 }