h3_vertexGraph.c (116 lines of code) (raw):
/*
* Copyright 2017-2018, 2020-2021 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @file vertexGraph.c
* @brief Data structure for storing a graph of vertices
*/
#include "h3_vertexGraph.h"
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "h3_alloc.h"
#include "h3_latLng.h"
/**
* Initialize a new VertexGraph
* @param graph Graph to initialize
* @param numBuckets Number of buckets to include in the graph
* @param res Resolution of the hexagons whose vertices we're storing
*/
void initVertexGraph(VertexGraph *graph, int numBuckets, int res) {
if (numBuckets > 0) {
graph->buckets = H3_MEMORY(calloc)(numBuckets, sizeof(VertexNode *));
assert(graph->buckets != NULL);
} else {
graph->buckets = NULL;
}
graph->numBuckets = numBuckets;
graph->size = 0;
graph->res = res;
}
/**
* Destroy a VertexGraph's sub-objects, freeing their memory. The caller is
* responsible for freeing memory allocated to the VertexGraph struct itself.
* @param graph Graph to destroy
*/
void destroyVertexGraph(VertexGraph *graph) {
VertexNode *node;
while ((node = firstVertexNode(graph)) != NULL) {
removeVertexNode(graph, node);
}
H3_MEMORY(free)(graph->buckets);
}
/**
* Get an integer hash for a lat/lng point, at a precision determined
* by the current hexagon resolution.
* TODO: Light testing suggests this might not be sufficient at resolutions
* finer than 10. Design a better hash function if performance and collisions
* seem to be an issue here.
* @param vertex Lat/lng vertex to hash
* @param res Resolution of the hexagon the vertex belongs to
* @param numBuckets Number of buckets in the graph
* @return Integer hash
*/
uint32_t _hashVertex(const LatLng *vertex, int res, int numBuckets) {
// Simple hash: Take the sum of the lat and lng with a precision level
// determined by the resolution, converted to int, modulo bucket count.
return (uint32_t)fmod(fabs((vertex->lat + vertex->lng) * pow(10, 15 - res)),
numBuckets);
}
void _initVertexNode(VertexNode *node, const LatLng *fromVtx,
const LatLng *toVtx) {
node->from = *fromVtx;
node->to = *toVtx;
node->next = NULL;
}
/**
* Add a edge to the graph
* @param graph Graph to add node to
* @param fromVtx Start vertex
* @param toVtx End vertex
* @return Pointer to the new node
*/
VertexNode *addVertexNode(VertexGraph *graph, const LatLng *fromVtx,
const LatLng *toVtx) {
// Make the new node
VertexNode *node = H3_MEMORY(malloc)(sizeof(VertexNode));
assert(node != NULL);
_initVertexNode(node, fromVtx, toVtx);
// Determine location
uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets);
// Check whether there's an existing node in that spot
VertexNode *currentNode = graph->buckets[index];
if (currentNode == NULL) {
// Set bucket to the new node
graph->buckets[index] = node;
} else {
// Find the end of the list
do {
// Check the the edge we're adding doesn't already exist
if (geoAlmostEqual(¤tNode->from, fromVtx) &&
geoAlmostEqual(¤tNode->to, toVtx)) {
// already exists, bail
H3_MEMORY(free)(node);
return currentNode;
}
if (currentNode->next != NULL) {
currentNode = currentNode->next;
}
} while (currentNode->next != NULL);
// Add the new node to the end of the list
currentNode->next = node;
}
graph->size++;
return node;
}
/**
* Remove a node from the graph. The input node will be freed, and should
* not be used after removal.
* @param graph Graph to mutate
* @param node Node to remove
* @return 0 on success, 1 on failure (node not found)
*/
int removeVertexNode(VertexGraph *graph, VertexNode *node) {
// Determine location
uint32_t index = _hashVertex(&node->from, graph->res, graph->numBuckets);
VertexNode *currentNode = graph->buckets[index];
int found = 0;
if (currentNode != NULL) {
if (currentNode == node) {
graph->buckets[index] = node->next;
found = 1;
}
// Look through the list
while (!found && currentNode->next != NULL) {
if (currentNode->next == node) {
// splice the node out
currentNode->next = node->next;
found = 1;
}
currentNode = currentNode->next;
}
}
if (found) {
H3_MEMORY(free)(node);
graph->size--;
return 0;
}
// Failed to find the node
return 1;
}
/**
* Find the Vertex node for a given edge, if it exists
* @param graph Graph to look in
* @param fromVtx Start vertex
* @param toVtx End vertex, or NULL if we don't care
* @return Pointer to the vertex node, if found
*/
VertexNode *findNodeForEdge(const VertexGraph *graph, const LatLng *fromVtx,
const LatLng *toVtx) {
// Determine location
uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets);
// Check whether there's an existing node in that spot
VertexNode *node = graph->buckets[index];
if (node != NULL) {
// Look through the list and see if we find the edge
do {
if (geoAlmostEqual(&node->from, fromVtx) &&
(toVtx == NULL || geoAlmostEqual(&node->to, toVtx))) {
return node;
}
node = node->next;
} while (node != NULL);
}
// Iteration lookup fail
return NULL;
}
/**
* Find a Vertex node starting at the given vertex
* @param graph Graph to look in
* @param fromVtx Start vertex
* @return Pointer to the vertex node, if found
*/
VertexNode *findNodeForVertex(const VertexGraph *graph, const LatLng *fromVtx) {
return findNodeForEdge(graph, fromVtx, NULL);
}
/**
* Get the next vertex node in the graph.
* @param graph Graph to iterate
* @return Vertex node, or NULL if at the end
*/
VertexNode *firstVertexNode(const VertexGraph *graph) {
VertexNode *node = NULL;
int currentIndex = 0;
while (node == NULL) {
if (currentIndex < graph->numBuckets) {
// find the first node in the next bucket
node = graph->buckets[currentIndex];
} else {
// end of iteration
return NULL;
}
currentIndex++;
}
return node;
}