Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Geospatial Polygons support holes #6529

Merged
merged 7 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/realm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,6 @@ add_library(Storage STATIC
$<TARGET_OBJECTS:Bid>
)

if (REALM_ENABLE_GEOSPATIAL)
target_compile_definitions(Storage PUBLIC REALM_ENABLE_GEOSPATIAL=1)
endif()

add_library(Realm::Storage ALIAS Storage)

set_target_properties(Storage PROPERTIES
Expand Down
1 change: 1 addition & 0 deletions src/realm/data_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <stdint.h>
#include <realm/util/to_string.hpp>
#include <realm/util/features.h>

namespace realm {

Expand Down
199 changes: 143 additions & 56 deletions src/realm/geospatial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <realm/obj.hpp>
#include <realm/table.hpp>
#include <realm/table_ref.hpp>
#include <realm/util/overload.hpp>

namespace {

Expand All @@ -64,6 +65,46 @@ namespace realm {
// src/mongo/db/geo/geoconstants.h
const double GeoCenterSphere::c_radius_meters = 6378100.0;

std::string Geospatial::get_type_string() const noexcept
{
return mpark::visit(util::overload{[&](const GeoPoint&) {
return "Point";
},
[&](const GeoBox&) {
return "Box";
},
[&](const GeoPolygon&) {
return "Polygon";
},
[&](const GeoCenterSphere&) {
return "CenterSphere";
},
[&](const mpark::monostate&) {
return "Invalid";
}},
m_value);
}

Geospatial::Type Geospatial::get_type() const noexcept
{
return mpark::visit(util::overload{[&](const GeoPoint&) {
return Type::Point;
},
[&](const GeoBox&) {
return Type::Box;
},
[&](const GeoPolygon&) {
return Type::Polygon;
},
[&](const GeoCenterSphere&) {
return Type::CenterSphere;
},
[&](const mpark::monostate&) {
return Type::Invalid;
}},
m_value);
}

bool Geospatial::is_geospatial(const TableRef table, ColKey link_col)
{
if (!table || !link_col) {
Expand Down Expand Up @@ -156,34 +197,30 @@ void Geospatial::assign_to(Obj& link) const
throw InvalidArgument(ErrorCodes::TypeMismatch,
util::format("Property %1 doesn't exist", c_geo_point_coords_col_name));
}
if (m_type == Type::Invalid) {
Geospatial::Type type = get_type();
if (type == Type::Invalid) {
link.remove();
return;
}
if (m_type != Type::Point) {
if (type != Type::Point) {
throw IllegalOperation("The only Geospatial type currently supported for storage is 'point'");
}
if (m_points.size() > 1) {
throw IllegalOperation("Only one Geospatial point is currently supported");
}
if (m_points.size() == 0) {
throw InvalidArgument("Geospatial value must have one point");
}
auto&& point = get<GeoPoint>();
link.set(type_col, get_type_string());
Lst<double> coords = link.get_list<double>(coords_col);
if (coords.size() >= 1) {
coords.set(0, m_points[0].longitude);
coords.set(0, point.longitude);
}
else {
coords.add(m_points[0].longitude);
coords.add(point.longitude);
}
if (coords.size() >= 2) {
coords.set(1, m_points[0].latitude);
coords.set(1, point.latitude);
}
else {
coords.add(m_points[0].latitude);
coords.add(point.latitude);
}
std::optional<double> altitude = m_points[0].get_altitude();
std::optional<double> altitude = point.get_altitude();
if (altitude) {
if (coords.size() >= 3) {
coords.set(2, *altitude);
Expand All @@ -202,63 +239,113 @@ S2Region& Geospatial::get_region() const
if (m_region)
return *m_region.get();

switch (m_type) {
// FIXME 'box' assumes legacy flat plane, should be removed?
case Type::Box: {
REALM_ASSERT(m_points.size() == 2);
auto &&lo = m_points[0], &&hi = m_points[1];
m_region = std::make_unique<S2LatLngRect>(S2LatLng::FromDegrees(lo.latitude, lo.longitude),
S2LatLng::FromDegrees(hi.latitude, hi.longitude));
} break;
case Type::Polygon: {
REALM_ASSERT(m_points.size() >= 3);
// should be really S2Polygon, but it's really needed only for MultyPolygon
std::vector<S2Point> points;
points.reserve(m_points.size());
for (auto&& p : m_points)
// FIXME rewrite without copying
points.emplace_back(S2LatLng::FromDegrees(p.latitude, p.longitude).ToPoint());
m_region = std::make_unique<S2Loop>(points);
} break;
case Type::CenterSphere: {
REALM_ASSERT(m_points.size() == 1);
REALM_ASSERT(m_radius_radians && m_radius_radians > 0.0);
auto&& p = m_points.front();
auto center = S2LatLng::FromDegrees(p.latitude, p.longitude).ToPoint();
auto radius = S1Angle::Radians(m_radius_radians);
m_region.reset(S2Cap::FromAxisAngle(center, radius).Clone()); // FIXME without extra copy
} break;
default:
REALM_UNREACHABLE();
}
mpark::visit(util::overload{
[&](const GeoPoint&) {
REALM_UNREACHABLE();
},
[&](const GeoBox& box) {
m_region =
std::make_unique<S2LatLngRect>(S2LatLng::FromDegrees(box.lo.latitude, box.lo.longitude),
S2LatLng::FromDegrees(box.hi.latitude, box.hi.longitude));
},
[&](const GeoPolygon& polygon) {
REALM_ASSERT(polygon.points.size() >= 1);
std::vector<S2Loop*> loops;
for (size_t i = 0; i < polygon.points.size(); ++i) {
std::vector<S2Point> points;
points.reserve(polygon.points[i].size());
for (auto&& p : polygon.points[i]) {
// FIXME rewrite without copying
points.emplace_back(S2LatLng::FromDegrees(p.latitude, p.longitude).ToPoint());
}
loops.push_back(new S2Loop(points));
}
// S2Polygon takes ownership of all the S2Loop pointers
m_region = std::make_unique<S2Polygon>(&loops);
},
[&](const GeoCenterSphere& sphere) {
auto center =
S2LatLng::FromDegrees(sphere.center.latitude, sphere.center.longitude).ToPoint();
auto radius = S1Angle::Radians(sphere.radius_radians);
m_region.reset(S2Cap::FromAxisAngle(center, radius).Clone()); // FIXME without extra copy
},
[&](const mpark::monostate&) {
REALM_UNREACHABLE();
}},
m_value);

return *m_region.get();
}

bool Geospatial::is_within(const Geospatial& geometry) const noexcept
{
REALM_ASSERT(m_points.size() == 1); // point
REALM_ASSERT(get_type() == Geospatial::Type::Point);

auto point = S2LatLng::FromDegrees(m_points[0].latitude, m_points[0].longitude).ToPoint();
auto&& geo_point = mpark::get<GeoPoint>(m_value);
auto point = S2LatLng::FromDegrees(geo_point.latitude, geo_point.longitude).ToPoint();

auto& region = geometry.get_region();
switch (geometry.m_type) {
case Type::Box:
return static_cast<S2LatLngRect&>(region).Contains(point);
case Type::Polygon:
return static_cast<S2Loop&>(region).Contains(point);
case Type::CenterSphere:
return static_cast<S2Cap&>(region).Contains(point);
default:
break;
}
return mpark::visit(util::overload{[&](const GeoPoint&) {
return false;
},
[&](const GeoBox&) {
return static_cast<S2LatLngRect&>(region).Contains(point);
},
[&](const GeoPolygon&) {
return static_cast<S2Polygon&>(region).Contains(point);
},
[&](const GeoCenterSphere&) {
return static_cast<S2Cap&>(region).Contains(point);
},
[&](const mpark::monostate&) {
return false;
}},
geometry.m_value);
}

std::string Geospatial::to_string() const
{
auto point_str = [&](const GeoPoint& point) -> std::string {
if (point.get_altitude()) {
return util::format("[%1, %2, %3]", point.longitude, point.latitude, *point.get_altitude());
}
return util::format("[%1, %2]", point.longitude, point.latitude);
};

REALM_UNREACHABLE(); // FIXME: other types and error handling
return mpark::visit(
util::overload{[&](const GeoPoint& point) {
return util::format("GeoPoint(%1)", point_str(point));
},
[&](const GeoBox& box) {
return util::format("GeoBox(%1, %2)", point_str(box.lo), point_str(box.hi));
},
[&](const GeoPolygon& poly) {
std::string points = "";
for (size_t i = 0; i < poly.points.size(); ++i) {
if (i != 0) {
points += ", ";
}
points += "{";
for (size_t j = 0; j < poly.points[i].size(); ++j) {
points += util::format("%1%2", j == 0 ? "" : ", ", point_str(poly.points[i][j]));
}
points += "}";
}
return util::format("GeoPolygon(%1)", points);
},
[&](const GeoCenterSphere& sphere) {
return util::format("GeoSphere(%1, %2)", point_str(sphere.center), sphere.radius_radians);
},
[&](const mpark::monostate&) {
return std::string("NULL");
}},
m_value);
return "NULL";
}

std::ostream& operator<<(std::ostream& ostr, const Geospatial& geo)
{
ostr << util::serializer::print_value(geo);
ostr << geo.to_string();
return ostr;
}

Expand Down
Loading