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  }