/// <summary> /// Destroy a VertexGraph's sub-objects, freeing their memory. The caller is /// responsible for freeing memory allocated to the VertexGraph struct itself. /// </summary> /// <param name="graph">Graph to destroy</param> /// <!-- Based off 3.1.1 --> public static void destroyVertexGraph(ref VertexGraph graph) { foreach (var bucket in graph.buckets) { bucket.Clear(); } graph.buckets.Clear(); }
/// <summary> /// Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons. /// Polygon outlines will follow GeoJSON MultiPolygon order: Each polygon will /// have one outer loop, which is first in the list, followed by any holes. /// /// It is expected that all hexagons in the set have the same resolution and /// that the set contains no duplicates. Behavior is undefined if duplicates /// or multiple resolutions are present, and the algorithm may produce unexpected /// or invalid output. /// </summary> /// <param name="h3Set">Set of hexagons</param> /// <param name="numHexes">NUmber of hexagons in set</param> /// <param name="out_polygons">output polygon</param> /// <!-- Based off 3.1.1 --> public static void h3SetToLinkedGeo(ref List <H3Index> h3Set, int numHexes, ref LinkedGeo.LinkedGeoPolygon out_polygons) { VertexGraph graph = new VertexGraph(0, 0); h3SetToVertexGraph(ref h3Set, numHexes, ref graph); _vertexGraphToLinkedGeo(ref graph, ref out_polygons); // TODO: The return value, possibly indicating an error, is discarded here - // we should use this when we update the API to return a value LinkedGeo.normalizeMultiPolygon(ref out_polygons); VertexGraph.destroyVertexGraph(ref graph); }
/// <summary> /// Get the next vertex node in the graph. /// </summary> /// <param name="graph">Graph to iterate</param> /// <returns>Vertex node or null if at the the end</returns> /// <!-- Based off 3.1.1 --> public static VertexNode firstVertexNode(ref VertexGraph graph) { foreach (var bucket in graph.buckets) { if (bucket.Count <= 0) { continue; } return(bucket[0]); } return(null); }
/// <summary> /// Internal: Create a vertex graph from a set of hexagons. It is the /// responsibility of the caller to call destroyVertexGraph on the populated /// graph, otherwise the memory in the graph nodes will not be freed. /// </summary> /// /// <param name="h3Set">Set of hexagons</param> /// <param name="numHexes">Number of hexagons in the set</param> /// <param name="graph">Output graph</param> /// <!-- Based off 3.1.1 --> public static void h3SetToVertexGraph(ref List <H3Index> h3Set, int numHexes, ref VertexGraph graph) { GeoBoundary vertices = new GeoBoundary(); GeoCoord fromVertex = new GeoCoord(); GeoCoord toVertex = new GeoCoord(); VertexGraph.VertexNode edge; if (numHexes < 1) { // We still need to init the graph, or calls to destroyVertexGraph will // fail graph = new VertexGraph(0, 0); return; } int res = H3Index.H3_GET_RESOLUTION(h3Set[0]); const int minBuckets = 6; // TODO: Better way to calculate/guess? int numBuckets = numHexes > minBuckets ? numHexes : minBuckets; graph = new VertexGraph(numBuckets, res); // Iterate through every hexagon for (int i = 0; i < numHexes; i++) { H3Index.h3ToGeoBoundary(h3Set[i], ref vertices); // iterate through every edge for (int j = 0; j < vertices.numVerts; j++) { fromVertex = new GeoCoord(vertices.verts[j].lat, vertices.verts[j].lon); //fromVtx = vertices.verts[j]; int idx = (j + 1) % vertices.numVerts; toVertex = new GeoCoord(vertices.verts[idx].lat, vertices.verts[idx].lon); //toVtx = vertices.verts[(j + 1) % vertices.numVerts]; // If we've seen this edge already, it will be reversed edge = VertexGraph.findNodeForEdge(ref graph, toVertex, fromVertex); if (edge != null) { // If we've seen it, drop it. No edge is shared by more than 2 // hexagons, so we'll never see it again. VertexGraph.removeVertexNode(ref graph, ref edge); } else { // Add a new node for this edge VertexGraph.addVertexNode(ref graph, fromVertex, toVertex); } } } }
/// <summary> /// Find the <see cref="VertexNode"/> for a given edge, if it exists. /// </summary> /// <param name="graph">Graph to look in</param> /// <param name="fromVtx">Start Vertex</param> /// <param name="ToVtx">End Vertex, or null if we don't care</param> /// <returns>Pointer to the vertex node, if found</returns> /// <!-- Based off 3.1.1 --> public static VertexNode findNodeForEdge( ref VertexGraph graph, GeoCoord fromVtx, GeoCoord toVtx) { uint index = _hashVertex(fromVtx, graph.res, graph.numBuckets); var currentBucket = graph.buckets[(int)index]; var nodeIndex = currentBucket.FindIndex( t => GeoCoord.geoAlmostEqual(t.from, fromVtx) && (toVtx == null || GeoCoord.geoAlmostEqual(t.to, toVtx)) ); return(nodeIndex < 0 ? null : currentBucket[nodeIndex]); }
/// <summary> /// Remove a node from the graph. The input node will be freed, and should /// not be used after removal. /// </summary> /// <param name="graph">Graph to mutate</param> /// <param name="node">Node to remove</param> /// <returns>0 on success, 1 on failure (node not found)</returns> /// <!-- Based off 3.1.1 --> public static int removeVertexNode(ref VertexGraph graph, ref VertexNode node) { // Determine location uint index = _hashVertex(node.from, graph.res, graph.numBuckets); var currentBucket = graph.buckets[(int)index]; var tnode = node; var nodeIndex = currentBucket.FindIndex(t => t.from == tnode.from && t.to == tnode.to); // Failed to find the node if (nodeIndex < 0) { return(1); } currentBucket.RemoveAt(nodeIndex); graph.size--; return(0); }
/// <summary> /// Internal: Create a LinkedGeoPolygon from a vertex graph. It is the /// responsibility of the caller to call destroyLinkedPolygon on the /// populated linked geo structure, or the memory for that structure /// will not be freed. /// </summary> /// <param name="graph">input graph</param> /// <param name="out_polygon">output polygon</param> /// <!-- Based off 3.1.1 --> internal static void _vertexGraphToLinkedGeo(ref VertexGraph graph, ref LinkedGeo.LinkedGeoPolygon out_polygon) { out_polygon = new LinkedGeo.LinkedGeoPolygon(); VertexGraph.VertexNode edge; // Find the next unused entry point while ((edge = VertexGraph.firstVertexNode(ref graph)) != null) { var loop = LinkedGeo.addNewLinkedLoop(ref out_polygon); // Walk the graph to get the outline do { var addLinkedCoord = LinkedGeo.addLinkedCoord(ref loop, ref edge.from); var nextVertex = edge.to; // Remove frees the node, so we can't use edge after this VertexGraph.removeVertexNode(ref graph, ref edge); edge = VertexGraph.findNodeForVertex(ref graph, ref nextVertex); } while (edge != null); } }
/// <summary>Add an edge to the graph</summary> /// <param name="graph">Graph to add node to</param> /// <param name="fromVtx">Start vertex</param> /// <param name="toVtx">End vertex</param> /// <returns>new node</returns> /// <!-- Based off 3.1.1 --> public static VertexNode addVertexNode(ref VertexGraph graph, GeoCoord fromVtx, GeoCoord toVtx) { // Make the new node VertexNode node = _initVertexNode(fromVtx, toVtx); // Determine location var index = _hashVertex(fromVtx, graph.res, graph.numBuckets); // Check whether there's an existing node in that spot List <VertexNode> currentNode = graph.buckets[(int)index]; if (currentNode.Count == 0) { // Set bucket to the new node graph.buckets[(int)index].Add(node); } else { // Go through the list to make sure the // edge doesn't already exist // // NOTE: Later, use a Hashset foreach (var vertexNode in graph.buckets[(int)index]) { if (GeoCoord.geoAlmostEqual(vertexNode.from, fromVtx) && GeoCoord.geoAlmostEqual(vertexNode.to, toVtx)) { // already exists, bail. return(vertexNode); } } // Add the new node to the end of the list graph.buckets[(int)index].Add(node); } graph.size++; return(node); }
/// <summary> /// Find a Vertex node starting at the given vertex /// </summary> /// <param name="graph">Graph to look in</param> /// <param name="fromVtx">Start vertex</param> /// <returns>Vertex node, if found</returns> /// <!-- Based off 3.1.1 --> public static VertexNode findNodeForVertex( ref VertexGraph graph, ref GeoCoord fromVtx) { return(findNodeForEdge(ref graph, fromVtx, null)); }