/Users/deen/code/yugabyte-db/src/yb/rocksdb/utilities/geodb/geodb_impl.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under the BSD-style license found in the |
3 | | // LICENSE file in the root directory of this source tree. An additional grant |
4 | | // of patent rights can be found in the PATENTS file in the same directory. |
5 | | // |
6 | | // The following only applies to changes made to this file as part of YugaByte development. |
7 | | // |
8 | | // Portions Copyright (c) YugaByte, Inc. |
9 | | // |
10 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
11 | | // in compliance with the License. You may obtain a copy of the License at |
12 | | // |
13 | | // http://www.apache.org/licenses/LICENSE-2.0 |
14 | | // |
15 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
16 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
17 | | // or implied. See the License for the specific language governing permissions and limitations |
18 | | // under the License. |
19 | | // |
20 | | #ifndef ROCKSDB_LITE |
21 | | |
22 | | #include "yb/rocksdb/utilities/geodb/geodb_impl.h" |
23 | | |
24 | | #ifndef __STDC_FORMAT_MACROS |
25 | | #define __STDC_FORMAT_MACROS |
26 | | #endif |
27 | | |
28 | | #include <vector> |
29 | | #include <map> |
30 | | #include <string> |
31 | | #include <limits> |
32 | | #include "yb/rocksdb/db/filename.h" |
33 | | #include "yb/rocksdb/util/coding.h" |
34 | | #include "yb/util/string_util.h" |
35 | | |
36 | | |
37 | | // |
38 | | // There are two types of keys. The first type of key-values |
39 | | // maps a geo location to the set of object ids and their values. |
40 | | // Table 1 |
41 | | // key : p + : + $quadkey + : + $id + |
42 | | // : + $latitude + : + $longitude |
43 | | // value : value of the object |
44 | | // This table can be used to find all objects that reside near |
45 | | // a specified geolocation. |
46 | | // |
47 | | // Table 2 |
48 | | // key : 'k' + : + $id |
49 | | // value: $quadkey |
50 | | |
51 | | namespace rocksdb { |
52 | | |
53 | | const double GeoDBImpl::PI = 3.141592653589793; |
54 | | const double GeoDBImpl::EarthRadius = 6378137; |
55 | | const double GeoDBImpl::MinLatitude = -85.05112878; |
56 | | const double GeoDBImpl::MaxLatitude = 85.05112878; |
57 | | const double GeoDBImpl::MinLongitude = -180; |
58 | | const double GeoDBImpl::MaxLongitude = 180; |
59 | | |
60 | | GeoDBImpl::GeoDBImpl(DB* db, const GeoDBOptions& options) : |
61 | 2 | GeoDB(db, options), db_(db), options_(options) { |
62 | 2 | } |
63 | | |
64 | 2 | GeoDBImpl::~GeoDBImpl() { |
65 | 2 | } |
66 | | |
67 | 3 | Status GeoDBImpl::Insert(const GeoObject& obj) { |
68 | 3 | WriteBatch batch; |
69 | | |
70 | | // It is possible that this id is already associated with |
71 | | // with a different position. We first have to remove that |
72 | | // association before we can insert the new one. |
73 | | |
74 | | // remove existing object, if it exists |
75 | 3 | GeoObject old; |
76 | 3 | Status status = GetById(obj.id, &old); |
77 | 3 | if (status.ok()) { |
78 | 0 | assert(obj.id.compare(old.id) == 0); |
79 | 0 | std::string quadkey = PositionToQuad(old.position, Detail); |
80 | 0 | std::string key1 = MakeKey1(old.position, old.id, quadkey); |
81 | 0 | std::string key2 = MakeKey2(old.id); |
82 | 0 | batch.Delete(Slice(key1)); |
83 | 0 | batch.Delete(Slice(key2)); |
84 | 3 | } else if (status.IsNotFound()) { |
85 | | // What if another thread is trying to insert the same ID concurrently? |
86 | 0 | } else { |
87 | 0 | return status; |
88 | 0 | } |
89 | | |
90 | | // insert new object |
91 | 3 | std::string quadkey = PositionToQuad(obj.position, Detail); |
92 | 3 | std::string key1 = MakeKey1(obj.position, obj.id, quadkey); |
93 | 3 | std::string key2 = MakeKey2(obj.id); |
94 | 3 | batch.Put(Slice(key1), Slice(obj.value)); |
95 | 3 | batch.Put(Slice(key2), Slice(quadkey)); |
96 | 3 | return db_->Write(woptions_, &batch); |
97 | 3 | } |
98 | | |
99 | | Status GeoDBImpl::GetByPosition(const GeoPosition& pos, |
100 | | const Slice& id, |
101 | 3 | std::string* value) { |
102 | 3 | std::string quadkey = PositionToQuad(pos, Detail); |
103 | 3 | std::string key1 = MakeKey1(pos, id, quadkey); |
104 | 3 | return db_->Get(roptions_, Slice(key1), value); |
105 | 3 | } |
106 | | |
107 | 7 | Status GeoDBImpl::GetById(const Slice& id, GeoObject* object) { |
108 | 7 | Status status; |
109 | 7 | std::string quadkey; |
110 | | |
111 | | // create an iterator so that we can get a consistent picture |
112 | | // of the database. |
113 | 7 | Iterator* iter = db_->NewIterator(roptions_); |
114 | | |
115 | | // create key for table2 |
116 | 7 | std::string kt = MakeKey2(id); |
117 | 7 | Slice key2(kt); |
118 | | |
119 | 7 | iter->Seek(key2); |
120 | 7 | if (iter->Valid() && iter->status().ok()) { |
121 | 5 | if (iter->key().compare(key2) == 0) { |
122 | 3 | quadkey = iter->value().ToString(); |
123 | 3 | } |
124 | 5 | } |
125 | 7 | if (quadkey.size() == 0) { |
126 | 4 | delete iter; |
127 | 4 | return STATUS(NotFound, key2); |
128 | 4 | } |
129 | | |
130 | | // |
131 | | // Seek to the quadkey + id prefix |
132 | | // |
133 | 3 | std::string prefix = MakeKey1Prefix(quadkey, id); |
134 | 3 | iter->Seek(Slice(prefix)); |
135 | 3 | assert(iter->Valid()); |
136 | 3 | if (!iter->Valid() || !iter->status().ok()) { |
137 | 0 | delete iter; |
138 | 0 | return STATUS(NotFound, ""); |
139 | 0 | } |
140 | | |
141 | | // split the key into p + quadkey + id + lat + lon |
142 | 3 | Slice key = iter->key(); |
143 | 3 | std::vector<std::string> parts = StringSplit(key.ToString(), ':'); |
144 | 3 | assert(parts.size() == 5); |
145 | 3 | assert(parts[0] == "p"); |
146 | 3 | assert(parts[1] == quadkey); |
147 | 3 | assert(parts[2] == id); |
148 | | |
149 | | // fill up output parameters |
150 | 3 | object->position.latitude = atof(parts[3].c_str()); |
151 | 3 | object->position.longitude = atof(parts[4].c_str()); |
152 | 3 | object->id = id.ToString(); // this is redundant |
153 | 3 | object->value = iter->value().ToString(); |
154 | 3 | delete iter; |
155 | 3 | return Status::OK(); |
156 | 3 | } |
157 | | |
158 | | |
159 | 1 | Status GeoDBImpl::Remove(const Slice& id) { |
160 | | // Read the object from the database |
161 | 1 | GeoObject obj; |
162 | 1 | Status status = GetById(id, &obj); |
163 | 1 | if (!status.ok()) { |
164 | 0 | return status; |
165 | 0 | } |
166 | | |
167 | | // remove the object by atomically deleting it from both tables |
168 | 1 | std::string quadkey = PositionToQuad(obj.position, Detail); |
169 | 1 | std::string key1 = MakeKey1(obj.position, obj.id, quadkey); |
170 | 1 | std::string key2 = MakeKey2(obj.id); |
171 | 1 | WriteBatch batch; |
172 | 1 | batch.Delete(Slice(key1)); |
173 | 1 | batch.Delete(Slice(key2)); |
174 | 1 | return db_->Write(woptions_, &batch); |
175 | 1 | } |
176 | | |
177 | | class GeoIteratorImpl : public GeoIterator { |
178 | | private: |
179 | | std::vector<GeoObject> values_; |
180 | | std::vector<GeoObject>::iterator iter_; |
181 | | public: |
182 | | explicit GeoIteratorImpl(std::vector<GeoObject> values) |
183 | 2 | : values_(std::move(values)) { |
184 | 2 | iter_ = values_.begin(); |
185 | 2 | } |
186 | | void Next() override; |
187 | | bool Valid() const override; |
188 | | const GeoObject& geo_object() override; |
189 | | Status status() const override; |
190 | | }; |
191 | | |
192 | | class GeoErrorIterator : public GeoIterator { |
193 | | private: |
194 | | Status status_; |
195 | | public: |
196 | 0 | explicit GeoErrorIterator(Status s) : status_(s) {} |
197 | 0 | void Next() override {}; |
198 | 0 | bool Valid() const override { return false; } |
199 | 0 | const GeoObject& geo_object() override { |
200 | 0 | GeoObject* g = new GeoObject(); |
201 | 0 | return *g; |
202 | 0 | } |
203 | 0 | Status status() const override { return status_; } |
204 | | }; |
205 | | |
206 | 1 | void GeoIteratorImpl::Next() { |
207 | 1 | assert(Valid()); |
208 | 1 | iter_++; |
209 | 1 | } |
210 | | |
211 | 5 | bool GeoIteratorImpl::Valid() const { |
212 | 5 | return iter_ != values_.end(); |
213 | 5 | } |
214 | | |
215 | 1 | const GeoObject& GeoIteratorImpl::geo_object() { |
216 | 1 | assert(Valid()); |
217 | 1 | return *iter_; |
218 | 1 | } |
219 | | |
220 | 0 | Status GeoIteratorImpl::status() const { |
221 | 0 | return Status::OK(); |
222 | 0 | } |
223 | | |
224 | | GeoIterator* GeoDBImpl::SearchRadial(const GeoPosition& pos, |
225 | | double radius, |
226 | 2 | int number_of_values) { |
227 | 2 | std::vector<GeoObject> values; |
228 | | |
229 | | // Gather all bounding quadkeys |
230 | 2 | std::vector<std::string> qids; |
231 | 2 | Status s = searchQuadIds(pos, radius, &qids); |
232 | 2 | if (!s.ok()) { |
233 | 0 | return new GeoErrorIterator(s); |
234 | 0 | } |
235 | | |
236 | | // create an iterator |
237 | 2 | Iterator* iter = db_->NewIterator(ReadOptions()); |
238 | | |
239 | | // Process each prospective quadkey |
240 | 8 | for (std::string qid : qids) { |
241 | | // The user is interested in only these many objects. |
242 | 8 | if (number_of_values == 0) { |
243 | 0 | break; |
244 | 0 | } |
245 | | |
246 | | // convert quadkey to db key prefix |
247 | 8 | std::string dbkey = MakeQuadKeyPrefix(qid); |
248 | | |
249 | 8 | for (iter->Seek(dbkey); |
250 | 9 | number_of_values > 0 && iter->Valid() && iter->status().ok(); |
251 | 8 | iter->Next()) { |
252 | | // split the key into p + quadkey + id + lat + lon |
253 | 8 | Slice key = iter->key(); |
254 | 8 | std::vector<std::string> parts = StringSplit(key.ToString(), ':'); |
255 | 8 | assert(parts.size() == 5); |
256 | 8 | assert(parts[0] == "p"); |
257 | 8 | std::string* quadkey = &parts[1]; |
258 | | |
259 | | // If the key we are looking for is a prefix of the key |
260 | | // we found from the database, then this is one of the keys |
261 | | // we are looking for. |
262 | 8 | auto res = std::mismatch(qid.begin(), qid.end(), quadkey->begin()); |
263 | 8 | if (res.first == qid.end()) { |
264 | 1 | GeoPosition obj_pos(atof(parts[3].c_str()), atof(parts[4].c_str())); |
265 | 1 | GeoObject obj(obj_pos, parts[4], iter->value().ToString()); |
266 | 1 | values.push_back(obj); |
267 | 1 | number_of_values--; |
268 | 7 | } else { |
269 | 7 | break; |
270 | 7 | } |
271 | 8 | } |
272 | 8 | } |
273 | 2 | delete iter; |
274 | 2 | return new GeoIteratorImpl(std::move(values)); |
275 | 2 | } |
276 | | |
277 | | std::string GeoDBImpl::MakeKey1(const GeoPosition& pos, Slice id, |
278 | 7 | std::string quadkey) { |
279 | 7 | std::string lat = rocksdb::ToString(pos.latitude); |
280 | 7 | std::string lon = rocksdb::ToString(pos.longitude); |
281 | 7 | std::string key = "p:"; |
282 | 7 | key.reserve(5 + quadkey.size() + id.size() + lat.size() + lon.size()); |
283 | 7 | key.append(quadkey); |
284 | 7 | key.append(":"); |
285 | 7 | key.append(id.ToString()); |
286 | 7 | key.append(":"); |
287 | 7 | key.append(lat); |
288 | 7 | key.append(":"); |
289 | 7 | key.append(lon); |
290 | 7 | return key; |
291 | 7 | } |
292 | | |
293 | 11 | std::string GeoDBImpl::MakeKey2(Slice id) { |
294 | 11 | std::string key = "k:"; |
295 | 11 | key.append(id.ToString()); |
296 | 11 | return key; |
297 | 11 | } |
298 | | |
299 | | std::string GeoDBImpl::MakeKey1Prefix(std::string quadkey, |
300 | 3 | Slice id) { |
301 | 3 | std::string key = "p:"; |
302 | 3 | key.reserve(3 + quadkey.size() + id.size()); |
303 | 3 | key.append(quadkey); |
304 | 3 | key.append(":"); |
305 | 3 | key.append(id.ToString()); |
306 | 3 | return key; |
307 | 3 | } |
308 | | |
309 | 8 | std::string GeoDBImpl::MakeQuadKeyPrefix(std::string quadkey) { |
310 | 8 | std::string key = "p:"; |
311 | 8 | key.append(quadkey); |
312 | 8 | return key; |
313 | 8 | } |
314 | | |
315 | | // convert degrees to radians |
316 | 4 | double GeoDBImpl::radians(double x) { |
317 | 4 | return (x * PI) / 180; |
318 | 4 | } |
319 | | |
320 | | // convert radians to degrees |
321 | 8 | double GeoDBImpl::degrees(double x) { |
322 | 8 | return (x * 180) / PI; |
323 | 8 | } |
324 | | |
325 | | // convert a gps location to quad coordinate |
326 | | std::string GeoDBImpl::PositionToQuad(const GeoPosition& pos, |
327 | 15 | int levelOfDetail) { |
328 | 15 | Pixel p = PositionToPixel(pos, levelOfDetail); |
329 | 15 | Tile tile = PixelToTile(p); |
330 | 15 | return TileToQuadKey(tile, levelOfDetail); |
331 | 15 | } |
332 | | |
333 | | GeoPosition GeoDBImpl::displaceLatLon(double lat, double lon, |
334 | 4 | double deltay, double deltax) { |
335 | 4 | double dLat = deltay / EarthRadius; |
336 | 4 | double dLon = deltax / (EarthRadius * cos(radians(lat))); |
337 | 4 | return GeoPosition(lat + degrees(dLat), |
338 | 4 | lon + degrees(dLon)); |
339 | 4 | } |
340 | | |
341 | | // |
342 | | // Return the distance between two positions on the earth |
343 | | // |
344 | | double GeoDBImpl::distance(double lat1, double lon1, |
345 | 0 | double lat2, double lon2) { |
346 | 0 | double lon = radians(lon2 - lon1); |
347 | 0 | double lat = radians(lat2 - lat1); |
348 | |
|
349 | 0 | double a = (sin(lat / 2) * sin(lat / 2)) + |
350 | 0 | cos(radians(lat1)) * cos(radians(lat2)) * |
351 | 0 | (sin(lon / 2) * sin(lon / 2)); |
352 | 0 | double angle = 2 * atan2(sqrt(a), sqrt(1 - a)); |
353 | 0 | return angle * EarthRadius; |
354 | 0 | } |
355 | | |
356 | | // |
357 | | // Returns all the quadkeys inside the search range |
358 | | // |
359 | | Status GeoDBImpl::searchQuadIds(const GeoPosition& position, |
360 | | double radius, |
361 | 2 | std::vector<std::string>* quadKeys) { |
362 | | // get the outline of the search square |
363 | 2 | GeoPosition topLeftPos = boundingTopLeft(position, radius); |
364 | 2 | GeoPosition bottomRightPos = boundingBottomRight(position, radius); |
365 | | |
366 | 2 | Pixel topLeft = PositionToPixel(topLeftPos, Detail); |
367 | 2 | Pixel bottomRight = PositionToPixel(bottomRightPos, Detail); |
368 | | |
369 | | // how many level of details to look for |
370 | 2 | int numberOfTilesAtMaxDepth = static_cast<int>(std::floor((bottomRight.x - topLeft.x) / 256)); |
371 | 2 | int zoomLevelsToRise = static_cast<int>( |
372 | 2 | std::floor(std::log(numberOfTilesAtMaxDepth) / std::log(2))); |
373 | 2 | zoomLevelsToRise++; |
374 | 2 | int levels = std::max(0, Detail - zoomLevelsToRise); |
375 | | |
376 | 2 | quadKeys->push_back(PositionToQuad(GeoPosition(topLeftPos.latitude, |
377 | 2 | topLeftPos.longitude), |
378 | 2 | levels)); |
379 | 2 | quadKeys->push_back(PositionToQuad(GeoPosition(topLeftPos.latitude, |
380 | 2 | bottomRightPos.longitude), |
381 | 2 | levels)); |
382 | 2 | quadKeys->push_back(PositionToQuad(GeoPosition(bottomRightPos.latitude, |
383 | 2 | topLeftPos.longitude), |
384 | 2 | levels)); |
385 | 2 | quadKeys->push_back(PositionToQuad(GeoPosition(bottomRightPos.latitude, |
386 | 2 | bottomRightPos.longitude), |
387 | 2 | levels)); |
388 | 2 | return Status::OK(); |
389 | 2 | } |
390 | | |
391 | | // Determines the ground resolution (in meters per pixel) at a specified |
392 | | // latitude and level of detail. |
393 | | // Latitude (in degrees) at which to measure the ground resolution. |
394 | | // Level of detail, from 1 (lowest detail) to 23 (highest detail). |
395 | | // Returns the ground resolution, in meters per pixel. |
396 | 0 | double GeoDBImpl::GroundResolution(double latitude, int levelOfDetail) { |
397 | 0 | latitude = clip(latitude, MinLatitude, MaxLatitude); |
398 | 0 | return cos(latitude * PI / 180) * 2 * PI * EarthRadius / |
399 | 0 | MapSize(levelOfDetail); |
400 | 0 | } |
401 | | |
402 | | // Converts a point from latitude/longitude WGS-84 coordinates (in degrees) |
403 | | // into pixel XY coordinates at a specified level of detail. |
404 | | GeoDBImpl::Pixel GeoDBImpl::PositionToPixel(const GeoPosition& pos, |
405 | 19 | int levelOfDetail) { |
406 | 19 | double latitude = clip(pos.latitude, MinLatitude, MaxLatitude); |
407 | 19 | double x = (pos.longitude + 180) / 360; |
408 | 19 | double sinLatitude = sin(latitude * PI / 180); |
409 | 19 | double y = 0.5 - std::log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * PI); |
410 | 19 | double mapSize = MapSize(levelOfDetail); |
411 | 19 | double X = std::floor(clip(x * mapSize + 0.5, 0, mapSize - 1)); |
412 | 19 | double Y = std::floor(clip(y * mapSize + 0.5, 0, mapSize - 1)); |
413 | 19 | return Pixel((unsigned int)X, (unsigned int)Y); |
414 | 19 | } |
415 | | |
416 | 0 | GeoPosition GeoDBImpl::PixelToPosition(const Pixel& pixel, int levelOfDetail) { |
417 | 0 | double mapSize = MapSize(levelOfDetail); |
418 | 0 | double x = (clip(pixel.x, 0, mapSize - 1) / mapSize) - 0.5; |
419 | 0 | double y = 0.5 - (clip(pixel.y, 0, mapSize - 1) / mapSize); |
420 | 0 | double latitude = 90 - 360 * atan(exp(-y * 2 * PI)) / PI; |
421 | 0 | double longitude = 360 * x; |
422 | 0 | return GeoPosition(latitude, longitude); |
423 | 0 | } |
424 | | |
425 | | // Converts a Pixel to a Tile |
426 | 15 | GeoDBImpl::Tile GeoDBImpl::PixelToTile(const Pixel& pixel) { |
427 | 15 | unsigned int tileX = static_cast<unsigned int>(std::floor(pixel.x / 256)); |
428 | 15 | unsigned int tileY = static_cast<unsigned int>(std::floor(pixel.y / 256)); |
429 | 15 | return Tile(tileX, tileY); |
430 | 15 | } |
431 | | |
432 | 0 | GeoDBImpl::Pixel GeoDBImpl::TileToPixel(const Tile& tile) { |
433 | 0 | unsigned int pixelX = tile.x * 256; |
434 | 0 | unsigned int pixelY = tile.y * 256; |
435 | 0 | return Pixel(pixelX, pixelY); |
436 | 0 | } |
437 | | |
438 | | // Convert a Tile to a quadkey |
439 | 15 | std::string GeoDBImpl::TileToQuadKey(const Tile& tile, int levelOfDetail) { |
440 | 15 | std::stringstream quadKey; |
441 | 288 | for (int i = levelOfDetail; i > 0; i--) { |
442 | 273 | char digit = '0'; |
443 | 273 | int mask = 1 << (i - 1); |
444 | 273 | if ((tile.x & mask) != 0) { |
445 | 150 | digit++; |
446 | 150 | } |
447 | 273 | if ((tile.y & mask) != 0) { |
448 | 67 | digit++; |
449 | 67 | digit++; |
450 | 67 | } |
451 | 273 | quadKey << digit; |
452 | 273 | } |
453 | 15 | return quadKey.str(); |
454 | 15 | } |
455 | | |
456 | | // |
457 | | // Convert a quadkey to a tile and its level of detail |
458 | | // |
459 | | void GeoDBImpl::QuadKeyToTile(std::string quadkey, Tile* tile, |
460 | 0 | int* levelOfDetail) { |
461 | 0 | tile->x = tile->y = 0; |
462 | 0 | *levelOfDetail = static_cast<int>(quadkey.size()); |
463 | 0 | const char* key = reinterpret_cast<const char*>(quadkey.c_str()); |
464 | 0 | for (int i = *levelOfDetail; i > 0; i--) { |
465 | 0 | int mask = 1 << (i - 1); |
466 | 0 | switch (key[*levelOfDetail - i]) { |
467 | 0 | case '0': |
468 | 0 | break; |
469 | | |
470 | 0 | case '1': |
471 | 0 | tile->x |= mask; |
472 | 0 | break; |
473 | | |
474 | 0 | case '2': |
475 | 0 | tile->y |= mask; |
476 | 0 | break; |
477 | | |
478 | 0 | case '3': |
479 | 0 | tile->x |= mask; |
480 | 0 | tile->y |= mask; |
481 | 0 | break; |
482 | | |
483 | 0 | default: |
484 | 0 | std::stringstream msg; |
485 | 0 | msg << quadkey; |
486 | 0 | msg << " Invalid QuadKey."; |
487 | 0 | throw std::runtime_error(msg.str()); |
488 | 0 | } |
489 | 0 | } |
490 | 0 | } |
491 | | } // namespace rocksdb |
492 | | |
493 | | #endif // ROCKSDB_LITE |