A GIS implementation for rocksdb.
Summary: This patch stores gps locations in rocksdb. Each object is uniquely identified by an id. Each object has a gps (latitude, longitude) associated with it. The geodb supports looking up an object either by its gps location or by its id. There is a method to retrieve all objects within a circular radius centered at a specified gps location. Test Plan: Simple unit-test attached. Reviewers: leveldb, haobo Reviewed By: haobo CC: leveldb, tecbot, haobo Differential Revision: https://reviews.facebook.net/D15567
This commit is contained in:
parent
64ae6e9eb9
commit
4031b98373
6
Makefile
6
Makefile
@ -93,7 +93,8 @@ TESTS = \
|
||||
write_batch_test\
|
||||
deletefile_test \
|
||||
table_test \
|
||||
thread_local_test
|
||||
thread_local_test \
|
||||
geodb_test
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@ -366,6 +367,9 @@ merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
deletefile_test: db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/deletefile_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
geodb_test: utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) utilities/geodb/geodb_test.o $(LIBOBJECTS) $(TESTHARNESS) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
|
||||
$(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
rm -f $@
|
||||
$(AR) -rs $@ $(MEMENVOBJECTS)
|
||||
|
103
include/utilities/geo_db.h
Normal file
103
include/utilities/geo_db.h
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
//
|
||||
// Configurable options needed for setting up a Geo database
|
||||
//
|
||||
struct GeoDBOptions {
|
||||
// Backup info and error messages will be written to info_log
|
||||
// if non-nullptr.
|
||||
// Default: nullptr
|
||||
Logger* info_log;
|
||||
|
||||
explicit GeoDBOptions(Logger* _info_log = nullptr):info_log(_info_log) { }
|
||||
};
|
||||
|
||||
//
|
||||
// A position in the earth's geoid
|
||||
//
|
||||
class GeoPosition {
|
||||
public:
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
||||
explicit GeoPosition(double la = 0, double lo = 0) :
|
||||
latitude(la), longitude(lo) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Description of an object on the Geoid. It is located by a GPS location,
|
||||
// and is identified by the id. The value associated with this object is
|
||||
// an opaque string 'value'. Different objects identified by unique id's
|
||||
// can have the same gps-location associated with them.
|
||||
//
|
||||
class GeoObject {
|
||||
public:
|
||||
GeoPosition position;
|
||||
std::string id;
|
||||
std::string value;
|
||||
|
||||
GeoObject() {}
|
||||
|
||||
GeoObject(const GeoPosition& pos, const std::string& i,
|
||||
const std::string& val) :
|
||||
position(pos), id(i), value(val) {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Stack your DB with GeoDB to be able to get geo-spatial support
|
||||
//
|
||||
class GeoDB : public StackableDB {
|
||||
public:
|
||||
// GeoDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// GeoDB owns the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of GeoDB
|
||||
// GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
GeoDB(DB* db, const GeoDBOptions& options) : StackableDB(db) {}
|
||||
virtual ~GeoDB() {}
|
||||
|
||||
// Insert a new object into the location database. The object is
|
||||
// uniquely identified by the id. If an object with the same id already
|
||||
// exists in the db, then the old one is overwritten by the new
|
||||
// object being inserted here.
|
||||
virtual Status Insert(const GeoObject& object) = 0;
|
||||
|
||||
// Retrieve the value of the object located at the specified GPS
|
||||
// location and is identified by the 'id'.
|
||||
virtual Status GetByPosition(const GeoPosition& pos,
|
||||
const Slice& id, std::string* value) = 0;
|
||||
|
||||
// Retrieve the value of the object identified by the 'id'. This method
|
||||
// could be potentially slower than GetByPosition
|
||||
virtual Status GetById(const Slice& id, GeoObject* object) = 0;
|
||||
|
||||
// Delete the specified object
|
||||
virtual Status Remove(const Slice& id) = 0;
|
||||
|
||||
// Returns a list of all items within a circular radius from the
|
||||
// specified gps location. If 'number_of_values' is specified,
|
||||
// then this call returns at most that many number of objects.
|
||||
// The radius is specified in 'meters'.
|
||||
virtual Status SearchRadial(const GeoPosition& pos,
|
||||
double radius,
|
||||
std::vector<GeoObject>* values,
|
||||
int number_of_values = INT_MAX) = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
427
utilities/geodb/geodb_impl.cc
Normal file
427
utilities/geodb/geodb_impl.cc
Normal file
@ -0,0 +1,427 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
#include "utilities/geodb/geodb_impl.h"
|
||||
|
||||
#define __STDC_FORMAT_MACROS
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include "db/filename.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
//
|
||||
// There are two types of keys. The first type of key-values
|
||||
// maps a geo location to the set of object ids and their values.
|
||||
// Table 1
|
||||
// key : p + : + $quadkey + : + $id +
|
||||
// : + $latitude + : + $longitude
|
||||
// value : value of the object
|
||||
// This table can be used to find all objects that reside near
|
||||
// a specified geolocation.
|
||||
//
|
||||
// Table 2
|
||||
// key : 'k' + : + $id
|
||||
// value: $quadkey
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
GeoDBImpl::GeoDBImpl(DB* db, const GeoDBOptions& options) :
|
||||
GeoDB(db, options), db_(db), options_(options) {
|
||||
}
|
||||
|
||||
GeoDBImpl::~GeoDBImpl() {
|
||||
}
|
||||
|
||||
Status GeoDBImpl::Insert(const GeoObject& obj) {
|
||||
WriteBatch batch;
|
||||
|
||||
// It is possible that this id is already associated with
|
||||
// with a different position. We first have to remove that
|
||||
// association before we can insert the new one.
|
||||
|
||||
// remove existing object, if it exists
|
||||
GeoObject old;
|
||||
Status status = GetById(obj.id, &old);
|
||||
if (status.ok()) {
|
||||
assert(obj.id.compare(old.id) == 0);
|
||||
std::string quadkey = PositionToQuad(old.position, Detail);
|
||||
std::string key1 = MakeKey1(old.position, old.id, quadkey);
|
||||
std::string key2 = MakeKey2(old.id);
|
||||
batch.Delete(Slice(key1));
|
||||
batch.Delete(Slice(key2));
|
||||
} else if (status.IsNotFound()) {
|
||||
// What if another thread is trying to insert the same ID concurrently?
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
|
||||
// insert new object
|
||||
std::string quadkey = PositionToQuad(obj.position, Detail);
|
||||
std::string key1 = MakeKey1(obj.position, obj.id, quadkey);
|
||||
std::string key2 = MakeKey2(obj.id);
|
||||
batch.Put(Slice(key1), Slice(obj.value));
|
||||
batch.Put(Slice(key2), Slice(quadkey));
|
||||
return db_->Write(woptions_, &batch);
|
||||
}
|
||||
|
||||
Status GeoDBImpl::GetByPosition(const GeoPosition& pos,
|
||||
const Slice& id,
|
||||
std::string* value) {
|
||||
std::string quadkey = PositionToQuad(pos, Detail);
|
||||
std::string key1 = MakeKey1(pos, id, quadkey);
|
||||
return db_->Get(roptions_, Slice(key1), value);
|
||||
}
|
||||
|
||||
Status GeoDBImpl::GetById(const Slice& id, GeoObject* object) {
|
||||
Status status;
|
||||
Slice quadkey;
|
||||
|
||||
// create an iterator so that we can get a consistent picture
|
||||
// of the database.
|
||||
Iterator* iter = db_->NewIterator(roptions_);
|
||||
|
||||
// create key for table2
|
||||
std::string kt = MakeKey2(id);
|
||||
Slice key2(kt);
|
||||
|
||||
iter->Seek(key2);
|
||||
if (iter->Valid() && iter->status().ok()) {
|
||||
if (iter->key().compare(key2) == 0) {
|
||||
quadkey = iter->value();
|
||||
}
|
||||
}
|
||||
if (quadkey.size() == 0) {
|
||||
delete iter;
|
||||
return Status::NotFound(key2);
|
||||
}
|
||||
|
||||
//
|
||||
// Seek to the quadkey + id prefix
|
||||
//
|
||||
std::string prefix = MakeKey1Prefix(quadkey.ToString(), id);
|
||||
iter->Seek(Slice(prefix));
|
||||
assert(iter->Valid());
|
||||
if (!iter->Valid() || !iter->status().ok()) {
|
||||
delete iter;
|
||||
return Status::NotFound();
|
||||
}
|
||||
|
||||
// split the key into p + quadkey + id + lat + lon
|
||||
std::vector<std::string> parts;
|
||||
Slice key = iter->key();
|
||||
StringSplit(&parts, key.ToString(), ':');
|
||||
assert(parts.size() == 5);
|
||||
assert(parts[0] == "p");
|
||||
assert(parts[1] == quadkey);
|
||||
assert(parts[2] == id);
|
||||
|
||||
// fill up output parameters
|
||||
object->position.latitude = atof(parts[3].c_str());
|
||||
object->position.longitude = atof(parts[4].c_str());
|
||||
object->id = id.ToString(); // this is redundant
|
||||
object->value = iter->value().ToString();
|
||||
delete iter;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
Status GeoDBImpl::Remove(const Slice& id) {
|
||||
// Read the object from the database
|
||||
GeoObject obj;
|
||||
Status status = GetById(id, &obj);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// remove the object by atomically deleting it from both tables
|
||||
std::string quadkey = PositionToQuad(obj.position, Detail);
|
||||
std::string key1 = MakeKey1(obj.position, obj.id, quadkey);
|
||||
std::string key2 = MakeKey2(obj.id);
|
||||
WriteBatch batch;
|
||||
batch.Delete(Slice(key1));
|
||||
batch.Delete(Slice(key2));
|
||||
return db_->Write(woptions_, &batch);
|
||||
}
|
||||
|
||||
Status GeoDBImpl::SearchRadial(const GeoPosition& pos,
|
||||
double radius,
|
||||
std::vector<GeoObject>* values,
|
||||
int number_of_values) {
|
||||
// Gather all bounding quadkeys
|
||||
std::vector<std::string> qids;
|
||||
Status s = searchQuadIds(pos, radius, &qids);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// create an iterator
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
|
||||
// Process each prospective quadkey
|
||||
for (std::string qid : qids) {
|
||||
// The user is interested in only these many objects.
|
||||
if (number_of_values == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// convert quadkey to db key prefix
|
||||
std::string dbkey = MakeQuadKeyPrefix(qid);
|
||||
|
||||
for (iter->Seek(dbkey);
|
||||
number_of_values > 0 && iter->Valid() && iter->status().ok();
|
||||
iter->Next()) {
|
||||
// split the key into p + quadkey + id + lat + lon
|
||||
std::vector<std::string> parts;
|
||||
Slice key = iter->key();
|
||||
StringSplit(&parts, key.ToString(), ':');
|
||||
assert(parts.size() == 5);
|
||||
assert(parts[0] == "p");
|
||||
std::string* quadkey = &parts[1];
|
||||
|
||||
// If the key we are looking for is a prefix of the key
|
||||
// we found from the database, then this is one of the keys
|
||||
// we are looking for.
|
||||
auto res = std::mismatch(qid.begin(), qid.end(), quadkey->begin());
|
||||
if (res.first == qid.end()) {
|
||||
GeoPosition pos(atof(parts[3].c_str()), atof(parts[4].c_str()));
|
||||
GeoObject obj(pos, parts[4], iter->value().ToString());
|
||||
values->push_back(obj);
|
||||
number_of_values--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete iter;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string GeoDBImpl::MakeKey1(const GeoPosition& pos, Slice id,
|
||||
std::string quadkey) {
|
||||
std::string lat = std::to_string(pos.latitude);
|
||||
std::string lon = std::to_string(pos.longitude);
|
||||
std::string key = "p:";
|
||||
key.reserve(5 + quadkey.size() + id.size() + lat.size() + lon.size());
|
||||
key.append(quadkey);
|
||||
key.append(":");
|
||||
key.append(id.ToString());
|
||||
key.append(":");
|
||||
key.append(lat);
|
||||
key.append(":");
|
||||
key.append(lon);
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string GeoDBImpl::MakeKey2(Slice id) {
|
||||
std::string key = "k:";
|
||||
key.append(id.ToString());
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string GeoDBImpl::MakeKey1Prefix(std::string quadkey,
|
||||
Slice id) {
|
||||
std::string key = "p:";
|
||||
key.reserve(3 + quadkey.size() + id.size());
|
||||
key.append(quadkey);
|
||||
key.append(":");
|
||||
key.append(id.ToString());
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string GeoDBImpl::MakeQuadKeyPrefix(std::string quadkey) {
|
||||
std::string key = "p:";
|
||||
key.append(quadkey);
|
||||
return key;
|
||||
}
|
||||
|
||||
void GeoDBImpl::StringSplit(std::vector<std::string>* tokens,
|
||||
const std::string &text, char sep) {
|
||||
std::size_t start = 0, end = 0;
|
||||
while ((end = text.find(sep, start)) != std::string::npos) {
|
||||
tokens->push_back(text.substr(start, end - start));
|
||||
start = end + 1;
|
||||
}
|
||||
tokens->push_back(text.substr(start));
|
||||
}
|
||||
|
||||
// convert degrees to radians
|
||||
double GeoDBImpl::radians(double x) {
|
||||
return (x * PI) / 180;
|
||||
}
|
||||
|
||||
// convert radians to degrees
|
||||
double GeoDBImpl::degrees(double x) {
|
||||
return (x * 180) / PI;
|
||||
}
|
||||
|
||||
// convert a gps location to quad coordinate
|
||||
std::string GeoDBImpl::PositionToQuad(const GeoPosition& pos,
|
||||
int levelOfDetail) {
|
||||
Pixel p = PositionToPixel(pos, levelOfDetail);
|
||||
Tile tile = PixelToTile(p);
|
||||
return TileToQuadKey(tile, levelOfDetail);
|
||||
}
|
||||
|
||||
GeoPosition GeoDBImpl::displaceLatLon(double lat, double lon,
|
||||
double deltay, double deltax) {
|
||||
double dLat = deltay / EarthRadius;
|
||||
double dLon = deltax / (EarthRadius * cos(radians(lat)));
|
||||
return GeoPosition(lat + degrees(dLat),
|
||||
lon + degrees(dLon));
|
||||
}
|
||||
|
||||
//
|
||||
// Return the distance between two positions on the earth
|
||||
//
|
||||
double GeoDBImpl::distance(double lat1, double lon1,
|
||||
double lat2, double lon2) {
|
||||
double lon = radians(lon2 - lon1);
|
||||
double lat = radians(lat2 - lat1);
|
||||
|
||||
double a = (sin(lat / 2) * sin(lat / 2)) +
|
||||
cos(radians(lat1)) * cos(radians(lat2)) *
|
||||
(sin(lon / 2) * sin(lon / 2));
|
||||
double angle = 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||
return angle * EarthRadius;
|
||||
}
|
||||
|
||||
//
|
||||
// Returns all the quadkeys inside the search range
|
||||
//
|
||||
Status GeoDBImpl::searchQuadIds(const GeoPosition& position,
|
||||
double radius,
|
||||
std::vector<std::string>* quadKeys) {
|
||||
// get the outline of the search square
|
||||
GeoPosition topLeftPos = boundingTopLeft(position, radius);
|
||||
GeoPosition bottomRightPos = boundingBottomRight(position, radius);
|
||||
|
||||
Pixel topLeft = PositionToPixel(topLeftPos, Detail);
|
||||
Pixel bottomRight = PositionToPixel(bottomRightPos, Detail);
|
||||
|
||||
// how many level of details to look for
|
||||
int numberOfTilesAtMaxDepth = floor((bottomRight.x - topLeft.x) / 256);
|
||||
int zoomLevelsToRise = floor(log(numberOfTilesAtMaxDepth) / log(2));
|
||||
zoomLevelsToRise++;
|
||||
int levels = std::max(0, Detail - zoomLevelsToRise);
|
||||
|
||||
quadKeys->push_back(PositionToQuad(GeoPosition(topLeftPos.latitude,
|
||||
topLeftPos.longitude),
|
||||
levels));
|
||||
quadKeys->push_back(PositionToQuad(GeoPosition(topLeftPos.latitude,
|
||||
bottomRightPos.longitude),
|
||||
levels));
|
||||
quadKeys->push_back(PositionToQuad(GeoPosition(bottomRightPos.latitude,
|
||||
topLeftPos.longitude),
|
||||
levels));
|
||||
quadKeys->push_back(PositionToQuad(GeoPosition(bottomRightPos.latitude,
|
||||
bottomRightPos.longitude),
|
||||
levels));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Determines the ground resolution (in meters per pixel) at a specified
|
||||
// latitude and level of detail.
|
||||
// Latitude (in degrees) at which to measure the ground resolution.
|
||||
// Level of detail, from 1 (lowest detail) to 23 (highest detail).
|
||||
// Returns the ground resolution, in meters per pixel.
|
||||
double GeoDBImpl::GroundResolution(double latitude, int levelOfDetail) {
|
||||
latitude = clip(latitude, MinLatitude, MaxLatitude);
|
||||
return cos(latitude * PI / 180) * 2 * PI * EarthRadius /
|
||||
MapSize(levelOfDetail);
|
||||
}
|
||||
|
||||
// Converts a point from latitude/longitude WGS-84 coordinates (in degrees)
|
||||
// into pixel XY coordinates at a specified level of detail.
|
||||
GeoDBImpl::Pixel GeoDBImpl::PositionToPixel(const GeoPosition& pos,
|
||||
int levelOfDetail) {
|
||||
double latitude = clip(pos.latitude, MinLatitude, MaxLatitude);
|
||||
double x = (pos.longitude + 180) / 360;
|
||||
double sinLatitude = sin(latitude * PI / 180);
|
||||
double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * PI);
|
||||
double mapSize = MapSize(levelOfDetail);
|
||||
double X = floor(clip(x * mapSize + 0.5, 0, mapSize - 1));
|
||||
double Y = floor(clip(y * mapSize + 0.5, 0, mapSize - 1));
|
||||
return Pixel((unsigned int)X, (unsigned int)Y);
|
||||
}
|
||||
|
||||
GeoPosition GeoDBImpl::PixelToPosition(const Pixel& pixel, int levelOfDetail) {
|
||||
double mapSize = MapSize(levelOfDetail);
|
||||
double x = (clip(pixel.x, 0, mapSize - 1) / mapSize) - 0.5;
|
||||
double y = 0.5 - (clip(pixel.y, 0, mapSize - 1) / mapSize);
|
||||
double latitude = 90 - 360 * atan(exp(-y * 2 * PI)) / PI;
|
||||
double longitude = 360 * x;
|
||||
return GeoPosition(latitude, longitude);
|
||||
}
|
||||
|
||||
// Converts a Pixel to a Tile
|
||||
GeoDBImpl::Tile GeoDBImpl::PixelToTile(const Pixel& pixel) {
|
||||
unsigned int tileX = floor(pixel.x / 256);
|
||||
unsigned int tileY = floor(pixel.y / 256);
|
||||
return Tile(tileX, tileY);
|
||||
}
|
||||
|
||||
GeoDBImpl::Pixel GeoDBImpl::TileToPixel(const Tile& tile) {
|
||||
unsigned int pixelX = tile.x * 256;
|
||||
unsigned int pixelY = tile.y * 256;
|
||||
return Pixel(pixelX, pixelY);
|
||||
}
|
||||
|
||||
// Convert a Tile to a quadkey
|
||||
std::string GeoDBImpl::TileToQuadKey(const Tile& tile, int levelOfDetail) {
|
||||
std::stringstream quadKey;
|
||||
for (int i = levelOfDetail; i > 0; i--) {
|
||||
char digit = '0';
|
||||
int mask = 1 << (i - 1);
|
||||
if ((tile.x & mask) != 0) {
|
||||
digit++;
|
||||
}
|
||||
if ((tile.y & mask) != 0) {
|
||||
digit++;
|
||||
digit++;
|
||||
}
|
||||
quadKey << digit;
|
||||
}
|
||||
return quadKey.str();
|
||||
}
|
||||
|
||||
//
|
||||
// Convert a quadkey to a tile and its level of detail
|
||||
//
|
||||
void GeoDBImpl::QuadKeyToTile(std::string quadkey, Tile* tile,
|
||||
int *levelOfDetail) {
|
||||
tile->x = tile->y = 0;
|
||||
*levelOfDetail = quadkey.size();
|
||||
const char* key = reinterpret_cast<const char *>(quadkey.c_str());
|
||||
for (int i = *levelOfDetail; i > 0; i--) {
|
||||
int mask = 1 << (i - 1);
|
||||
switch (key[*levelOfDetail - i]) {
|
||||
case '0':
|
||||
break;
|
||||
|
||||
case '1':
|
||||
tile->x |= mask;
|
||||
break;
|
||||
|
||||
case '2':
|
||||
tile->y |= mask;
|
||||
break;
|
||||
|
||||
case '3':
|
||||
tile->x |= mask;
|
||||
tile->y |= mask;
|
||||
break;
|
||||
|
||||
default:
|
||||
std::stringstream msg;
|
||||
msg << quadkey;
|
||||
msg << " Invalid QuadKey.";
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
187
utilities/geodb/geodb_impl.h
Normal file
187
utilities/geodb/geodb_impl.h
Normal file
@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "utilities/geo_db.h"
|
||||
#include "utilities/stackable_db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A specific implementation of GeoDB
|
||||
|
||||
class GeoDBImpl : public GeoDB {
|
||||
public:
|
||||
GeoDBImpl(DB* db, const GeoDBOptions& options);
|
||||
~GeoDBImpl();
|
||||
|
||||
// Associate the GPS location with the identified by 'id'. The value
|
||||
// is a blob that is associated with this object.
|
||||
virtual Status Insert(const GeoObject& object);
|
||||
|
||||
// Retrieve the value of the object located at the specified GPS
|
||||
// location and is identified by the 'id'.
|
||||
virtual Status GetByPosition(const GeoPosition& pos,
|
||||
const Slice& id,
|
||||
std::string* value);
|
||||
|
||||
// Retrieve the value of the object identified by the 'id'. This method
|
||||
// could be potentially slower than GetByPosition
|
||||
virtual Status GetById(const Slice& id, GeoObject* object);
|
||||
|
||||
// Delete the specified object
|
||||
virtual Status Remove(const Slice& id);
|
||||
|
||||
// Returns a list of all items within a circular radius from the
|
||||
// specified gps location
|
||||
virtual Status SearchRadial(const GeoPosition& pos,
|
||||
double radius,
|
||||
std::vector<GeoObject>* values,
|
||||
int number_of_values);
|
||||
|
||||
private:
|
||||
DB* db_;
|
||||
const GeoDBOptions options_;
|
||||
const WriteOptions woptions_;
|
||||
const ReadOptions roptions_;
|
||||
|
||||
// The value of PI
|
||||
static constexpr double PI = 3.141592653589793;
|
||||
|
||||
// convert degrees to radians
|
||||
static double radians(double x);
|
||||
|
||||
// convert radians to degrees
|
||||
static double degrees(double x);
|
||||
|
||||
// A pixel class that captures X and Y coordinates
|
||||
class Pixel {
|
||||
public:
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Pixel(unsigned int a, unsigned int b) :
|
||||
x(a), y(b) {
|
||||
}
|
||||
};
|
||||
|
||||
// A Tile in the geoid
|
||||
class Tile {
|
||||
public:
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
Tile(unsigned int a, unsigned int b) :
|
||||
x(a), y(b) {
|
||||
}
|
||||
};
|
||||
|
||||
// convert a gps location to quad coordinate
|
||||
static std::string PositionToQuad(const GeoPosition& pos, int levelOfDetail);
|
||||
|
||||
// arbitrary constant use for WGS84 via
|
||||
// http://en.wikipedia.org/wiki/World_Geodetic_System
|
||||
// http://mathforum.org/library/drmath/view/51832.html
|
||||
// http://msdn.microsoft.com/en-us/library/bb259689.aspx
|
||||
// http://www.tuicool.com/articles/NBrE73
|
||||
//
|
||||
const int Detail = 23;
|
||||
static constexpr double EarthRadius = 6378137;
|
||||
static constexpr double MinLatitude = -85.05112878;
|
||||
static constexpr double MaxLatitude = 85.05112878;
|
||||
static constexpr double MinLongitude = -180;
|
||||
static constexpr double MaxLongitude = 180;
|
||||
|
||||
// clips a number to the specified minimum and maximum values.
|
||||
static double clip(double n, double minValue, double maxValue) {
|
||||
return fmin(fmax(n, minValue), maxValue);
|
||||
}
|
||||
|
||||
// Determines the map width and height (in pixels) at a specified level
|
||||
// of detail, from 1 (lowest detail) to 23 (highest detail).
|
||||
// Returns the map width and height in pixels.
|
||||
static unsigned int MapSize(int levelOfDetail) {
|
||||
return (unsigned int)(256 << levelOfDetail);
|
||||
}
|
||||
|
||||
// Determines the ground resolution (in meters per pixel) at a specified
|
||||
// latitude and level of detail.
|
||||
// Latitude (in degrees) at which to measure the ground resolution.
|
||||
// Level of detail, from 1 (lowest detail) to 23 (highest detail).
|
||||
// Returns the ground resolution, in meters per pixel.
|
||||
static double GroundResolution(double latitude, int levelOfDetail);
|
||||
|
||||
// Converts a point from latitude/longitude WGS-84 coordinates (in degrees)
|
||||
// into pixel XY coordinates at a specified level of detail.
|
||||
static Pixel PositionToPixel(const GeoPosition& pos, int levelOfDetail);
|
||||
|
||||
static GeoPosition PixelToPosition(const Pixel& pixel, int levelOfDetail);
|
||||
|
||||
// Converts a Pixel to a Tile
|
||||
static Tile PixelToTile(const Pixel& pixel);
|
||||
|
||||
static Pixel TileToPixel(const Tile& tile);
|
||||
|
||||
// Convert a Tile to a quadkey
|
||||
static std::string TileToQuadKey(const Tile& tile, int levelOfDetail);
|
||||
|
||||
// Convert a quadkey to a tile and its level of detail
|
||||
static void QuadKeyToTile(std::string quadkey, Tile* tile,
|
||||
int *levelOfDetail);
|
||||
|
||||
// Return the distance between two positions on the earth
|
||||
static double distance(double lat1, double lon1,
|
||||
double lat2, double lon2);
|
||||
static GeoPosition displaceLatLon(double lat, double lon,
|
||||
double deltay, double deltax);
|
||||
|
||||
//
|
||||
// Returns the top left position after applying the delta to
|
||||
// the specified position
|
||||
//
|
||||
static GeoPosition boundingTopLeft(const GeoPosition& in, double radius) {
|
||||
return displaceLatLon(in.latitude, in.longitude, -radius, -radius);
|
||||
}
|
||||
|
||||
//
|
||||
// Returns the bottom right position after applying the delta to
|
||||
// the specified position
|
||||
static GeoPosition boundingBottomRight(const GeoPosition& in,
|
||||
double radius) {
|
||||
return displaceLatLon(in.latitude, in.longitude, radius, radius);
|
||||
}
|
||||
|
||||
//
|
||||
// Get all quadkeys within a radius of a specified position
|
||||
//
|
||||
Status searchQuadIds(const GeoPosition& position,
|
||||
double radius,
|
||||
std::vector<std::string>* quadKeys);
|
||||
|
||||
// splits a string into its components
|
||||
static void StringSplit(std::vector<std::string>* tokens,
|
||||
const std::string &text,
|
||||
char sep);
|
||||
|
||||
//
|
||||
// Create keys for accessing rocksdb table(s)
|
||||
//
|
||||
static std::string MakeKey1(const GeoPosition& pos,
|
||||
Slice id,
|
||||
std::string quadkey);
|
||||
static std::string MakeKey2(Slice id);
|
||||
static std::string MakeKey1Prefix(std::string quadkey,
|
||||
Slice id);
|
||||
static std::string MakeQuadKeyPrefix(std::string quadkey);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
123
utilities/geodb/geodb_test.cc
Normal file
123
utilities/geodb/geodb_test.cc
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
//
|
||||
#include "utilities/geodb/geodb_impl.h"
|
||||
|
||||
#include <cctype>
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class GeoDBTest {
|
||||
public:
|
||||
static const std::string kDefaultDbName;
|
||||
static Options options;
|
||||
DB* db;
|
||||
GeoDB* geodb;
|
||||
|
||||
GeoDBTest() {
|
||||
GeoDBOptions geodb_options;
|
||||
ASSERT_OK(DestroyDB(kDefaultDbName, options));
|
||||
options.create_if_missing = true;
|
||||
Status status = DB::Open(options, kDefaultDbName, &db);
|
||||
geodb = new GeoDBImpl(db, geodb_options);
|
||||
}
|
||||
|
||||
~GeoDBTest() {
|
||||
delete geodb;
|
||||
}
|
||||
|
||||
GeoDB* getdb() {
|
||||
return geodb;
|
||||
}
|
||||
};
|
||||
|
||||
const std::string GeoDBTest::kDefaultDbName = "/tmp/geodefault/";
|
||||
Options GeoDBTest::options = Options();
|
||||
|
||||
// Insert, Get and Remove
|
||||
TEST(GeoDBTest, SimpleTest) {
|
||||
GeoPosition pos1(100, 101);
|
||||
std::string id1("id1");
|
||||
std::string value1("value1");
|
||||
|
||||
// insert first object into database
|
||||
GeoObject obj1(pos1, id1, value1);
|
||||
Status status = getdb()->Insert(obj1);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// insert second object into database
|
||||
GeoPosition pos2(200, 201);
|
||||
std::string id2("id2");
|
||||
std::string value2 = "value2";
|
||||
GeoObject obj2(pos2, id2, value2);
|
||||
status = getdb()->Insert(obj2);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// retrieve first object using position
|
||||
std::string value;
|
||||
status = getdb()->GetByPosition(pos1, Slice(id1), &value);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_EQ(value, value1);
|
||||
|
||||
// retrieve first object using id
|
||||
GeoObject obj;
|
||||
status = getdb()->GetById(Slice(id1), &obj);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_EQ(obj.position.latitude, 100);
|
||||
ASSERT_EQ(obj.position.longitude, 101);
|
||||
ASSERT_EQ(obj.id.compare(id1), 0);
|
||||
ASSERT_EQ(obj.value, value1);
|
||||
|
||||
// delete first object
|
||||
status = getdb()->Remove(Slice(id1));
|
||||
ASSERT_TRUE(status.ok());
|
||||
status = getdb()->GetByPosition(pos1, Slice(id1), &value);
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
status = getdb()->GetById(id1, &obj);
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
|
||||
// check that we can still find second object
|
||||
status = getdb()->GetByPosition(pos2, id2, &value);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_EQ(value, value2);
|
||||
status = getdb()->GetById(id2, &obj);
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
// Search.
|
||||
// Verify distances via http://www.stevemorse.org/nearest/distance.php
|
||||
TEST(GeoDBTest, Search) {
|
||||
GeoPosition pos1(45, 45);
|
||||
std::string id1("mid1");
|
||||
std::string value1 = "midvalue1";
|
||||
|
||||
// insert object at 45 degree latitude
|
||||
GeoObject obj1(pos1, id1, value1);
|
||||
Status status = getdb()->Insert(obj1);
|
||||
ASSERT_TRUE(status.ok());
|
||||
|
||||
// search all objects centered at 46 degree latitude with
|
||||
// a radius of 200 kilometers. We should find the one object that
|
||||
// we inserted earlier.
|
||||
std::vector<GeoObject> values;
|
||||
status = getdb()->SearchRadial(GeoPosition(46, 46), 200000, &values);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
|
||||
// search all objects centered at 46 degree latitude with
|
||||
// a radius of 2 kilometers. There should be none.
|
||||
values.clear();
|
||||
status = getdb()->SearchRadial(GeoPosition(46, 46), 2, &values);
|
||||
ASSERT_TRUE(status.ok());
|
||||
ASSERT_EQ(values.size(), 0);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return rocksdb::test::RunAllTests();
|
||||
}
|
Loading…
Reference in New Issue
Block a user