YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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