Conflicts: db/db_impl.cc db/db_test.cc db/internal_stats.cc db/internal_stats.h db/version_edit.cc db/version_edit.h db/version_set.cc include/rocksdb/options.h util/options.ccmain
commit
ddbd1ece88
@ -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
|
@ -0,0 +1,17 @@ |
|||||||
|
NATIVE_JAVA_CLASSES = org.rocksdb.RocksDB
|
||||||
|
NATIVE_INCLUDE = ./include
|
||||||
|
ROCKSDB_JAR = rocksdbjni.jar
|
||||||
|
|
||||||
|
clean: |
||||||
|
-find . -name "*.class" -exec rm {} \;
|
||||||
|
-find . -name "hs*.log" -exec rm {} \;
|
||||||
|
rm -f $(ROCKSDB_JAR)
|
||||||
|
|
||||||
|
java: |
||||||
|
javac org/rocksdb/*.java
|
||||||
|
jar -cf $(ROCKSDB_JAR) org/rocksdb/*.class
|
||||||
|
javah -d $(NATIVE_INCLUDE) -jni $(NATIVE_JAVA_CLASSES)
|
||||||
|
|
||||||
|
sample: |
||||||
|
javac -cp $(ROCKSDB_JAR) RocksDBSample.java
|
||||||
|
java -ea -Djava.library.path=.:../ -cp ".:./*" RocksDBSample /tmp/rocksdbjni/
|
@ -0,0 +1,79 @@ |
|||||||
|
// Copyright (c) 2014, 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.
|
||||||
|
|
||||||
|
import java.util.*; |
||||||
|
import java.lang.*; |
||||||
|
import org.rocksdb.*; |
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
public class RocksDBSample { |
||||||
|
static { |
||||||
|
System.loadLibrary("rocksdbjni"); |
||||||
|
} |
||||||
|
|
||||||
|
public static void main(String[] args) { |
||||||
|
if (args.length < 1) { |
||||||
|
System.out.println("usage: RocksDBSample db_path"); |
||||||
|
return; |
||||||
|
} |
||||||
|
String db_path = args[0]; |
||||||
|
|
||||||
|
System.out.println("RocksDBSample"); |
||||||
|
|
||||||
|
try { |
||||||
|
RocksDB db = RocksDB.open(db_path); |
||||||
|
db.put("hello".getBytes(), "world".getBytes()); |
||||||
|
byte[] value = db.get("hello".getBytes()); |
||||||
|
System.out.format("Get('hello') = %s\n", |
||||||
|
new String(value)); |
||||||
|
|
||||||
|
for (int i = 1; i <= 9; ++i) { |
||||||
|
for (int j = 1; j <= 9; ++j) { |
||||||
|
db.put(String.format("%dx%d", i, j).getBytes(), |
||||||
|
String.format("%d", i * j).getBytes()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 1; i <= 9; ++i) { |
||||||
|
for (int j = 1; j <= 9; ++j) { |
||||||
|
System.out.format("%s ", new String(db.get( |
||||||
|
String.format("%dx%d", i, j).getBytes()))); |
||||||
|
} |
||||||
|
System.out.println(""); |
||||||
|
} |
||||||
|
|
||||||
|
value = db.get("1x1".getBytes()); |
||||||
|
assert(value != null); |
||||||
|
value = db.get("world".getBytes()); |
||||||
|
assert(value == null); |
||||||
|
|
||||||
|
byte[] testKey = "asdf".getBytes(); |
||||||
|
byte[] testValue = |
||||||
|
"asdfghjkl;'?><MNBVCXZQWERTYUIOP{+_)(*&^%$#@".getBytes(); |
||||||
|
db.put(testKey, testValue); |
||||||
|
byte[] testResult = db.get(testKey); |
||||||
|
assert(testResult != null); |
||||||
|
assert(Arrays.equals(testValue, testResult)); |
||||||
|
assert(new String(testValue).equals(new String(testResult))); |
||||||
|
|
||||||
|
byte[] insufficientArray = new byte[10]; |
||||||
|
byte[] enoughArray = new byte[50]; |
||||||
|
int len; |
||||||
|
len = db.get(testKey, insufficientArray); |
||||||
|
assert(len > insufficientArray.length); |
||||||
|
len = db.get("asdfjkl;".getBytes(), enoughArray); |
||||||
|
assert(len == RocksDB.NOT_FOUND); |
||||||
|
len = db.get(testKey, enoughArray); |
||||||
|
assert(len == testValue.length); |
||||||
|
try { |
||||||
|
db.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
System.err.println(e); |
||||||
|
} |
||||||
|
} catch (RocksDBException e) { |
||||||
|
System.err.println(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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.
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
import java.lang.*; |
||||||
|
import java.util.*; |
||||||
|
import java.io.Closeable; |
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A RocksDB is a persistent ordered map from keys to values. It is safe for |
||||||
|
* concurrent access from multiple threads without any external synchronization. |
||||||
|
* All methods of this class could potentially throw RocksDBException, which |
||||||
|
* indicates sth wrong at the rocksdb library side and the call failed. |
||||||
|
*/ |
||||||
|
public class RocksDB implements Closeable { |
||||||
|
public static final int NOT_FOUND = -1; |
||||||
|
/** |
||||||
|
* The factory constructor of RocksDB that opens a RocksDB instance given |
||||||
|
* the path to the database. |
||||||
|
* |
||||||
|
* @param path the path to the rocksdb. |
||||||
|
* @param status an out value indicating the status of the Open(). |
||||||
|
* @return a rocksdb instance on success, null if the specified rocksdb can |
||||||
|
* not be opened. |
||||||
|
*/ |
||||||
|
public static RocksDB open(String path) throws RocksDBException { |
||||||
|
RocksDB db = new RocksDB(); |
||||||
|
db.open0(path); |
||||||
|
return db; |
||||||
|
} |
||||||
|
|
||||||
|
@Override public void close() throws IOException { |
||||||
|
if (nativeHandle != 0) { |
||||||
|
close0(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the database entry for "key" to "value". |
||||||
|
* |
||||||
|
* @param key the specified key to be inserted. |
||||||
|
* @param value the value associated with the specified key. |
||||||
|
*/ |
||||||
|
public void put(byte[] key, byte[] value) throws RocksDBException { |
||||||
|
put(key, key.length, value, value.length); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the value associated with the specified key. |
||||||
|
* |
||||||
|
* @param key the key to retrieve the value. |
||||||
|
* @param value the out-value to receive the retrieved value. |
||||||
|
* @return The size of the actual value that matches the specified |
||||||
|
* {@code key} in byte. If the return value is greater than the |
||||||
|
* length of {@code value}, then it indicates that the size of the |
||||||
|
* input buffer {@code value} is insufficient and partial result will |
||||||
|
* be returned. RocksDB.NOT_FOUND will be returned if the value not |
||||||
|
* found. |
||||||
|
*/ |
||||||
|
public int get(byte[] key, byte[] value) throws RocksDBException { |
||||||
|
return get(key, key.length, value, value.length); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The simplified version of get which returns a new byte array storing |
||||||
|
* the value associated with the specified input key if any. null will be |
||||||
|
* returned if the specified key is not found. |
||||||
|
* |
||||||
|
* @param key the key retrieve the value. |
||||||
|
* @return a byte array storing the value associated with the input key if |
||||||
|
* any. null if it does not find the specified key. |
||||||
|
* |
||||||
|
* @see RocksDBException |
||||||
|
*/ |
||||||
|
public byte[] get(byte[] key) throws RocksDBException { |
||||||
|
return get(key, key.length); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Private constructor. |
||||||
|
*/ |
||||||
|
private RocksDB() { |
||||||
|
nativeHandle = -1; |
||||||
|
} |
||||||
|
|
||||||
|
// native methods
|
||||||
|
private native void open0(String path) throws RocksDBException; |
||||||
|
private native void put( |
||||||
|
byte[] key, int keyLen, |
||||||
|
byte[] value, int valueLen) throws RocksDBException; |
||||||
|
private native int get( |
||||||
|
byte[] key, int keyLen, |
||||||
|
byte[] value, int valueLen) throws RocksDBException; |
||||||
|
private native byte[] get( |
||||||
|
byte[] key, int keyLen) throws RocksDBException; |
||||||
|
private native void close0(); |
||||||
|
|
||||||
|
private long nativeHandle; |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package org.rocksdb; |
||||||
|
|
||||||
|
import java.lang.*; |
||||||
|
import java.util.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* A RocksDBException encapsulates the error of an operation. This exception |
||||||
|
* type is used to describe an internal error from the c++ rocksdb library. |
||||||
|
*/ |
||||||
|
public class RocksDBException extends Exception { |
||||||
|
/** |
||||||
|
* The private construct used by a set of public static factory method. |
||||||
|
* |
||||||
|
* @param msg the specified error message. |
||||||
|
*/ |
||||||
|
public RocksDBException(String msg) { |
||||||
|
super(msg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
// Copyright (c) 2014, 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.
|
||||||
|
|
||||||
|
// This file is designed for caching those frequently used IDs and provide
|
||||||
|
// efficient portal (i.e, a set of static functions) to access java code
|
||||||
|
// from c++.
|
||||||
|
|
||||||
|
#ifndef JAVA_ROCKSJNI_PORTAL_H_ |
||||||
|
#define JAVA_ROCKSJNI_PORTAL_H_ |
||||||
|
|
||||||
|
#include <jni.h> |
||||||
|
#include "rocksdb/db.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
// The portal class for org.rocksdb.RocksDB
|
||||||
|
class RocksDBJni { |
||||||
|
public: |
||||||
|
// Get the java class id of org.rocksdb.RocksDB.
|
||||||
|
static jclass getJClass(JNIEnv* env) { |
||||||
|
static jclass jclazz = env->FindClass("org/rocksdb/RocksDB"); |
||||||
|
assert(jclazz != nullptr); |
||||||
|
return jclazz; |
||||||
|
} |
||||||
|
|
||||||
|
// Get the field id of the member variable of org.rocksdb.RocksDB
|
||||||
|
// that stores the pointer to rocksdb::DB.
|
||||||
|
static jfieldID getHandleFieldID(JNIEnv* env) { |
||||||
|
static jfieldID fid = env->GetFieldID( |
||||||
|
getJClass(env), "nativeHandle", "J"); |
||||||
|
assert(fid != nullptr); |
||||||
|
return fid; |
||||||
|
} |
||||||
|
|
||||||
|
// Get the pointer to rocksdb::DB of the specified org.rocksdb.RocksDB.
|
||||||
|
static rocksdb::DB* getHandle(JNIEnv* env, jobject jdb) { |
||||||
|
return reinterpret_cast<rocksdb::DB*>( |
||||||
|
env->GetLongField(jdb, getHandleFieldID(env))); |
||||||
|
} |
||||||
|
|
||||||
|
// Pass the rocksdb::DB pointer to the java side.
|
||||||
|
static void setHandle(JNIEnv* env, jobject jdb, rocksdb::DB* db) { |
||||||
|
env->SetLongField( |
||||||
|
jdb, getHandleFieldID(env), |
||||||
|
reinterpret_cast<jlong>(db)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// The portal class for org.rocksdb.RocksDBException
|
||||||
|
class RocksDBExceptionJni { |
||||||
|
public: |
||||||
|
// Get the jclass of org.rocksdb.RocksDBException
|
||||||
|
static jclass getJClass(JNIEnv* env) { |
||||||
|
static jclass jclazz = env->FindClass("org/rocksdb/RocksDBException"); |
||||||
|
assert(jclazz != nullptr); |
||||||
|
return jclazz; |
||||||
|
} |
||||||
|
|
||||||
|
// Create and throw a java exception by converting the input
|
||||||
|
// Status to an RocksDBException.
|
||||||
|
//
|
||||||
|
// In case s.ok() is true, then this function will not throw any
|
||||||
|
// exception.
|
||||||
|
static void ThrowNew(JNIEnv* env, Status s) { |
||||||
|
if (s.ok()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
jstring msg = env->NewStringUTF(s.ToString().c_str()); |
||||||
|
// get the constructor id of org.rocksdb.RocksDBException
|
||||||
|
static jmethodID mid = env->GetMethodID( |
||||||
|
getJClass(env), "<init>", "(Ljava/lang/String;)V"); |
||||||
|
assert(mid != nullptr); |
||||||
|
|
||||||
|
env->Throw((jthrowable)env->NewObject(getJClass(env), mid, msg)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
#endif // JAVA_ROCKSJNI_PORTAL_H_
|
@ -0,0 +1,185 @@ |
|||||||
|
// Copyright (c) 2014, 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.
|
||||||
|
//
|
||||||
|
// This file implements the "bridge" between Java and C++ and enables
|
||||||
|
// calling c++ rocksdb::DB methods from Java side.
|
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <jni.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "include/org_rocksdb_RocksDB.h" |
||||||
|
#include "rocksjni/portal.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB |
||||||
|
* Method: open0 |
||||||
|
* Signature: (Ljava/lang/String;)V |
||||||
|
*/ |
||||||
|
void Java_org_rocksdb_RocksDB_open0( |
||||||
|
JNIEnv* env, jobject java_db, jstring jdb_path) { |
||||||
|
rocksdb::DB* db; |
||||||
|
rocksdb::Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
|
||||||
|
jboolean isCopy = false; |
||||||
|
const char* db_path = env->GetStringUTFChars(jdb_path, &isCopy); |
||||||
|
rocksdb::Status s = rocksdb::DB::Open(options, db_path, &db); |
||||||
|
env->ReleaseStringUTFChars(jdb_path, db_path); |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
rocksdb::RocksDBJni::setHandle(env, java_db, db); |
||||||
|
return; |
||||||
|
} |
||||||
|
rocksdb::RocksDBExceptionJni::ThrowNew(env, s); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB |
||||||
|
* Method: put |
||||||
|
* Signature: ([BI[BI)V |
||||||
|
*/ |
||||||
|
void Java_org_rocksdb_RocksDB_put( |
||||||
|
JNIEnv* env, jobject jdb, |
||||||
|
jbyteArray jkey, jint jkey_len, |
||||||
|
jbyteArray jvalue, jint jvalue_len) { |
||||||
|
rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); |
||||||
|
|
||||||
|
jboolean isCopy; |
||||||
|
jbyte* key = env->GetByteArrayElements(jkey, &isCopy); |
||||||
|
jbyte* value = env->GetByteArrayElements(jvalue, &isCopy); |
||||||
|
rocksdb::Slice key_slice( |
||||||
|
reinterpret_cast<char*>(key), jkey_len); |
||||||
|
rocksdb::Slice value_slice( |
||||||
|
reinterpret_cast<char*>(value), jvalue_len); |
||||||
|
|
||||||
|
rocksdb::Status s = db->Put( |
||||||
|
rocksdb::WriteOptions(), key_slice, value_slice); |
||||||
|
|
||||||
|
// trigger java unref on key and value.
|
||||||
|
// by passing JNI_ABORT, it will simply release the reference without
|
||||||
|
// copying the result back to the java byte array.
|
||||||
|
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); |
||||||
|
env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
rocksdb::RocksDBExceptionJni::ThrowNew(env, s); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB |
||||||
|
* Method: get |
||||||
|
* Signature: ([BI)[B |
||||||
|
*/ |
||||||
|
jbyteArray Java_org_rocksdb_RocksDB_get___3BI( |
||||||
|
JNIEnv* env, jobject jdb, jbyteArray jkey, jint jkey_len) { |
||||||
|
rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); |
||||||
|
|
||||||
|
jboolean isCopy; |
||||||
|
jbyte* key = env->GetByteArrayElements(jkey, &isCopy); |
||||||
|
rocksdb::Slice key_slice( |
||||||
|
reinterpret_cast<char*>(key), jkey_len); |
||||||
|
|
||||||
|
std::string value; |
||||||
|
rocksdb::Status s = db->Get( |
||||||
|
rocksdb::ReadOptions(), |
||||||
|
key_slice, &value); |
||||||
|
|
||||||
|
// trigger java unref on key.
|
||||||
|
// by passing JNI_ABORT, it will simply release the reference without
|
||||||
|
// copying the result back to the java byte array.
|
||||||
|
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); |
||||||
|
|
||||||
|
if (s.IsNotFound()) { |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
jbyteArray jvalue = env->NewByteArray(value.size()); |
||||||
|
env->SetByteArrayRegion( |
||||||
|
jvalue, 0, value.size(), |
||||||
|
reinterpret_cast<const jbyte*>(value.c_str())); |
||||||
|
return jvalue; |
||||||
|
} |
||||||
|
rocksdb::RocksDBExceptionJni::ThrowNew(env, s); |
||||||
|
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB |
||||||
|
* Method: get |
||||||
|
* Signature: ([BI[BI)I |
||||||
|
*/ |
||||||
|
jint Java_org_rocksdb_RocksDB_get___3BI_3BI( |
||||||
|
JNIEnv* env, jobject jdb, |
||||||
|
jbyteArray jkey, jint jkey_len, |
||||||
|
jbyteArray jvalue, jint jvalue_len) { |
||||||
|
static const int kNotFound = -1; |
||||||
|
static const int kStatusError = -2; |
||||||
|
|
||||||
|
rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, jdb); |
||||||
|
|
||||||
|
jboolean isCopy; |
||||||
|
jbyte* key = env->GetByteArrayElements(jkey, &isCopy); |
||||||
|
jbyte* value = env->GetByteArrayElements(jvalue, &isCopy); |
||||||
|
rocksdb::Slice key_slice( |
||||||
|
reinterpret_cast<char*>(key), jkey_len); |
||||||
|
|
||||||
|
// TODO(yhchiang): we might save one memory allocation here by adding
|
||||||
|
// a DB::Get() function which takes preallocated jbyte* as input.
|
||||||
|
std::string cvalue; |
||||||
|
rocksdb::Status s = db->Get( |
||||||
|
rocksdb::ReadOptions(), key_slice, &cvalue); |
||||||
|
|
||||||
|
// trigger java unref on key.
|
||||||
|
// by passing JNI_ABORT, it will simply release the reference without
|
||||||
|
// copying the result back to the java byte array.
|
||||||
|
env->ReleaseByteArrayElements(jkey, key, JNI_ABORT); |
||||||
|
|
||||||
|
if (s.IsNotFound()) { |
||||||
|
env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT); |
||||||
|
return kNotFound; |
||||||
|
} else if (!s.ok()) { |
||||||
|
// Here since we are throwing a Java exception from c++ side.
|
||||||
|
// As a result, c++ does not know calling this function will in fact
|
||||||
|
// throwing an exception. As a result, the execution flow will
|
||||||
|
// not stop here, and codes after this throw will still be
|
||||||
|
// executed.
|
||||||
|
rocksdb::RocksDBExceptionJni::ThrowNew(env, s); |
||||||
|
|
||||||
|
// Return a dummy const value to avoid compilation error, although
|
||||||
|
// java side might not have a chance to get the return value :)
|
||||||
|
return kStatusError; |
||||||
|
} |
||||||
|
|
||||||
|
int cvalue_len = static_cast<int>(cvalue.size()); |
||||||
|
int length = std::min(jvalue_len, cvalue_len); |
||||||
|
|
||||||
|
memcpy(value, cvalue.c_str(), length); |
||||||
|
env->ReleaseByteArrayElements(jvalue, value, JNI_COMMIT); |
||||||
|
if (cvalue_len > length) { |
||||||
|
return static_cast<jint>(cvalue_len); |
||||||
|
} |
||||||
|
return length; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: org_rocksdb_RocksDB |
||||||
|
* Method: close0 |
||||||
|
* Signature: ()V |
||||||
|
*/ |
||||||
|
void Java_org_rocksdb_RocksDB_close0( |
||||||
|
JNIEnv* env, jobject java_db) { |
||||||
|
rocksdb::DB* db = rocksdb::RocksDBJni::getHandle(env, java_db); |
||||||
|
delete db; |
||||||
|
db = nullptr; |
||||||
|
|
||||||
|
rocksdb::RocksDBJni::setHandle(env, java_db, db); |
||||||
|
} |
@ -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
|
@ -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
|
@ -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