1#pragma once
2
3#include <Core/Types.h>
4#include <Core/Defines.h>
5#include <Core/TypeListNumber.h>
6#include <Columns/IColumn.h>
7#include <Columns/ColumnVector.h>
8#include <Common/typeid_cast.h>
9#include <ext/range.h>
10
11/// Warning in boost::geometry during template strategy substitution.
12#pragma GCC diagnostic push
13
14#if !__clang__
15#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
16#endif
17
18#pragma GCC diagnostic ignored "-Wunused-parameter"
19
20#include <boost/geometry.hpp>
21
22#pragma GCC diagnostic pop
23
24#include <boost/geometry/geometries/point_xy.hpp>
25#include <boost/geometry/geometries/polygon.hpp>
26#include <boost/geometry/geometries/multi_polygon.hpp>
27#include <boost/geometry/geometries/segment.hpp>
28#include <boost/geometry/algorithms/comparable_distance.hpp>
29#include <boost/geometry/strategies/cartesian/distance_pythagoras.hpp>
30
31#include <array>
32#include <vector>
33#include <iterator>
34#include <cmath>
35#include <algorithm>
36#include <IO/WriteBufferFromString.h>
37
38namespace DB
39{
40
41namespace ErrorCodes
42{
43extern const int LOGICAL_ERROR;
44}
45
46
47namespace GeoUtils
48{
49
50
51template <typename Polygon>
52UInt64 getPolygonAllocatedBytes(const Polygon & polygon)
53{
54 UInt64 size = 0;
55
56 using RingType = typename Polygon::ring_type;
57 using ValueType = typename RingType::value_type;
58
59 auto sizeOfRing = [](const RingType & ring) { return sizeof(ring) + ring.capacity() * sizeof(ValueType); };
60
61 size += sizeOfRing(polygon.outer());
62
63 const auto & inners = polygon.inners();
64 size += sizeof(inners) + inners.capacity() * sizeof(RingType);
65 for (auto & inner : inners)
66 size += sizeOfRing(inner);
67
68 return size;
69}
70
71template <typename MultiPolygon>
72UInt64 getMultiPolygonAllocatedBytes(const MultiPolygon & multi_polygon)
73{
74 using ValueType = typename MultiPolygon::value_type;
75 UInt64 size = multi_polygon.capacity() * sizeof(ValueType);
76
77 for (const auto & polygon : multi_polygon)
78 size += getPolygonAllocatedBytes(polygon);
79
80 return size;
81}
82
83template <typename CoordinateType = Float32>
84class PointInPolygonWithGrid
85{
86public:
87 using Point = boost::geometry::model::d2::point_xy<CoordinateType>;
88 /// Counter-Clockwise ordering.
89 using Polygon = boost::geometry::model::polygon<Point, false>;
90 using MultiPolygon = boost::geometry::model::multi_polygon<Polygon>;
91 using Box = boost::geometry::model::box<Point>;
92 using Segment = boost::geometry::model::segment<Point>;
93
94 explicit PointInPolygonWithGrid(const Polygon & polygon_, UInt16 grid_size_ = 8)
95 : grid_size(std::max<UInt16>(1, grid_size_)), polygon(polygon_) {}
96
97 void init();
98
99 /// True if bound box is empty.
100 bool hasEmptyBound() const { return has_empty_bound; }
101
102 UInt64 getAllocatedBytes() const;
103
104 inline bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y);
105
106private:
107 enum class CellType
108 {
109 inner,
110 outer,
111 singleLine,
112 pairOfLinesSingleConvexPolygon,
113 pairOfLinesSingleNonConvexPolygons,
114 pairOfLinesDifferentPolygons,
115 complexPolygon
116 };
117
118 struct HalfPlane
119 {
120 /// Line, a * x + b * y + c = 0. Vector (a, b) points inside half-plane.
121 CoordinateType a;
122 CoordinateType b;
123 CoordinateType c;
124
125 /// Take left half-plane.
126 void fill(const Point & from, const Point & to)
127 {
128 a = -(to.y() - from.y());
129 b = to.x() - from.x();
130 c = -from.x() * a - from.y() * b;
131 }
132
133 /// Inner part of the HalfPlane is the left side of initialized vector.
134 bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y) const { return a * x + b * y + c >= 0; }
135 };
136
137 struct Cell
138 {
139 static const int max_stored_half_planes = 2;
140
141 HalfPlane half_planes[max_stored_half_planes];
142 size_t index_of_inner_polygon;
143 CellType type;
144 };
145
146 const UInt16 grid_size;
147
148 Polygon polygon;
149 std::vector<Cell> cells;
150 std::vector<MultiPolygon> polygons;
151
152 CoordinateType cell_width;
153 CoordinateType cell_height;
154
155 CoordinateType x_shift;
156 CoordinateType y_shift;
157 CoordinateType x_scale;
158 CoordinateType y_scale;
159
160 bool has_empty_bound = false;
161 bool was_grid_built = false;
162
163 void buildGrid();
164
165 void calcGridAttributes(Box & box);
166
167 template <typename T>
168 T ALWAYS_INLINE getCellIndex(T row, T col) const { return row * grid_size + col; }
169
170 /// Complex case. Will check intersection directly.
171 inline void addComplexPolygonCell(size_t index, const Box & box);
172
173 /// Empty intersection or intersection == box.
174 inline void addCell(size_t index, const Box & empty_box);
175
176 /// Intersection is a single polygon.
177 inline void addCell(size_t index, const Box & box, const Polygon & intersection);
178
179 /// Intersection is a pair of polygons.
180 inline void addCell(size_t index, const Box & box, const Polygon & first, const Polygon & second);
181
182 /// Returns a list of half-planes were formed from intersection edges without box edges.
183 inline std::vector<HalfPlane> findHalfPlanes(const Box & box, const Polygon & intersection);
184
185 /// Check that polygon.outer() is convex.
186 inline bool isConvex(const Polygon & polygon);
187
188 using Distance = typename boost::geometry::default_comparable_distance_result<Point, Segment>::type;
189
190 /// min(distance(point, edge) : edge in polygon)
191 inline Distance distance(const Point & point, const Polygon & polygon);
192};
193
194template <typename CoordinateType>
195UInt64 PointInPolygonWithGrid<CoordinateType>::getAllocatedBytes() const
196{
197 UInt64 size = sizeof(*this);
198
199 size += cells.capacity() * sizeof(Cell);
200 size += polygons.capacity() * sizeof(MultiPolygon);
201 size += getPolygonAllocatedBytes(polygon);
202
203 for (const auto & elem : polygons)
204 size += getMultiPolygonAllocatedBytes(elem);
205
206 return size;
207}
208
209template <typename CoordinateType>
210void PointInPolygonWithGrid<CoordinateType>::init()
211{
212 if (!was_grid_built)
213 buildGrid();
214
215 was_grid_built = true;
216}
217
218template <typename CoordinateType>
219void PointInPolygonWithGrid<CoordinateType>::calcGridAttributes(
220 PointInPolygonWithGrid<CoordinateType>::Box & box)
221{
222 boost::geometry::envelope(polygon, box);
223
224 const Point & min_corner = box.min_corner();
225 const Point & max_corner = box.max_corner();
226
227#pragma GCC diagnostic push
228#if !__clang__
229#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
230#endif
231
232 cell_width = (max_corner.x() - min_corner.x()) / grid_size;
233 cell_height = (max_corner.y() - min_corner.y()) / grid_size;
234
235#pragma GCC diagnostic pop
236
237 if (cell_width == 0 || cell_height == 0)
238 {
239 has_empty_bound = true;
240 return;
241 }
242
243 x_scale = 1 / cell_width;
244 y_scale = 1 / cell_height;
245 x_shift = -min_corner.x();
246 y_shift = -min_corner.y();
247}
248
249template <typename CoordinateType>
250void PointInPolygonWithGrid<CoordinateType>::buildGrid()
251{
252 Box box;
253 calcGridAttributes(box);
254
255 if (has_empty_bound)
256 return;
257
258 cells.assign(grid_size * grid_size, {});
259
260 const Point & min_corner = box.min_corner();
261
262 for (size_t row = 0; row < grid_size; ++row)
263 {
264#pragma GCC diagnostic push
265#if !__clang__
266#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
267#endif
268 CoordinateType y_min = min_corner.y() + row * cell_height;
269 CoordinateType y_max = min_corner.y() + (row + 1) * cell_height;
270
271 for (size_t col = 0; col < grid_size; ++col)
272 {
273 CoordinateType x_min = min_corner.x() + col * cell_width;
274 CoordinateType x_max = min_corner.x() + (col + 1) * cell_width;
275#pragma GCC diagnostic pop
276 Box cell_box(Point(x_min, y_min), Point(x_max, y_max));
277
278 Polygon cell_bound;
279 boost::geometry::convert(cell_box, cell_bound);
280
281 MultiPolygon intersection;
282 boost::geometry::intersection(polygon, cell_bound, intersection);
283
284 size_t cellIndex = getCellIndex(row, col);
285
286 if (intersection.empty())
287 addCell(cellIndex, cell_box);
288 else if (intersection.size() == 1)
289 addCell(cellIndex, cell_box, intersection.front());
290 else if (intersection.size() == 2)
291 addCell(cellIndex, cell_box, intersection.front(), intersection.back());
292 else
293 addComplexPolygonCell(cellIndex, cell_box);
294 }
295 }
296}
297
298template <typename CoordinateType>
299bool PointInPolygonWithGrid<CoordinateType>::contains(CoordinateType x, CoordinateType y)
300{
301 if (has_empty_bound)
302 return false;
303
304 CoordinateType float_row = (y + y_shift) * y_scale;
305 CoordinateType float_col = (x + x_shift) * x_scale;
306
307 if (float_row < 0 || float_row > grid_size)
308 return false;
309 if (float_col < 0 || float_col > grid_size)
310 return false;
311
312 int row = std::min<int>(float_row, grid_size - 1);
313 int col = std::min<int>(float_col, grid_size - 1);
314
315 int index = getCellIndex(row, col);
316 const auto & cell = cells[index];
317
318 switch (cell.type)
319 {
320 case CellType::inner:
321 return true;
322 case CellType::outer:
323 return false;
324 case CellType::singleLine:
325 return cell.half_planes[0].contains(x, y);
326 case CellType::pairOfLinesSingleConvexPolygon:
327 return cell.half_planes[0].contains(x, y) && cell.half_planes[1].contains(x, y);
328 case CellType::pairOfLinesDifferentPolygons: [[fallthrough]];
329 case CellType::pairOfLinesSingleNonConvexPolygons:
330 return cell.half_planes[0].contains(x, y) || cell.half_planes[1].contains(x, y);
331 case CellType::complexPolygon:
332 return boost::geometry::within(Point(x, y), polygons[cell.index_of_inner_polygon]);
333 }
334
335 __builtin_unreachable();
336}
337
338template <typename CoordinateType>
339typename PointInPolygonWithGrid<CoordinateType>::Distance
340PointInPolygonWithGrid<CoordinateType>::distance(
341 const PointInPolygonWithGrid<CoordinateType>::Point & point,
342 const PointInPolygonWithGrid<CoordinateType>::Polygon & poly)
343{
344 const auto & outer = poly.outer();
345 Distance distance = 0;
346 for (auto i : ext::range(0, outer.size() - 1))
347 {
348 Segment segment(outer[i], outer[i + 1]);
349 Distance current = boost::geometry::comparable_distance(point, segment);
350 distance = i ? std::min(current, distance) : current;
351 }
352 return distance;
353}
354
355template <typename CoordinateType>
356bool PointInPolygonWithGrid<CoordinateType>::isConvex(const PointInPolygonWithGrid<CoordinateType>::Polygon & poly)
357{
358 const auto & outer = poly.outer();
359 /// Segment or point.
360 if (outer.size() < 4)
361 return false;
362
363 auto vecProduct = [](const Point & from, const Point & to) { return from.x() * to.y() - from.y() * to.x(); };
364 auto getVector = [](const Point & from, const Point & to) -> Point
365 {
366 return Point(to.x() - from.x(), to.y() - from.y());
367 };
368
369 Point first = getVector(outer[0], outer[1]);
370 Point prev = first;
371
372 for (auto i : ext::range(1, outer.size() - 1))
373 {
374 Point cur = getVector(outer[i], outer[i + 1]);
375 if (vecProduct(prev, cur) < 0)
376 return false;
377
378 prev = cur;
379 }
380
381 return vecProduct(prev, first) >= 0;
382}
383
384template <typename CoordinateType>
385std::vector<typename PointInPolygonWithGrid<CoordinateType>::HalfPlane>
386PointInPolygonWithGrid<CoordinateType>::findHalfPlanes(
387 const PointInPolygonWithGrid<CoordinateType>::Box & box,
388 const PointInPolygonWithGrid<CoordinateType>::Polygon & intersection)
389{
390 std::vector<HalfPlane> half_planes;
391 Polygon bound;
392 boost::geometry::convert(box, bound);
393 const auto & outer = intersection.outer();
394
395 for (auto i : ext::range(0, outer.size() - 1))
396 {
397 /// Want to detect is intersection edge was formed from box edge or from polygon edge.
398 /// If center of the edge closer to box, than don't form the half-plane.
399 Segment segment(outer[i], outer[i + 1]);
400 Point center((segment.first.x() + segment.second.x()) / 2, (segment.first.y() + segment.second.y()) / 2);
401 if (distance(center, polygon) < distance(center, bound))
402 {
403 half_planes.push_back({});
404 half_planes.back().fill(segment.first, segment.second);
405 }
406 }
407
408 return half_planes;
409}
410
411template <typename CoordinateType>
412void PointInPolygonWithGrid<CoordinateType>::addComplexPolygonCell(
413 size_t index, const PointInPolygonWithGrid<CoordinateType>::Box & box)
414{
415 cells[index].type = CellType::complexPolygon;
416 cells[index].index_of_inner_polygon = polygons.size();
417
418 /// Expand box in (1 + eps_factor) times to eliminate errors for points on box bound.
419 static constexpr CoordinateType eps_factor = 0.01;
420 auto x_eps = eps_factor * (box.max_corner().x() - box.min_corner().x());
421 auto y_eps = eps_factor * (box.max_corner().y() - box.min_corner().y());
422
423 Point min_corner(box.min_corner().x() - x_eps, box.min_corner().y() - y_eps);
424 Point max_corner(box.max_corner().x() + x_eps, box.max_corner().y() + y_eps);
425 Box box_with_eps_bound(min_corner, max_corner);
426
427 Polygon bound;
428 boost::geometry::convert(box_with_eps_bound, bound);
429
430 MultiPolygon intersection;
431 boost::geometry::intersection(polygon, bound, intersection);
432
433 polygons.push_back(intersection);
434}
435
436template <typename CoordinateType>
437void PointInPolygonWithGrid<CoordinateType>::addCell(
438 size_t index, const PointInPolygonWithGrid<CoordinateType>::Box & empty_box)
439{
440 const auto & min_corner = empty_box.min_corner();
441 const auto & max_corner = empty_box.max_corner();
442
443 Point center((min_corner.x() + max_corner.x()) / 2, (min_corner.y() + max_corner.y()) / 2);
444
445 if (boost::geometry::within(center, polygon))
446 cells[index].type = CellType::inner;
447 else
448 cells[index].type = CellType::outer;
449
450}
451
452template <typename CoordinateType>
453void PointInPolygonWithGrid<CoordinateType>::addCell(
454 size_t index,
455 const PointInPolygonWithGrid<CoordinateType>::Box & box,
456 const PointInPolygonWithGrid<CoordinateType>::Polygon & intersection)
457{
458 if (!intersection.inners().empty())
459 addComplexPolygonCell(index, box);
460
461 auto half_planes = findHalfPlanes(box, intersection);
462
463 if (half_planes.empty())
464 addCell(index, box);
465 else if (half_planes.size() == 1)
466 {
467 cells[index].type = CellType::singleLine;
468 cells[index].half_planes[0] = half_planes[0];
469 }
470 else if (half_planes.size() == 2)
471 {
472 cells[index].type = isConvex(intersection) ? CellType::pairOfLinesSingleConvexPolygon
473 : CellType::pairOfLinesSingleNonConvexPolygons;
474 cells[index].half_planes[0] = half_planes[0];
475 cells[index].half_planes[1] = half_planes[1];
476 }
477 else
478 addComplexPolygonCell(index, box);
479}
480
481template <typename CoordinateType>
482void PointInPolygonWithGrid<CoordinateType>::addCell(
483 size_t index,
484 const PointInPolygonWithGrid<CoordinateType>::Box & box,
485 const PointInPolygonWithGrid<CoordinateType>::Polygon & first,
486 const PointInPolygonWithGrid<CoordinateType>::Polygon & second)
487{
488 if (!first.inners().empty() || !second.inners().empty())
489 addComplexPolygonCell(index, box);
490
491 auto first_half_planes = findHalfPlanes(box, first);
492 auto second_half_planes = findHalfPlanes(box, second);
493
494 if (first_half_planes.empty())
495 addCell(index, box, first);
496 else if (second_half_planes.empty())
497 addCell(index, box, second);
498 else if (first_half_planes.size() == 1 && second_half_planes.size() == 1)
499 {
500 cells[index].type = CellType::pairOfLinesDifferentPolygons;
501 cells[index].half_planes[0] = first_half_planes[0];
502 cells[index].half_planes[1] = second_half_planes[0];
503 }
504 else
505 addComplexPolygonCell(index, box);
506}
507
508
509template <typename Strategy, typename CoordinateType = Float32>
510class PointInPolygon
511{
512public:
513 using Point = boost::geometry::model::d2::point_xy<CoordinateType>;
514 /// Counter-Clockwise ordering.
515 using Polygon = boost::geometry::model::polygon<Point, false>;
516 using Box = boost::geometry::model::box<Point>;
517
518 explicit PointInPolygon(const Polygon & polygon_) : polygon(polygon_) {}
519
520 void init()
521 {
522 boost::geometry::envelope(polygon, box);
523
524 const Point & min_corner = box.min_corner();
525 const Point & max_corner = box.max_corner();
526
527 if (min_corner.x() == max_corner.x() || min_corner.y() == max_corner.y())
528 has_empty_bound = true;
529 }
530
531 bool hasEmptyBound() const { return has_empty_bound; }
532
533 inline bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y)
534 {
535 Point point(x, y);
536
537 if (!boost::geometry::within(point, box))
538 return false;
539
540 return boost::geometry::covered_by(point, polygon, strategy);
541 }
542
543 UInt64 getAllocatedBytes() const { return sizeof(*this); }
544
545private:
546 const Polygon & polygon;
547 Box box;
548 bool has_empty_bound = false;
549 Strategy strategy;
550};
551
552
553/// Algorithms.
554
555template <typename T, typename U, typename PointInPolygonImpl>
556ColumnPtr pointInPolygon(const ColumnVector<T> & x, const ColumnVector<U> & y, PointInPolygonImpl && impl)
557{
558 auto size = x.size();
559
560 impl.init();
561
562 if (impl.hasEmptyBound())
563 {
564 return ColumnVector<UInt8>::create(size, 0);
565 }
566
567 auto result = ColumnVector<UInt8>::create(size);
568 auto & data = result->getData();
569
570 const auto & x_data = x.getData();
571 const auto & y_data = y.getData();
572
573 for (auto i : ext::range(0, size))
574 {
575 data[i] = static_cast<UInt8>(impl.contains(x_data[i], y_data[i]));
576 }
577
578 return result;
579}
580
581template <typename ... Types>
582struct CallPointInPolygon;
583
584template <typename Type, typename ... Types>
585struct CallPointInPolygon<Type, Types ...>
586{
587 template <typename T, typename PointInPolygonImpl>
588 static ColumnPtr call(const ColumnVector<T> & x, const IColumn & y, PointInPolygonImpl && impl)
589 {
590 if (auto column = typeid_cast<const ColumnVector<Type> *>(&y))
591 return pointInPolygon(x, *column, impl);
592 return CallPointInPolygon<Types ...>::template call<T>(x, y, impl);
593 }
594
595 template <typename PointInPolygonImpl>
596 static ColumnPtr call(const IColumn & x, const IColumn & y, PointInPolygonImpl && impl)
597 {
598 using Impl = typename ApplyTypeListForClass<::DB::GeoUtils::CallPointInPolygon, TypeListNativeNumbers>::Type;
599 if (auto column = typeid_cast<const ColumnVector<Type> *>(&x))
600 return Impl::template call<Type>(*column, y, impl);
601 return CallPointInPolygon<Types ...>::call(x, y, impl);
602 }
603};
604
605template <>
606struct CallPointInPolygon<>
607{
608 template <typename T, typename PointInPolygonImpl>
609 static ColumnPtr call(const ColumnVector<T> &, const IColumn & y, PointInPolygonImpl &&)
610 {
611 throw Exception(std::string("Unknown numeric column type: ") + demangle(typeid(y).name()), ErrorCodes::LOGICAL_ERROR);
612 }
613
614 template <typename PointInPolygonImpl>
615 static ColumnPtr call(const IColumn & x, const IColumn &, PointInPolygonImpl &&)
616 {
617 throw Exception(std::string("Unknown numeric column type: ") + demangle(typeid(x).name()), ErrorCodes::LOGICAL_ERROR);
618 }
619};
620
621template <typename PointInPolygonImpl>
622ColumnPtr pointInPolygon(const IColumn & x, const IColumn & y, PointInPolygonImpl && impl)
623{
624 using Impl = typename ApplyTypeListForClass<::DB::GeoUtils::CallPointInPolygon, TypeListNativeNumbers>::Type;
625 return Impl::call(x, y, impl);
626}
627
628/// Total angle (signed) between neighbor vectors in linestring. Zero if linestring.size() < 2.
629template <typename Linestring>
630double calcLinestringRotation(const Linestring & points)
631{
632 using Point = std::decay_t<decltype(*points.begin())>;
633 double rotation = 0;
634
635 auto vecProduct = [](const Point & from, const Point & to) { return from.x() * to.y() - from.y() * to.x(); };
636 auto scalarProduct = [](const Point & from, const Point & to) { return from.x() * to.x() + from.y() * to.y(); };
637 auto getVector = [](const Point & from, const Point & to) -> Point
638 {
639 return Point(to.x() - from.x(), to.y() - from.y());
640 };
641
642 for (auto it = points.begin(); it != points.end(); ++it)
643 {
644 if (it != points.begin())
645 {
646 auto prev = std::prev(it);
647 auto next = std::next(it);
648
649 if (next == points.end())
650 next = std::next(points.begin());
651
652 Point from = getVector(*prev, *it);
653 Point to = getVector(*it, *next);
654
655 auto vec_prod = vecProduct(from, to);
656 auto scalar_prod = scalarProduct(from, to);
657 auto ang = std::atan2(vec_prod, scalar_prod);
658 rotation += ang;
659 }
660 }
661
662 return rotation;
663}
664
665/// Make inner linestring counter-clockwise and outers clockwise oriented.
666template <typename Polygon>
667void normalizePolygon(Polygon && polygon)
668{
669 auto & outer = polygon.outer();
670 if (calcLinestringRotation(outer) < 0)
671 std::reverse(outer.begin(), outer.end());
672
673 auto & inners = polygon.inners();
674 for (auto & inner : inners)
675 if (calcLinestringRotation(inner) > 0)
676 std::reverse(inner.begin(), inner.end());
677}
678
679
680template <typename Polygon>
681std::string serialize(Polygon && polygon)
682{
683 std::string result;
684
685 {
686 WriteBufferFromString buffer(result);
687
688 using RingType = typename std::decay_t<Polygon>::ring_type;
689
690 auto serializeFloat = [&buffer](float value) { buffer.write(reinterpret_cast<char *>(&value), sizeof(value)); };
691 auto serializeSize = [&buffer](size_t size) { buffer.write(reinterpret_cast<char *>(&size), sizeof(size)); };
692
693 auto serializeRing = [& serializeFloat, & serializeSize](const RingType & ring)
694 {
695 serializeSize(ring.size());
696 for (const auto & point : ring)
697 {
698 serializeFloat(point.x());
699 serializeFloat(point.y());
700 }
701 };
702
703 serializeRing(polygon.outer());
704
705 const auto & inners = polygon.inners();
706 serializeSize(inners.size());
707 for (auto & inner : inners)
708 serializeRing(inner);
709 }
710
711 return result;
712}
713
714size_t geohashEncode(Float64 longitude, Float64 latitude, UInt8 precision, char * out);
715
716void geohashDecode(const char * encoded_string, size_t encoded_len, Float64 * longitude, Float64 * latitude);
717
718std::vector<std::pair<Float64, Float64>> geohashCoverBox(Float64 longitude_min, Float64 latitude_min, Float64 longitude_max, Float64 latitude_max, UInt8 precision, UInt32 max_items = 0);
719
720struct GeohashesInBoxPreparedArgs
721{
722 UInt64 items_count = 0;
723 UInt8 precision = 0;
724
725 Float64 longitude_min = 0.0;
726 Float64 latitude_min = 0.0;
727 Float64 longitude_max = 0.0;
728 Float64 latitude_max = 0.0;
729
730 Float64 longitude_step = 0.0;
731 Float64 latitude_step = 0.0;
732};
733
734GeohashesInBoxPreparedArgs geohashesInBoxPrepare(const Float64 longitude_min,
735 const Float64 latitude_min,
736 Float64 longitude_max,
737 Float64 latitude_max,
738 UInt8 precision);
739
740UInt64 geohashesInBox(const GeohashesInBoxPreparedArgs & estimation, char * out);
741
742} /// GeoUtils
743
744} /// DB
745