From d8e553985f94755bea1d26aaa69ca20abac9de20 Mon Sep 17 00:00:00 2001 From: "jie.wang" <38901892+jievince@users.noreply.github.com> Date: Tue, 12 Oct 2021 15:59:20 +0800 Subject: [PATCH] Geo spatial: 2. geography functions (#2979) * Geo spatial: 2. Add geo functions * add ByteOrderDataIOStream * add s2 util test * let Geography store variant * address yee's comments --- .clang-tidy | 4 +- src/common/datatypes/CommonCpp2Ops.h | 8 + src/common/datatypes/Geography.cpp | 256 +++- src/common/datatypes/Geography.h | 148 ++- src/common/datatypes/GeographyOps-inl.h | 591 ++++++++- src/common/datatypes/test/GeographyTest.cpp | 15 +- src/common/datatypes/test/ValueTest.cpp | 32 +- src/common/function/CMakeLists.txt | 1 + src/common/function/FunctionManager.cpp | 348 +++++ src/common/geo/CMakeLists.txt | 2 +- src/common/geo/GeoFunction.cpp | 554 ++++++++ src/common/geo/GeoFunction.h | 73 ++ src/common/geo/GeoShape.h | 18 - src/common/geo/GeoUtils.h | 68 +- src/common/geo/io/CMakeLists.txt | 3 +- src/common/geo/io/Geometry.h | 102 -- src/common/geo/io/wkb/ByteOrder.h | 44 +- .../geo/io/wkb/ByteOrderDataIOStream.cpp | 52 + src/common/geo/io/wkb/ByteOrderDataIOStream.h | 66 + src/common/geo/io/wkb/CMakeLists.txt | 6 + src/common/geo/io/wkb/WKBReader.cpp | 137 +- src/common/geo/io/wkb/WKBReader.h | 39 +- src/common/geo/io/wkb/WKBWriter.cpp | 88 +- src/common/geo/io/wkb/WKBWriter.h | 27 +- src/common/geo/io/wkb/test/CMakeLists.txt | 55 + src/common/geo/io/wkb/test/WKBTest.cpp | 181 +++ src/common/geo/io/wkt/WKTReader.cpp | 2 +- src/common/geo/io/wkt/WKTReader.h | 32 +- src/common/geo/io/wkt/WKTScanner.h | 13 +- src/common/geo/io/wkt/WKTWriter.cpp | 24 +- src/common/geo/io/wkt/WKTWriter.h | 6 +- src/common/geo/io/wkt/test/CMakeLists.txt | 8 +- .../test/{WKTParserTest.cpp => WKTTest.cpp} | 78 +- src/common/geo/io/wkt/wkt_parser.yy | 67 +- src/common/geo/test/CMakeLists.txt | 22 + src/common/geo/test/GeoFunctionTest.cpp | 1167 +++++++++++++++++ src/interface/common.thrift | 23 +- 37 files changed, 3773 insertions(+), 587 deletions(-) create mode 100644 src/common/geo/GeoFunction.cpp create mode 100644 src/common/geo/GeoFunction.h delete mode 100644 src/common/geo/GeoShape.h delete mode 100644 src/common/geo/io/Geometry.h create mode 100644 src/common/geo/io/wkb/ByteOrderDataIOStream.cpp create mode 100644 src/common/geo/io/wkb/ByteOrderDataIOStream.h create mode 100644 src/common/geo/io/wkb/CMakeLists.txt create mode 100644 src/common/geo/io/wkb/test/CMakeLists.txt create mode 100644 src/common/geo/io/wkb/test/WKBTest.cpp rename src/common/geo/io/wkt/test/{WKTParserTest.cpp => WKTTest.cpp} (53%) create mode 100644 src/common/geo/test/CMakeLists.txt create mode 100644 src/common/geo/test/GeoFunctionTest.cpp diff --git a/.clang-tidy b/.clang-tidy index ed3c5f554f1..134cb61e561 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -85,10 +85,10 @@ Checks: '-*,clang-diagnostic-*,clang-analyzer-*,-misc-unused-parameters, modernize-use-emplace, modernize-use-equals-default, modernize-use-equals-delete, - modernize-use-nodiscard, + -modernize-use-nodiscard, modernize-use-nullptr, modernize-use-override, - modernize-use-trailing-return-type, + -modernize-use-trailing-return-type, modernize-use-transparent-functors, modernize-use-using, diff --git a/src/common/datatypes/CommonCpp2Ops.h b/src/common/datatypes/CommonCpp2Ops.h index 8be8fdf89b4..82dff6e0f0f 100644 --- a/src/common/datatypes/CommonCpp2Ops.h +++ b/src/common/datatypes/CommonCpp2Ops.h @@ -24,6 +24,10 @@ struct Map; struct Set; struct List; struct DataSet; +struct Coordinate; +struct Point; +struct LineString; +struct Polygon; struct Geography; } // namespace nebula @@ -44,6 +48,10 @@ SPECIALIZE_CPP2OPS(nebula::Map); SPECIALIZE_CPP2OPS(nebula::Set); SPECIALIZE_CPP2OPS(nebula::List); SPECIALIZE_CPP2OPS(nebula::DataSet); +SPECIALIZE_CPP2OPS(nebula::Coordinate); +SPECIALIZE_CPP2OPS(nebula::Point); +SPECIALIZE_CPP2OPS(nebula::LineString); +SPECIALIZE_CPP2OPS(nebula::Polygon); SPECIALIZE_CPP2OPS(nebula::Geography); } // namespace apache::thrift diff --git a/src/common/datatypes/Geography.cpp b/src/common/datatypes/Geography.cpp index 3318c89f087..922e2525301 100644 --- a/src/common/datatypes/Geography.cpp +++ b/src/common/datatypes/Geography.cpp @@ -19,59 +19,232 @@ namespace nebula { -StatusOr Geography::fromWKT(const std::string& wkt) { - auto geomRet = WKTReader().read(wkt); - NG_RETURN_IF_ERROR(geomRet); - auto geom = geomRet.value(); - auto wkb = WKBWriter().write(geom); - return Geography(wkb); +void Coordinate::normalize() { + // Reduce the x(longitude) to the range [-180, 180] degrees + x = std::remainder(x, 360.0); + + // Reduce the y(latitude) to the range [-90, 90] degrees + double tmp = remainder(y, 360.0); + if (tmp > 90.0) { + y = 180.0 - tmp; + } else if (tmp < -90.0) { + y = -180.0 - tmp; + } } -GeoShape Geography::shape() const { - // TODO(jie) May store the shapetype as the data member of Geography is ok. - const uint8_t* beg = reinterpret_cast(wkb.data()); - const uint8_t* end = beg + wkb.size(); - WKBReader reader; - auto byteOrderRet = reader.readByteOrder(beg, end); - if (!byteOrderRet.ok()) { - return GeoShape::UNKNOWN; +bool Coordinate::isValid() const { return std::abs(x) <= 180.0 && std::abs(y) <= 90.0; } + +void Point::normalize() {} + +bool Point::isValid() const { return coord.isValid(); } + +void LineString::normalize() { geo::GeoUtils::removeAdjacentDuplicateCoordinates(coordList); } + +bool LineString::isValid() const { + // LineString must have at least 2 coordinates; + if (coordList.size() < 2) { + return false; + } + auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + return static_cast(s2Region.get())->IsValid(); +} + +void Polygon::normalize() { + for (auto& coordList : coordListList) { + geo::GeoUtils::removeAdjacentDuplicateCoordinates(coordList); } - ByteOrder byteOrder = byteOrderRet.value(); - auto shapeTypeRet = reader.readShapeType(beg, end, byteOrder); - if (!shapeTypeRet.ok()) { - return GeoShape::UNKNOWN; +} + +bool Polygon::isValid() const { + for (const auto& coordList : coordListList) { + // Polygon's LinearRing must have at least 4 coordinates + if (coordList.size() < 4) { + return false; + } + // Polygon's LinearRing must be closed + if (coordList.front() != coordList.back()) { + return false; + } } - return shapeTypeRet.value(); + auto s2Region = geo::GeoUtils::s2RegionFromGeography(*this); + return static_cast(s2Region.get())->IsValid(); +} + +StatusOr Geography::fromWKT(const std::string& wkt, + bool needNormalize, + bool verifyValidity) { + auto geogRet = geo::WKTReader().read(wkt); + if (!geogRet.ok()) { + return geogRet; + } + auto geog = std::move(geogRet).value(); + if (needNormalize) { + geog.normalize(); + } + if (verifyValidity) { + if (!geog.isValid()) { + return Status::Error("Failed to parse an valid Geography instance from the wkt `%s'", + wkt.c_str()); + } + } + + return geog; +} + +GeoShape Geography::shape() const { + switch (geo_.index()) { + case 0: + return GeoShape::POINT; + case 1: + return GeoShape::LINESTRING; + case 2: + return GeoShape::POLYGON; + default: // May never reaches here, because the default constructor of the variant geo_ will + // hold the value-initialized value of the first alternative(Point). + return GeoShape::UNKNOWN; + } +} + +const Point& Geography::point() const { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); +} + +const LineString& Geography::lineString() const { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); +} + +const Polygon& Geography::polygon() const { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); +} + +Point& Geography::mutablePoint() { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); +} + +LineString& Geography::mutableLineString() { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); } -std::unique_ptr Geography::asWKT() const { - auto geomRet = WKBReader().read(wkb); - if (!geomRet.ok()) { - LOG(ERROR) << geomRet.status(); - return nullptr; +Polygon& Geography::mutablePolygon() { + CHECK(std::holds_alternative(geo_)); + return std::get(geo_); +} + +void Geography::normalize() { + switch (shape()) { + case GeoShape::POINT: { + auto& point = mutablePoint(); + point.normalize(); + return; + } + case GeoShape::LINESTRING: { + auto& line = mutableLineString(); + line.normalize(); + return; + } + case GeoShape::POLYGON: { + auto& polygon = mutablePolygon(); + polygon.normalize(); + return; + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return; + } } - auto geom = geomRet.value(); - return std::make_unique(WKTWriter().write(geom)); } -std::unique_ptr Geography::asWKBHex() const { - auto geomRet = WKBReader().read(wkb); - if (!geomRet.ok()) { - LOG(ERROR) << geomRet.status(); - return nullptr; +bool Geography::isValid() const { + switch (shape()) { + case GeoShape::POINT: { + const auto& point = this->point(); + return point.isValid(); + } + case GeoShape::LINESTRING: { + const auto& line = this->lineString(); + return line.isValid(); + } + case GeoShape::POLYGON: { + const auto& polygon = this->polygon(); + return polygon.isValid(); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } } - auto geom = geomRet.value(); - return std::make_unique(folly::hexlify(WKBWriter().write(geom))); } +std::string Geography::asWKT() const { return geo::WKTWriter().write(*this); } + +std::string Geography::asWKB() const { return geo::WKBWriter().write(*this); } + +std::string Geography::asWKBHex() const { return folly::hexlify(geo::WKBWriter().write(*this)); } + std::unique_ptr Geography::asS2() const { - auto geomRet = WKBReader().read(wkb); - if (!geomRet.ok()) { - LOG(ERROR) << geomRet.status(); - return nullptr; + return geo::GeoUtils::s2RegionFromGeography(*this); +} + +bool Geography::operator==(const Geography& rhs) const { + auto lhsShape = shape(); + auto rhsShape = rhs.shape(); + if (lhsShape != rhsShape) { + return false; + } + + switch (lhsShape) { + case GeoShape::POINT: { + return point() == rhs.point(); + } + case GeoShape::LINESTRING: { + return lineString() == rhs.lineString(); + } + case GeoShape::POLYGON: { + return polygon() == rhs.polygon(); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) << "Geography shapes other than Point/LineString/Polygon are not currently " + "supported"; + return false; + } + } +} + +bool Geography::operator<(const Geography& rhs) const { + auto lhsShape = shape(); + auto rhsShape = rhs.shape(); + if (lhsShape != rhsShape) { + return lhsShape < rhsShape; + } + + switch (lhsShape) { + case GeoShape::POINT: { + return point() < rhs.point(); + } + case GeoShape::LINESTRING: { + return lineString() < rhs.lineString(); + } + case GeoShape::POLYGON: { + return polygon() < rhs.polygon(); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) << "Geography shapes other than Point/LineString/Polygon are not currently " + "supported"; + return false; + } } - auto geom = geomRet.value(); - return GeoUtils::s2RegionFromGeomtry(geom); + return false; } } // namespace nebula @@ -79,8 +252,9 @@ std::unique_ptr Geography::asS2() const { namespace std { // Inject a customized hash function -std::size_t hash::operator()(const nebula::Geography& h) const noexcept { - return hash{}(h.wkb); +std::size_t hash::operator()(const nebula::Geography& v) const noexcept { + std::string wkb = v.asWKB(); + return hash{}(wkb); } } // namespace std diff --git a/src/common/datatypes/Geography.h b/src/common/datatypes/Geography.h index 241f8c5a09d..5f2ecd31790 100644 --- a/src/common/datatypes/Geography.h +++ b/src/common/datatypes/Geography.h @@ -11,9 +11,11 @@ #include #include +#include + +#include "common/base/Base.h" #include "common/base/StatusOr.h" #include "common/datatypes/Value.h" -#include "common/geo/io/Geometry.h" // Do not include here, it will indirectly includes a header file which defines a // enum `BEGIN`(not enum class). While Geography.h is indirectly included by parser.yy, which has a @@ -23,6 +25,13 @@ class S2Polygon; namespace nebula { +enum class GeoShape : uint32_t { + UNKNOWN = 0, // illegal + POINT = 1, + LINESTRING = 2, + POLYGON = 3, +}; + // clang-format off /* static const std::unordered_map kShapeTypeToS2Region = { @@ -34,45 +43,140 @@ static const std::unordered_map kShapeTypeToS2Region = { */ // clang-format on -// Do not construct a S2 object when constructing Geography. It's expensive. -// We just construct S2 when doing computation. +struct Coordinate { + double x, y; + + Coordinate() = default; + Coordinate(double lng, double lat) : x(lng), y(lat) {} + + void normalize(); + bool isValid() const; + + void clear() { + x = 0.0; + y = 0.0; + } + void __clear() { clear(); } + + // TODO(jie) compare double correctly + bool operator==(const Coordinate& rhs) const { return x == rhs.x && y == rhs.y; } + bool operator!=(const Coordinate& rhs) const { return !(*this == rhs); } + bool operator<(const Coordinate& rhs) const { + if (x != rhs.x) { + return x < rhs.x; + } + if (y != rhs.y) { + return y < rhs.y; + } + return false; + } +}; + +struct Point { + Coordinate coord; + + Point() = default; + explicit Point(const Coordinate& v) : coord(v) {} + explicit Point(Coordinate&& v) : coord(std::move(v)) {} + + void normalize(); + bool isValid() const; + + void clear() { coord.clear(); } + void __clear() { clear(); } + + bool operator==(const Point& rhs) const { return coord == rhs.coord; } + bool operator<(const Point& rhs) const { return coord < rhs.coord; } +}; + +struct LineString { + std::vector coordList; + + LineString() = default; + explicit LineString(const std::vector& v) : coordList(v) {} + explicit LineString(std::vector&& v) : coordList(std::move(v)) {} + + uint32_t numCoord() const { return coordList.size(); } + + void normalize(); + bool isValid() const; + + void clear() { coordList.clear(); } + void __clear() { clear(); } + + bool operator==(const LineString& rhs) const { return coordList == rhs.coordList; } + bool operator<(const LineString& rhs) const { return coordList < rhs.coordList; } +}; + +struct Polygon { + std::vector> coordListList; + + Polygon() = default; + explicit Polygon(const std::vector>& v) : coordListList(v) {} + explicit Polygon(std::vector>&& v) : coordListList(std::move(v)) {} + + uint32_t numCoordList() const { return coordListList.size(); } + + void normalize(); + bool isValid() const; + + void clear() { coordListList.clear(); } + void __clear() { clear(); } + + bool operator==(const Polygon& rhs) const { return coordListList == rhs.coordListList; } + bool operator<(const Polygon& rhs) const { return coordListList < rhs.coordListList; } +}; + struct Geography { - std::string wkb; // TODO(jie) Is it better to store Geometry* or S2Region* here? + std::variant geo_; - Geography() = default; + // Factory method + static StatusOr fromWKT(const std::string& wkt, + bool needNormalize = false, + bool verifyValidity = false); - static StatusOr fromWKT(const std::string& wkt); + Geography() {} + Geography(const Point& v) : geo_(v) {} // NOLINT + Geography(Point&& v) : geo_(std::move(v)) {} // NOLINT + Geography(const LineString& v) : geo_(v) {} // NOLINT + Geography(LineString&& v) : geo_(std::move(v)) {} // NOLINT + Geography(const Polygon& v) : geo_(v) {} // NOLINT + Geography(Polygon&& v) : geo_(std::move(v)) {} // NOLINT GeoShape shape() const; - std::unique_ptr asWKT() const; + const Point& point() const; + const LineString& lineString() const; + const Polygon& polygon() const; - std::unique_ptr asWKBHex() const; + Point& mutablePoint(); + LineString& mutableLineString(); + Polygon& mutablePolygon(); - std::unique_ptr asS2() const; + void normalize(); + bool isValid() const; - std::string toString() const { return wkb; } + std::string asWKT() const; - folly::dynamic toJson() const { return toString(); } + std::string asWKB() const; - void clear() { wkb.clear(); } + std::string asWKBHex() const; - void __clear() { clear(); } + std::unique_ptr asS2() const; - bool operator==(const Geography& rhs) const { return wkb == rhs.wkb; } + std::string toString() const { return asWKT(); } - bool operator!=(const Geography& rhs) const { return !(wkb == rhs.wkb); } + folly::dynamic toJson() const { return toString(); } - bool operator<(const Geography& rhs) const { return wkb < rhs.wkb; } + void clear() { geo_.~variant(); } - private: - explicit Geography(const std::string& bytes) { - // TODO(jie): Must ensure the bytes is valid - wkb = bytes; - } + void __clear() { clear(); } + + bool operator==(const Geography& rhs) const; + bool operator<(const Geography& rhs) const; }; -inline std::ostream& operator<<(std::ostream& os, const Geography& g) { return os << g.wkb; } +inline std::ostream& operator<<(std::ostream& os, const Geography& g) { return os << g.toString(); } } // namespace nebula diff --git a/src/common/datatypes/GeographyOps-inl.h b/src/common/datatypes/GeographyOps-inl.h index 8e5804ae593..874eb5222f1 100644 --- a/src/common/datatypes/GeographyOps-inl.h +++ b/src/common/datatypes/GeographyOps-inl.h @@ -20,35 +20,41 @@ namespace thrift { /************************************** * - * Ops for class Geography + * Ops for struct Coordinate * *************************************/ namespace detail { template <> -struct TccStructTraits { +struct TccStructTraits { static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, MAYBE_UNUSED int16_t& fid, MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { - if (_fname == "wkb") { + if (_fname == "x") { fid = 1; - _ftype = apache::thrift::protocol::T_STRING; + _ftype = apache::thrift::protocol::T_DOUBLE; + } else if (_fname == "y") { + fid = 2; + _ftype = apache::thrift::protocol::T_DOUBLE; } } }; } // namespace detail -inline constexpr protocol::TType Cpp2Ops::thriftType() { +inline constexpr protocol::TType Cpp2Ops::thriftType() { return apache::thrift::protocol::T_STRUCT; } template -uint32_t Cpp2Ops::write(Protocol* proto, nebula::Geography const* obj) { +uint32_t Cpp2Ops::write(Protocol* proto, nebula::Coordinate const* obj) { uint32_t xfer = 0; - xfer += proto->writeStructBegin("Geography"); - xfer += proto->writeFieldBegin("wkb", apache::thrift::protocol::T_STRING, 1); - xfer += proto->writeString(obj->wkb); + xfer += proto->writeStructBegin("Coordinate"); + xfer += proto->writeFieldBegin("x", protocol::T_DOUBLE, 1); + xfer += proto->writeDouble(obj->x); + xfer += proto->writeFieldEnd(); + xfer += proto->writeFieldBegin("y", protocol::T_DOUBLE, 2); + xfer += proto->writeDouble(obj->y); xfer += proto->writeFieldEnd(); xfer += proto->writeFieldStop(); xfer += proto->writeStructEnd(); @@ -56,17 +62,375 @@ uint32_t Cpp2Ops::write(Protocol* proto, nebula::Geography co } template -void Cpp2Ops::read(Protocol* proto, nebula::Geography* obj) { +void Cpp2Ops::read(Protocol* proto, nebula::Coordinate* obj) { + detail::ProtocolReaderStructReadState readState; + + readState.readStructBegin(proto); + + using apache::thrift::TProtocolException; + + if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, protocol::T_DOUBLE))) { + goto _loop; + } +_readField_x : { proto->readDouble(obj->x); } + + if (UNLIKELY(!readState.advanceToNextField(proto, 1, 2, protocol::T_DOUBLE))) { + goto _loop; + } +_readField_y : { proto->readDouble(obj->y); } + + if (UNLIKELY(!readState.advanceToNextField(proto, 2, 0, apache::thrift::protocol::T_STOP))) { + goto _loop; + } + +_end: + readState.readStructEnd(proto); + + return; + +_loop: + if (readState.fieldType == apache::thrift::protocol::T_STOP) { + goto _end; + } + + if (proto->kUsesFieldNames()) { + detail::TccStructTraits::translateFieldName( + readState.fieldName(), readState.fieldId, readState.fieldType); + } + + switch (readState.fieldId) { + case 1: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_DOUBLE)) { + goto _readField_x; + } else { + goto _skip; + } + } + case 2: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_DOUBLE)) { + goto _readField_y; + } else { + goto _skip; + } + } + default: { +_skip: + proto->skip(readState.fieldType); + readState.readFieldEnd(proto); + readState.readFieldBeginNoInline(proto); + goto _loop; + } + } +} + +template +uint32_t Cpp2Ops::serializedSize(Protocol const* proto, + nebula::Coordinate const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Coordinate"); + xfer += proto->serializedFieldSize("x", apache::thrift::protocol::T_DOUBLE, 1); + xfer += proto->serializedSizeDouble(obj->x); + xfer += proto->serializedFieldSize("y", apache::thrift::protocol::T_DOUBLE, 2); + xfer += proto->serializedSizeDouble(obj->y); + xfer += proto->serializedSizeStop(); + return xfer; +} + +template +uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, + nebula::Coordinate const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Coordinate"); + xfer += proto->serializedFieldSize("x", apache::thrift::protocol::T_DOUBLE, 1); + xfer += proto->serializedSizeDouble(obj->x); + xfer += proto->serializedFieldSize("y", apache::thrift::protocol::T_DOUBLE, 2); + xfer += proto->serializedSizeDouble(obj->y); + xfer += proto->serializedSizeStop(); + return xfer; +} + +/************************************** + * + * Ops for struct Point + * + *************************************/ +namespace detail { + +template <> +struct TccStructTraits { + static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, + MAYBE_UNUSED int16_t& fid, + MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { + if (_fname == "coord") { + fid = 1; + _ftype = apache::thrift::protocol::T_STRUCT; + } + } +}; + +} // namespace detail + +inline constexpr protocol::TType Cpp2Ops::thriftType() { + return apache::thrift::protocol::T_STRUCT; +} + +template +uint32_t Cpp2Ops::write(Protocol* proto, nebula::Point const* obj) { + uint32_t xfer = 0; + xfer += proto->writeStructBegin("Point"); + xfer += proto->writeFieldBegin("coord", apache::thrift::protocol::T_STRUCT, 1); + xfer += Cpp2Ops::write(proto, &obj->coord); + xfer += proto->writeFieldEnd(); + xfer += proto->writeFieldStop(); + xfer += proto->writeStructEnd(); + return xfer; +} + +template +void Cpp2Ops::read(Protocol* proto, nebula::Point* obj) { + apache::thrift::detail::ProtocolReaderStructReadState readState; + + readState.readStructBegin(proto); + + using apache::thrift::TProtocolException; + + if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, apache::thrift::protocol::T_STRUCT))) { + goto _loop; + } +_readField_coord : { Cpp2Ops::read(proto, &obj->coord); } + + if (UNLIKELY(!readState.advanceToNextField(proto, 1, 0, apache::thrift::protocol::T_STOP))) { + goto _loop; + } + +_end: + readState.readStructEnd(proto); + + return; + +_loop: + if (readState.fieldType == apache::thrift::protocol::T_STOP) { + goto _end; + } + + if (proto->kUsesFieldNames()) { + detail::TccStructTraits::translateFieldName( + readState.fieldName(), readState.fieldId, readState.fieldType); + } + + switch (readState.fieldId) { + case 1: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_STRUCT)) { + goto _readField_coord; + } else { + goto _skip; + } + } + default: { +_skip: + proto->skip(readState.fieldType); + readState.readFieldEnd(proto); + readState.readFieldBeginNoInline(proto); + goto _loop; + } + } +} + +template +uint32_t Cpp2Ops::serializedSize(Protocol const* proto, nebula::Point const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Point"); + xfer += proto->serializedFieldSize("coord", apache::thrift::protocol::T_STRUCT, 1); + xfer += Cpp2Ops::serializedSize(proto, &obj->coord); + xfer += proto->serializedSizeStop(); + return xfer; +} + +template +uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, nebula::Point const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Point"); + xfer += proto->serializedFieldSize("coord", apache::thrift::protocol::T_STRUCT, 1); + xfer += Cpp2Ops::serializedSize(proto, &obj->coord); + xfer += proto->serializedSizeStop(); + return xfer; +} + +/************************************** + * + * Ops for struct LineString + * + *************************************/ +namespace detail { + +template <> +struct TccStructTraits { + static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, + MAYBE_UNUSED int16_t& fid, + MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { + if (_fname == "coordList") { + fid = 1; + _ftype = apache::thrift::protocol::T_LIST; + } + } +}; + +} // namespace detail + +inline constexpr protocol::TType Cpp2Ops::thriftType() { + return apache::thrift::protocol::T_LIST; +} + +template +uint32_t Cpp2Ops::write(Protocol* proto, nebula::LineString const* obj) { + uint32_t xfer = 0; + xfer += proto->writeStructBegin("LineString"); + xfer += proto->writeFieldBegin("coordList", apache::thrift::protocol::T_LIST, 1); + xfer += + detail::pm::protocol_methods, + std::vector>::write(*proto, obj->coordList); + xfer += proto->writeFieldEnd(); + xfer += proto->writeFieldStop(); + xfer += proto->writeStructEnd(); + return xfer; +} + +template +void Cpp2Ops::read(Protocol* proto, nebula::LineString* obj) { + apache::thrift::detail::ProtocolReaderStructReadState readState; + + readState.readStructBegin(proto); + + using apache::thrift::TProtocolException; + + if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, apache::thrift::protocol::T_LIST))) { + goto _loop; + } +_readField_coordList : { + obj->coordList = std::vector(); + detail::pm::protocol_methods, + std::vector>::read(*proto, obj->coordList); +} + + if (UNLIKELY(!readState.advanceToNextField(proto, 1, 0, apache::thrift::protocol::T_STOP))) { + goto _loop; + } + +_end: + readState.readStructEnd(proto); + + return; + +_loop: + if (readState.fieldType == apache::thrift::protocol::T_STOP) { + goto _end; + } + + if (proto->kUsesFieldNames()) { + detail::TccStructTraits::translateFieldName( + readState.fieldName(), readState.fieldId, readState.fieldType); + } + + switch (readState.fieldId) { + case 1: { + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_LIST)) { + goto _readField_coordList; + } else { + goto _skip; + } + } + default: { +_skip: + proto->skip(readState.fieldType); + readState.readFieldEnd(proto); + readState.readFieldBeginNoInline(proto); + goto _loop; + } + } +} + +template +uint32_t Cpp2Ops::serializedSize(Protocol const* proto, + nebula::LineString const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("LineString"); + xfer += proto->serializedFieldSize("coordList", apache::thrift::protocol::T_LIST, 1); + xfer += detail::pm::protocol_methods< + type_class::list, + std::vector>::serializedSize(*proto, obj->coordList); + xfer += proto->serializedSizeStop(); + return xfer; +} + +template +uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, + nebula::LineString const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("LineString"); + xfer += proto->serializedFieldSize("coordList", apache::thrift::protocol::T_LIST, 1); + xfer += detail::pm::protocol_methods< + type_class::list, + std::vector>::serializedSize(*proto, obj->coordList); + xfer += proto->serializedSizeStop(); + return xfer; +} + +/************************************** + * + * Ops for struct Polygon + * + *************************************/ +namespace detail { + +template <> +struct TccStructTraits { + static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, + MAYBE_UNUSED int16_t& fid, + MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { + if (_fname == "coordListList") { + fid = 1; + _ftype = apache::thrift::protocol::T_LIST; + } + } +}; + +} // namespace detail + +inline constexpr protocol::TType Cpp2Ops::thriftType() { + return apache::thrift::protocol::T_LIST; +} + +template +uint32_t Cpp2Ops::write(Protocol* proto, nebula::Polygon const* obj) { + uint32_t xfer = 0; + xfer += proto->writeStructBegin("Polygon"); + xfer += proto->writeFieldBegin("coordListList", apache::thrift::protocol::T_LIST, 1); + xfer += detail::pm::protocol_methods< + type_class::list, + std::vector>>::write(*proto, obj->coordListList); + xfer += proto->writeFieldEnd(); + xfer += proto->writeFieldStop(); + xfer += proto->writeStructEnd(); + return xfer; +} + +template +void Cpp2Ops::read(Protocol* proto, nebula::Polygon* obj) { apache::thrift::detail::ProtocolReaderStructReadState readState; readState.readStructBegin(proto); using apache::thrift::TProtocolException; - if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, apache::thrift::protocol::T_STRING))) { + if (UNLIKELY(!readState.advanceToNextField(proto, 0, 1, apache::thrift::protocol::T_LIST))) { goto _loop; } -_readField_wkb : { proto->readString(obj->wkb); } +_readField_coordListList : { + obj->coordListList = std::vector>(); + detail::pm::protocol_methods< + type_class::list, + std::vector>>::read(*proto, obj->coordListList); +} if (UNLIKELY(!readState.advanceToNextField(proto, 1, 0, apache::thrift::protocol::T_STOP))) { goto _loop; @@ -83,14 +447,14 @@ _readField_wkb : { proto->readString(obj->wkb); } } if (proto->kUsesFieldNames()) { - detail::TccStructTraits::translateFieldName( + detail::TccStructTraits::translateFieldName( readState.fieldName(), readState.fieldId, readState.fieldType); } switch (readState.fieldId) { case 1: { - if (LIKELY(readState.fieldType == apache::thrift::protocol::T_STRING)) { - goto _readField_wkb; + if (LIKELY(readState.fieldType == apache::thrift::protocol::T_LIST)) { + goto _readField_coordListList; } else { goto _skip; } @@ -105,13 +469,184 @@ _readField_wkb : { proto->readString(obj->wkb); } } } +template +uint32_t Cpp2Ops::serializedSize(Protocol const* proto, + nebula::Polygon const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Polygon"); + xfer += proto->serializedFieldSize("coordListList", apache::thrift::protocol::T_LIST, 1); + xfer += detail::pm::protocol_methods< + type_class::list, + std::vector>>::serializedSize(*proto, + obj->coordListList); + + xfer += proto->serializedSizeStop(); + return xfer; +} + +template +uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, + nebula::Polygon const* obj) { + uint32_t xfer = 0; + xfer += proto->serializedStructSize("Polygon"); + xfer += proto->serializedFieldSize("coordListList", apache::thrift::protocol::T_LIST, 1); + xfer += detail::pm::protocol_methods< + type_class::list, + std::vector>>::serializedSize(*proto, + obj->coordListList); + + xfer += proto->serializedSizeStop(); + return xfer; +} + +/************************************** + * + * Ops for class Geography + * + *************************************/ +namespace detail { + +template <> +struct TccStructTraits { + static void translateFieldName(MAYBE_UNUSED folly::StringPiece _fname, + MAYBE_UNUSED int16_t& fid, + MAYBE_UNUSED apache::thrift::protocol::TType& _ftype) { + if (_fname == "ptVal") { + fid = 1; + _ftype = apache::thrift::protocol::T_STRUCT; + } else if (_fname == "lsVal") { + fid = 2; + _ftype = apache::thrift::protocol::T_STRUCT; + } else if (_fname == "pgVal") { + fid = 3; + _ftype = apache::thrift::protocol::T_STRUCT; + } + } +}; + +} // namespace detail + +inline constexpr protocol::TType Cpp2Ops::thriftType() { + return apache::thrift::protocol::T_STRUCT; +} + +template +uint32_t Cpp2Ops::write(Protocol* proto, nebula::Geography const* obj) { + uint32_t xfer = 0; + xfer += proto->writeStructBegin("Geography"); + switch (obj->shape()) { + case nebula::GeoShape::POINT: { + xfer += proto->writeFieldBegin("ptVal", apache::thrift::protocol::T_STRUCT, 1); + xfer += Cpp2Ops::write(proto, &obj->point()); + xfer += proto->writeFieldEnd(); + break; + } + case nebula::GeoShape::LINESTRING: { + xfer += proto->writeFieldBegin("lsVal", apache::thrift::protocol::T_STRUCT, 2); + xfer += Cpp2Ops::write(proto, &obj->lineString()); + xfer += proto->writeFieldEnd(); + break; + } + case nebula::GeoShape::POLYGON: { + xfer += proto->writeFieldBegin("ptVal", apache::thrift::protocol::T_STRUCT, 3); + xfer += Cpp2Ops::write(proto, &obj->polygon()); + xfer += proto->writeFieldEnd(); + break; + } + case nebula::GeoShape::UNKNOWN: { + break; + } + } + xfer += proto->writeFieldStop(); + xfer += proto->writeStructEnd(); + return xfer; +} + +template +void Cpp2Ops::read(Protocol* proto, nebula::Geography* obj) { + apache::thrift::detail::ProtocolReaderStructReadState readState; + readState.fieldId = 0; + + readState.readStructBegin(proto); + + using apache::thrift::protocol::TProtocolException; + + readState.readFieldBegin(proto); + if (readState.fieldType == apache::thrift::protocol::T_STOP) { + obj->clear(); + } else { + if (proto->kUsesFieldNames()) { + detail::TccStructTraits::translateFieldName( + readState.fieldName(), readState.fieldId, readState.fieldType); + } + switch (readState.fieldId) { + case 1: { + if (readState.fieldType == apache::thrift::protocol::T_STRUCT) { + obj->geo_ = nebula::Point(); + Cpp2Ops::read(proto, &obj->mutablePoint()); + } else { + proto->skip(readState.fieldType); + } + break; + } + case 2: { + if (readState.fieldType == apache::thrift::protocol::T_STRUCT) { + obj->geo_ = nebula::LineString(); + Cpp2Ops::read(proto, &obj->mutableLineString()); + } else { + proto->skip(readState.fieldType); + } + break; + } + case 3: { + if (readState.fieldType == apache::thrift::protocol::T_STRUCT) { + obj->geo_ = nebula::Polygon(); + Cpp2Ops::read(proto, &obj->mutablePolygon()); + } else { + proto->skip(readState.fieldType); + } + break; + } + default: { + proto->skip(readState.fieldType); + break; + } + } + readState.readFieldEnd(proto); + readState.readFieldBegin(proto); + if (UNLIKELY(readState.fieldType != apache::thrift::protocol::T_STOP)) { + using apache::thrift::protocol::TProtocolException; + TProtocolException::throwUnionMissingStop(); + } + } + readState.readStructEnd(proto); +} + template uint32_t Cpp2Ops::serializedSize(Protocol const* proto, nebula::Geography const* obj) { uint32_t xfer = 0; xfer += proto->serializedStructSize("Geography"); - xfer += proto->serializedFieldSize("wkb", apache::thrift::protocol::T_STRING, 1); - xfer += proto->serializedSizeString(obj->wkb); + switch (obj->shape()) { + case nebula::GeoShape::POINT: { + xfer += proto->serializedFieldSize("ptVal", protocol::T_STRUCT, 1); + xfer += Cpp2Ops::serializedSize(proto, &obj->point()); + break; + } + case nebula::GeoShape::LINESTRING: { + xfer += proto->serializedFieldSize("lsVal", protocol::T_STRUCT, 2); + xfer += Cpp2Ops::serializedSize(proto, &obj->lineString()); + break; + } + case nebula::GeoShape::POLYGON: { + xfer += proto->serializedFieldSize("pgVal", protocol::T_STRUCT, 3); + xfer += Cpp2Ops::serializedSize(proto, &obj->polygon()); + break; + } + case nebula::GeoShape::UNKNOWN: { + break; + } + } xfer += proto->serializedSizeStop(); return xfer; } @@ -121,8 +656,26 @@ uint32_t Cpp2Ops::serializedSizeZC(Protocol const* proto, nebula::Geography const* obj) { uint32_t xfer = 0; xfer += proto->serializedStructSize("Geography"); - xfer += proto->serializedFieldSize("wkb", apache::thrift::protocol::T_STRING, 1); - xfer += proto->serializedSizeString(obj->wkb); + switch (obj->shape()) { + case nebula::GeoShape::POINT: { + xfer += proto->serializedFieldSize("ptVal", protocol::T_STRUCT, 1); + xfer += Cpp2Ops::serializedSize(proto, &obj->point()); + break; + } + case nebula::GeoShape::LINESTRING: { + xfer += proto->serializedFieldSize("lsVal", protocol::T_STRUCT, 2); + xfer += Cpp2Ops::serializedSize(proto, &obj->lineString()); + break; + } + case nebula::GeoShape::POLYGON: { + xfer += proto->serializedFieldSize("pgVal", protocol::T_STRUCT, 3); + xfer += Cpp2Ops::serializedSize(proto, &obj->polygon()); + break; + } + case nebula::GeoShape::UNKNOWN: { + break; + } + } xfer += proto->serializedSizeStop(); return xfer; } diff --git a/src/common/datatypes/test/GeographyTest.cpp b/src/common/datatypes/test/GeographyTest.cpp index 8c0a4b12115..0b15b0da36c 100644 --- a/src/common/datatypes/test/GeographyTest.cpp +++ b/src/common/datatypes/test/GeographyTest.cpp @@ -42,27 +42,24 @@ TEST(Geography, asWKT) { auto gRet = Geography::fromWKT(wkt); ASSERT_TRUE(gRet.ok()); auto g = gRet.value(); - auto got = g.asWKT(); - ASSERT_TRUE(!!got); - EXPECT_EQ(wkt, *got); + std::string got = g.asWKT(); + EXPECT_EQ(wkt, got); } { std::string wkt = "LINESTRING(28.4 79.2,134.25 -28.34)"; auto gRet = Geography::fromWKT(wkt); ASSERT_TRUE(gRet.ok()); auto g = gRet.value(); - auto got = g.asWKT(); - ASSERT_TRUE(!!got); - EXPECT_EQ(wkt, *got); + std::string got = g.asWKT(); + EXPECT_EQ(wkt, got); } { std::string wkt = "POLYGON((1 2,3 4,5 6,1 2))"; auto gRet = Geography::fromWKT(wkt); ASSERT_TRUE(gRet.ok()); auto g = gRet.value(); - auto got = g.asWKT(); - ASSERT_TRUE(!!got); - EXPECT_EQ(wkt, *got); + std::string got = g.asWKT(); + EXPECT_EQ(wkt, got); } } diff --git a/src/common/datatypes/test/ValueTest.cpp b/src/common/datatypes/test/ValueTest.cpp index b6a345ef065..6d3fdb4987b 100644 --- a/src/common/datatypes/test/ValueTest.cpp +++ b/src/common/datatypes/test/ValueTest.cpp @@ -1117,6 +1117,21 @@ TEST(Value, DecodeEncode) { // Geography Value(Geography::fromWKT("Point(3 8)").value()), + Value(Geography::fromWKT("LineString(3 8, 4 6, 5 7)").value()), + Value(Geography::fromWKT( + "Polygon((1 2, 3 4, 5 6, 7 8, 1 2), (1.2 3.6, 4.7 5.2, 3.9 8.8, 1.2 3.6))") + .value()), + Value(Geography(Point(Coordinate(4, 9)))), + Value(Geography(LineString( + std::vector{Coordinate(0, 1), Coordinate(2, 3), Coordinate(0, 1)}))), + Value(Geography(Polygon(std::vector>{ + std::vector{Coordinate(0, 1), + Coordinate(2, 3), + Coordinate(4, 5), + Coordinate(6, 7), + Coordinate(0, 1)}, + std::vector{ + Coordinate(2, 4), Coordinate(5, 6), Coordinate(3, 8), Coordinate(2, 4)}}))), }; for (const auto& val : values) { std::string buf; @@ -1156,12 +1171,17 @@ TEST(Value, Ctor) { Value vMap(Map({{"a", 9}, {"b", 10}})); EXPECT_TRUE(vMap.isMap()); // TODO(jie) Add more geography value test - Value vGeoPoint(Geography::fromWKT("POINT(0 1)").value()); - EXPECT_TRUE(vGeoPoint.isGeography()); - Value vGeoLine(Geography::fromWKT("LINESTRING(0 1,2 7)").value()); - EXPECT_TRUE(vGeoLine.isGeography()); - Value vGeoPolygon(Geography::fromWKT("POLYGON((0 1,2 3,4 5,6 7,0 1),(2 4,5 6,3 8,2 4))").value()); - EXPECT_TRUE(vGeoPolygon.isGeography()); + Value vGeogPoint{Geography(Point(Coordinate(3, 7)))}; + EXPECT_TRUE(vGeogPoint.isGeography()); + Value vGeogLine{ + Geography(LineString(std::vector{Coordinate(0, 1), Coordinate(2, 7)}))}; + EXPECT_TRUE(vGeogLine.isGeography()); + Value vGeogPolygon{Geography(Polygon(std::vector>{ + std::vector{ + Coordinate(0, 1), Coordinate(2, 3), Coordinate(4, 5), Coordinate(6, 7), Coordinate(0, 1)}, + std::vector{ + Coordinate(2, 4), Coordinate(5, 6), Coordinate(3, 8), Coordinate(2, 4)}}))}; + EXPECT_TRUE(vGeogPolygon.isGeography()); // Disabled // Lead to compile error // Value v(nullptr); diff --git a/src/common/function/CMakeLists.txt b/src/common/function/CMakeLists.txt index f7c01e8ebb3..79cbfafa8cf 100644 --- a/src/common/function/CMakeLists.txt +++ b/src/common/function/CMakeLists.txt @@ -6,6 +6,7 @@ nebula_add_library( function_manager_obj OBJECT FunctionManager.cpp + ../geo/GeoFunction.cpp ) nebula_add_library( diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index cd068125e6c..a25c38fdcf2 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -11,12 +11,18 @@ #include "common/base/Base.h" #include "common/datatypes/DataSet.h" #include "common/datatypes/Edge.h" +#include "common/datatypes/Geography.h" #include "common/datatypes/List.h" #include "common/datatypes/Map.h" #include "common/datatypes/Path.h" #include "common/datatypes/Set.h" #include "common/datatypes/Vertex.h" #include "common/expression/Expression.h" +#include "common/geo/GeoFunction.h" +#include "common/geo/io/wkb/WKBReader.h" +#include "common/geo/io/wkb/WKBWriter.h" +#include "common/geo/io/wkt/WKTReader.h" +#include "common/geo/io/wkt/WKTWriter.h" #include "common/thrift/ThriftTypes.h" #include "common/time/TimeUtils.h" #include "common/time/WallClock.h" @@ -302,6 +308,94 @@ std::unordered_map> FunctionManager::typ TypeSignature({Value::Type::DATASET, Value::Type::INT, Value::Type::STRING}, Value::Type::__EMPTY__), }}, + // These geo functions of the ST prefix follow the Simple Feature Access and SQL/MM + // specification. See https://www.ogc.org/standards/sfa, https://www.ogc.org/standards/sfs, and + // https://www.researchgate.net/publication/221323544_SQLMM_Spatial_-_The_Standard_to_Manage_Spatial_Data_in_a_Relational_Database_System + // geo constructors + {"st_point", + { + TypeSignature({Value::Type::FLOAT, Value::Type::FLOAT}, Value::Type::GEOGRAPHY), + TypeSignature({Value::Type::INT, Value::Type::INT}, Value::Type::GEOGRAPHY), + TypeSignature({Value::Type::FLOAT, Value::Type::INT}, Value::Type::GEOGRAPHY), + TypeSignature({Value::Type::INT, Value::Type::FLOAT}, Value::Type::GEOGRAPHY), + }}, + // geo parsers + {"st_geogfromtext", + { + TypeSignature({Value::Type::STRING}, Value::Type::GEOGRAPHY), + }}, + // This function requires binary data to be support first. + // {"st_geogfromwkb", + // { + // TypeSignature({Value::Type::STRING}, Value::Type::GEOGRAPHY), + // }}, + // geo formatters + {"st_astext", + { + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::STRING), + }}, + {"st_asbinary", + { + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::STRING), + }}, + // geo accessors + {"st_isvalid", + { + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::BOOL), + }}, + // TODO(jie) The geo predicates should follow the DE-9IM model. See + // https://en.wikipedia.org/wiki/DE-9IM + // geo predicates + {"st_intersects", + { + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY}, Value::Type::BOOL), + }}, + {"st_covers", + { + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY}, Value::Type::BOOL), + }}, + {"st_coveredby", + { + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY}, Value::Type::BOOL), + }}, + {"st_dwithin", + { + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY, Value::Type::FLOAT}, + Value::Type::BOOL), + }}, + // geo measures + {"st_distance", + { + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::GEOGRAPHY}, Value::Type::FLOAT), + }}, + // geo s2 functions + {"s2_cellidfrompoint", + { + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::INT), + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::INT}, Value::Type::INT), + }}, + {"s2_coveringcellids", + { + TypeSignature({Value::Type::GEOGRAPHY}, Value::Type::LIST), + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::INT}, Value::Type::LIST), + TypeSignature({Value::Type::GEOGRAPHY, Value::Type::INT, Value::Type::INT}, + Value::Type::LIST), + TypeSignature( + {Value::Type::GEOGRAPHY, Value::Type::INT, Value::Type::INT, Value::Type::INT}, + Value::Type::LIST), + TypeSignature({Value::Type::GEOGRAPHY, + Value::Type::INT, + Value::Type::INT, + Value::Type::INT, + Value::Type::INT}, + Value::Type::LIST), + TypeSignature({Value::Type::GEOGRAPHY, + Value::Type::INT, + Value::Type::INT, + Value::Type::INT, + Value::Type::FLOAT}, + Value::Type::LIST), + }}, }; // static @@ -2239,6 +2333,260 @@ FunctionManager::FunctionManager() { return folly::join(args[0].get().getStr(), result); }; } + // geo constructors + { + auto &attr = functions_["st_point"]; + attr.minArity_ = 2; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isNumeric() || !args[1].get().isNumeric()) { + return Value::kNullBadType; + } + double x = args[0].get().isInt() ? args[0].get().getInt() : args[0].get().getFloat(); + double y = args[1].get().isInt() ? args[1].get().getInt() : args[1].get().getFloat(); + Point point(Coordinate(x, y)); + return Geography(point); + }; + } + // geo parsers + { + auto &attr = functions_["st_geogfromtext"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isStr()) { + return Value::kNullBadType; + } + const std::string &wkt = args[0].get().getStr(); + // Parse a geography from the wkt, normalize it and then verify its validity. + auto geogRet = Geography::fromWKT(wkt, true, true); + if (!geogRet.ok()) { + LOG(ERROR) << "ST_GeogFromText error: " << geogRet.status(); + return Value::kNullBadData; + } + return std::move(geogRet).value(); + }; + } + // { + // auto &attr = functions_["st_geogfromwkb"]; + // attr.minArity_ = 1; + // attr.maxArity_ = 1; + // attr.isPure_ = true; + // attr.body_ = [](const auto &args) -> Value { + // if (!args[0].get().isStr()) { // wkb is byte sequence + // return Value::kNullBadType; + // } + // const std::string &wkb = args[0].get().getStr(); + // auto geogRet = Geography::fromWKB(wkb, true, true); + // if (!geogRet.ok()) { + // return Value::kNullBadData; + // } + // auto geog = std::move(geogRet).value(); + // return geog; + // }; + // } + // geo formatters + { + auto &attr = functions_["st_astext"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography()) { + return Value::kNullBadType; + } + const Geography &g = args[0].get().getGeography(); + return g.asWKT(); + }; + } + { + auto &attr = functions_["st_asbinary"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography()) { + return Value::kNullBadType; + } + const Geography &g = args[0].get().getGeography(); + return g.asWKBHex(); + }; + } + // geo accessors + { + auto &attr = functions_["st_isvalid"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography()) { + return Value::kNullBadType; + } + return args[0].get().getGeography().isValid(); + }; + } + // geo predicates + { + auto &attr = functions_["st_intersects"]; + attr.minArity_ = 2; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography() || !args[1].get().isGeography()) { + return Value::kNullBadType; + } + return geo::GeoFunction::intersects(args[0].get().getGeography(), + args[1].get().getGeography()); + }; + } + { + auto &attr = functions_["st_covers"]; + attr.minArity_ = 2; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography() || !args[1].get().isGeography()) { + return Value::kNullBadType; + } + return geo::GeoFunction::covers(args[0].get().getGeography(), args[1].get().getGeography()); + }; + } + { + auto &attr = functions_["st_coveredby"]; + attr.minArity_ = 2; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography() || !args[1].get().isGeography()) { + return Value::kNullBadType; + } + return geo::GeoFunction::coveredBy(args[0].get().getGeography(), + args[1].get().getGeography()); + }; + } + { + auto &attr = functions_["st_dwithin"]; + attr.minArity_ = 3; + attr.maxArity_ = 3; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography() || !args[1].get().isGeography() || + !args[2].get().isFloat()) { + return Value::kNullBadType; + } + return geo::GeoFunction::dWithin(args[0].get().getGeography(), + args[1].get().getGeography(), + args[2].get().getFloat(), + true); + }; + } + // geo measures + { + auto &attr = functions_["st_distance"]; + attr.minArity_ = 2; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography() || !args[1].get().isGeography()) { + return Value::kNullBadType; + } + return geo::GeoFunction::distance(args[0].get().getGeography(), args[1].get().getGeography()); + }; + } + // geo s2 functions + { + auto &attr = functions_["s2_cellidfrompoint"]; + attr.minArity_ = 1; + attr.maxArity_ = 2; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography()) { + return Value::kNullBadType; + } + int level = 30; + if (args.size() == 2) { + if (!args[1].get().isInt()) { + return Value::kNullBadType; + } + level = args[1].get().getInt(); + if (level < 0 || level > 30) { + return Value::kNullBadData; + } + } + // TODO(jie) Should return uint64_t Value + uint64_t cellId = geo::GeoFunction::s2CellIdFromPoint(args[0].get().getGeography(), level); + const char *tmp = reinterpret_cast(&cellId); + int64_t cellId2 = *reinterpret_cast(tmp); + return cellId2; + }; + } + { + auto &attr = functions_["s2_coveringcellids"]; + attr.minArity_ = 1; + attr.maxArity_ = 5; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isGeography()) { + return Value::kNullBadType; + } + int minLevel = 0; + int maxLevel = 30; + int maxCells = 8; + double bufferInMeters = 0.0; + if (args.size() > 1) { + if (!args[1].get().isInt()) { + return Value::kNullBadType; + } + minLevel = args[1].get().getInt(); + if (minLevel < 0 || minLevel > 30) { + return Value::kNullBadData; + } + } + if (args.size() > 2) { + if (!args[2].get().isInt()) { + return Value::kNullBadType; + } + maxLevel = args[2].get().getInt(); + if (maxLevel < 0 || maxLevel > 30) { + return Value::kNullBadData; + } + if (maxLevel < minLevel) { + return Value::kNullBadData; + } + } + if (args.size() > 3) { + if (!args[3].get().isInt()) { + return Value::kNullBadType; + } + maxCells = args[3].get().getInt(); + if (maxCells <= 0) { + return Value::kNullBadData; + } + } + if (args.size() > 4) { + if (!args[4].get().isNumeric()) { + return Value::kNullBadType; + } + bufferInMeters = args[4].get().isInt() ? args[4].get().getInt() : args[4].get().getFloat(); + if (bufferInMeters < 0.0) { + return Value::kNullBadData; + } + } + std::vector cellIds = geo::GeoFunction::s2CoveringCellIds( + args[0].get().getGeography(), minLevel, maxLevel, maxCells, bufferInMeters); + // TODO(jie) Should return uint64_t List + std::vector vals; + vals.reserve(cellIds.size()); + for (uint64_t cellId : cellIds) { + const char *tmp = reinterpret_cast(&cellId); + int64_t cellId2 = *reinterpret_cast(tmp); + vals.emplace_back(cellId2); + } + return List(vals); + }; + } } // NOLINT // static diff --git a/src/common/geo/CMakeLists.txt b/src/common/geo/CMakeLists.txt index 64129d9bc83..f7eafd34bb0 100644 --- a/src/common/geo/CMakeLists.txt +++ b/src/common/geo/CMakeLists.txt @@ -4,4 +4,4 @@ # attached with Common Clause Condition 1.0, found in the LICENSES directory. nebula_add_subdirectory(io) -# nebula_add_subdirectory(test) +nebula_add_subdirectory(test) diff --git a/src/common/geo/GeoFunction.cpp b/src/common/geo/GeoFunction.cpp new file mode 100644 index 00000000000..84bdc7ecb6d --- /dev/null +++ b/src/common/geo/GeoFunction.cpp @@ -0,0 +1,554 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/geo/GeoFunction.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nebula { +namespace geo { + +bool GeoFunction::intersects(const Geography& a, const Geography& b) { + auto aRegion = a.asS2(); + auto bRegion = b.asS2(); + if (!aRegion || !bRegion) { + return false; + } + + switch (a.shape()) { + case GeoShape::POINT: { + switch (b.shape()) { + case GeoShape::POINT: + return static_cast(aRegion.get()) + ->MayIntersect(S2Cell(static_cast(bRegion.get())->point())); + case GeoShape::LINESTRING: + return static_cast(bRegion.get()) + ->MayIntersect(S2Cell(static_cast(aRegion.get())->point())); + case GeoShape::POLYGON: + return static_cast(bRegion.get()) + ->MayIntersect(S2Cell(static_cast(aRegion.get())->point())); + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::LINESTRING: { + switch (b.shape()) { + case GeoShape::POINT: + return static_cast(aRegion.get()) + ->MayIntersect(S2Cell(static_cast(bRegion.get())->point())); + case GeoShape::LINESTRING: + return static_cast(aRegion.get()) + ->Intersects(static_cast(bRegion.get())); + case GeoShape::POLYGON: + return static_cast(bRegion.get()) + ->Intersects(*static_cast(aRegion.get())); + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::POLYGON: { + switch (b.shape()) { + case GeoShape::POINT: + return static_cast(aRegion.get()) + ->MayIntersect(S2Cell(static_cast(bRegion.get())->point())); + case GeoShape::LINESTRING: + return static_cast(aRegion.get()) + ->Intersects(*static_cast(bRegion.get())); + case GeoShape::POLYGON: + return static_cast(aRegion.get()) + ->Intersects(static_cast(bRegion.get())); + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + + return false; +} + +bool GeoFunction::covers(const Geography& a, const Geography& b) { + auto aRegion = a.asS2(); + auto bRegion = b.asS2(); + if (!aRegion || !bRegion) { + return false; + } + + switch (a.shape()) { + case GeoShape::POINT: { + switch (b.shape()) { + case GeoShape::POINT: + return static_cast(aRegion.get()) + ->Contains(static_cast(bRegion.get())->point()); + case GeoShape::LINESTRING: + case GeoShape::POLYGON: + return false; + case GeoShape::UNKNOWN: + default: + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + case GeoShape::LINESTRING: { + S2Polyline* aLine = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: + return aLine->MayIntersect(S2Cell(static_cast(bRegion.get())->point())); + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + if (aLine->NearlyCovers(*bLine, S1Angle::Radians(1e-15))) { + return true; + } + // LineString should covers its reverse + aLine->Reverse(); + return aLine->NearlyCovers(*bLine, S1Angle::Radians(1e-15)); + } + case GeoShape::POLYGON: + return false; + case GeoShape::UNKNOWN: + default: + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + case GeoShape::POLYGON: { + S2Polygon* aPolygon = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: + return aPolygon->Contains(static_cast(bRegion.get())->point()); + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + if (aPolygon->Contains(*bLine)) { + return true; + } + bLine->Reverse(); + return aPolygon->Contains(*bLine); + } + case GeoShape::POLYGON: + return aPolygon->Contains(static_cast(bRegion.get())); + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + + return false; +} + +bool GeoFunction::coveredBy(const Geography& a, const Geography& b) { return covers(b, a); } + +bool GeoFunction::dWithin(const Geography& a, const Geography& b, double distance, bool inclusive) { + auto aRegion = a.asS2(); + auto bRegion = b.asS2(); + if (!aRegion || !bRegion) { + return false; + } + + switch (a.shape()) { + case GeoShape::POINT: { + const S2Point& aPoint = static_cast(aRegion.get())->point(); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + double closestDistance = S2Earth::GetDistanceMeters(aPoint, bPoint); + return inclusive ? closestDistance <= distance : closestDistance < distance; + } + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + return s2PointAndS2PolylineAreWithinDistance(aPoint, bLine, distance, inclusive); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + return s2PointAndS2PolygonAreWithinDistance(aPoint, bPolygon, distance, inclusive); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::LINESTRING: { + S2Polyline* aLine = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + return s2PointAndS2PolylineAreWithinDistance(bPoint, aLine, distance, inclusive); + } + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + MutableS2ShapeIndex aIndex, bIndex; + aIndex.Add(std::make_unique(aLine)); + bIndex.Add(std::make_unique(bLine)); + S2ClosestEdgeQuery query(&aIndex); + S2ClosestEdgeQuery::ShapeIndexTarget target(&bIndex); + if (inclusive) { + return query.IsDistanceLessOrEqual( + &target, S2Earth::ToChordAngle(util::units::Meters(distance))); + } + return query.IsDistanceLess(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + return s2PolylineAndS2PolygonAreWithinDistance(aLine, bPolygon, distance, inclusive); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::POLYGON: { + S2Polygon* aPolygon = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + return s2PointAndS2PolygonAreWithinDistance(bPoint, aPolygon, distance, inclusive); + } + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + return s2PolylineAndS2PolygonAreWithinDistance(bLine, aPolygon, distance, inclusive); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + S2ClosestEdgeQuery query(&aPolygon->index()); + S2ClosestEdgeQuery::ShapeIndexTarget target(&bPolygon->index()); + if (inclusive) { + return query.IsDistanceLessOrEqual( + &target, S2Earth::ToChordAngle(util::units::Meters(distance))); + } + return query.IsDistanceLess(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return false; + } + } + + return false; +} + +double GeoFunction::distance(const Geography& a, const Geography& b) { + auto aRegion = a.asS2(); + auto bRegion = b.asS2(); + if (!aRegion || !bRegion) { + return -1.0; + } + + switch (a.shape()) { + case GeoShape::POINT: { + const S2Point& aPoint = static_cast(aRegion.get())->point(); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + return S2Earth::GetDistanceMeters(aPoint, bPoint); + } + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + return distanceOfS2PolylineWithS2Point(bLine, aPoint); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + return distanceOfS2PolygonWithS2Point(bPolygon, aPoint); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return -1.0; + } + } + } + case GeoShape::LINESTRING: { + S2Polyline* aLine = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + return distanceOfS2PolylineWithS2Point(aLine, bPoint); + } + case GeoShape::LINESTRING: { + const S2Polyline* bLine = static_cast(bRegion.get()); + MutableS2ShapeIndex aIndex, bIndex; + aIndex.Add(std::make_unique(aLine)); + bIndex.Add(std::make_unique(bLine)); + S2ClosestEdgeQuery query(&aIndex); + S2ClosestEdgeQuery::ShapeIndexTarget target(&bIndex); + return S2Earth::ToMeters(query.GetDistance(&target)); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + return distanceOfS2PolygonWithS2Polyline(bPolygon, aLine); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return -1.0; + } + } + } + case GeoShape::POLYGON: { + S2Polygon* aPolygon = static_cast(aRegion.get()); + switch (b.shape()) { + case GeoShape::POINT: { + const S2Point& bPoint = static_cast(bRegion.get())->point(); + return distanceOfS2PolygonWithS2Point(aPolygon, bPoint); + } + case GeoShape::LINESTRING: { + S2Polyline* bLine = static_cast(bRegion.get()); + return distanceOfS2PolygonWithS2Polyline(aPolygon, bLine); + } + case GeoShape::POLYGON: { + S2Polygon* bPolygon = static_cast(bRegion.get()); + S2ClosestEdgeQuery query(&aPolygon->index()); + S2ClosestEdgeQuery::ShapeIndexTarget target(&bPolygon->index()); + return S2Earth::ToMeters(query.GetDistance(&target)); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return -1.0; + } + } + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return -1.0; + } + } + + return -1.0; +} + +uint64_t GeoFunction::s2CellIdFromPoint(const Geography& a, int level) { + auto aRegion = a.asS2(); + if (!aRegion) { + return -1; + } + if (level < 0 || level > 30) { + LOG(ERROR) << "s2 level argument must be in range [0,30], got " << level; + return -1; + } + + switch (a.shape()) { + case GeoShape::POINT: { + const auto& s2Point = static_cast(aRegion.get())->point(); + S2CellId cellId(s2Point); + return level == 30 ? cellId.id() : cellId.parent(level).id(); + } + case GeoShape::LINESTRING: + case GeoShape::POLYGON: + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) << "S2_CellIdFromPoint only accept Point"; + return -1; + } + } + + return false; +} + +std::vector GeoFunction::s2CoveringCellIds( + const Geography& a, int minLevel, int maxLevel, int maxCells, double bufferInMeters) { + auto aRegion = a.asS2(); + if (!aRegion) { + return {}; + } + if (minLevel < 0 || minLevel > 30) { + LOG(ERROR) << "s2 min_level argument must be in range [0,30], got " << minLevel; + return {}; + } + if (maxLevel < 0 || maxLevel > 30) { + LOG(ERROR) << "s2 max_level argument must be in range [0,30], got " << maxLevel; + return {}; + } + if (maxCells <= 0) { + LOG(ERROR) << "s2 max_cells argument must be greater than or equal to 0, got " << maxCells; + return {}; + } + if (bufferInMeters < 0.0) { + LOG(ERROR) << "s2 buffer_meters argument must be nonnegative, got " << bufferInMeters; + return {}; + } + + S2RegionCoverer::Options opts; + opts.set_min_level(minLevel); + opts.set_max_level(maxLevel); + opts.set_max_cells(maxCells); + + if (bufferInMeters == 0.0) { + return coveringCellIds(*aRegion, opts); + } + + S1Angle radius = S2Earth::ToAngle(util::units::Meters(bufferInMeters)); + + switch (a.shape()) { + case GeoShape::POINT: { + const S2Point& gPoint = static_cast(aRegion.get())->point(); + S2Cap gCap(gPoint, radius); + return coveringCellIds(gCap, opts); + } + case GeoShape::LINESTRING: { + S2Polyline* gLine = static_cast(aRegion.get()); + MutableS2ShapeIndex index; + index.Add(std::make_unique(gLine)); + S2ShapeIndexBufferedRegion gBuffer(&index, radius); + return coveringCellIds(gBuffer, opts); + } + case GeoShape::POLYGON: { + S2Polygon* gPolygon = static_cast(aRegion.get()); + S2ShapeIndexBufferedRegion gBuffer(&gPolygon->index(), radius); + return coveringCellIds(gBuffer, opts); + } + case GeoShape::UNKNOWN: + default: { + LOG(ERROR) + << "Geography shapes other than Point/LineString/Polygon are not currently supported"; + return {}; + } + } + + return {}; +} + +std::vector GeoFunction::coveringCellIds(const S2Region& r, + const S2RegionCoverer::Options& opts) { + S2RegionCoverer rc(opts); + std::vector covering; + rc.GetCovering(r, &covering); + std::vector cellIds; + cellIds.reserve(covering.size()); + for (auto& cellId : covering) { + cellIds.push_back(cellId.id()); + } + return cellIds; +} + +double GeoFunction::distanceOfS2PolylineWithS2Point(const S2Polyline* aLine, + const S2Point& bPoint) { + int tmp; + S2Point cloestPointOnLine = aLine->Project(bPoint, &tmp); + return S2Earth::GetDistanceMeters(cloestPointOnLine, bPoint); +} + +double GeoFunction::distanceOfS2PolygonWithS2Polyline(const S2Polygon* aPolygon, + const S2Polyline* bLine) { + S2ClosestEdgeQuery query(&aPolygon->index()); + MutableS2ShapeIndex bIndex; + bIndex.Add(std::make_unique(bLine)); + S2ClosestEdgeQuery::ShapeIndexTarget target(&bIndex); + return S2Earth::ToMeters(query.GetDistance(&target)); +} + +double GeoFunction::distanceOfS2PolygonWithS2Point(const S2Polygon* aPolygon, + const S2Point& bPoint) { + return S2Earth::ToMeters(aPolygon->GetDistance(bPoint)); +} + +bool GeoFunction::s2PointAndS2PolylineAreWithinDistance(const S2Point& aPoint, + const S2Polyline* bLine, + double distance, + bool inclusive) { + MutableS2ShapeIndex bIndex; + bIndex.Add(std::make_unique(bLine)); + S2ClosestEdgeQuery query(&bIndex); + S2ClosestEdgeQuery::PointTarget target(aPoint); + if (inclusive) { + return query.IsDistanceLessOrEqual(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); + } else { + return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); + } +} + +bool GeoFunction::s2PointAndS2PolygonAreWithinDistance(const S2Point& aPoint, + const S2Polygon* bPolygon, + double distance, + bool inclusive) { + S2ClosestEdgeQuery query(&bPolygon->index()); + S2ClosestEdgeQuery::PointTarget target(aPoint); + if (inclusive) { + return query.IsDistanceLessOrEqual(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); + } else { + return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); + } +} + +bool GeoFunction::s2PolylineAndS2PolygonAreWithinDistance(const S2Polyline* aLine, + const S2Polygon* bPolygon, + double distance, + bool inclusive) { + MutableS2ShapeIndex aIndex; + aIndex.Add(std::make_unique(aLine)); + S2ClosestEdgeQuery::ShapeIndexTarget target(&aIndex); + S2ClosestEdgeQuery query(&bPolygon->index()); + if (inclusive) { + return query.IsDistanceLessOrEqual(&target, + S2Earth::ToChordAngle(util::units::Meters(distance))); + } else { + return query.IsDistanceLess(&target, S2Earth::ToChordAngle(util::units::Meters(distance))); + } +} + +} // namespace geo +} // namespace nebula diff --git a/src/common/geo/GeoFunction.h b/src/common/geo/GeoFunction.h new file mode 100644 index 00000000000..5b37a998c15 --- /dev/null +++ b/src/common/geo/GeoFunction.h @@ -0,0 +1,73 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include + +#include "common/datatypes/Geography.h" + +namespace nebula { +namespace geo { + +class GeoFunction { + public: + // Returns true if any point in the set that comprises A is also a member of the set of points + // that + // make up B. + static bool intersects(const Geography& a, const Geography& b); + + // Returns true if no point in b lies exterior of b. + // The difference between ST_Covers, ST_Contains and ST_ContainsProperly, see + // http://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html + static bool covers(const Geography& a, const Geography& b); + static bool coveredBy(const Geography& a, const Geography& b); + + // Returns true if any of a is within distance meters of b. + // We don't need to find the closest points. We just need to find the first point pair whose + // distance is less than or less equal than the given distance. (Early quit) + static bool dWithin(const Geography& a, const Geography& b, double distance, bool inclusive); + + // Return the closest distance in meters of a and b. + static double distance(const Geography& a, const Geography& b); + + static uint64_t s2CellIdFromPoint(const Geography& a, int level = 30); + + static std::vector s2CoveringCellIds(const Geography& a, + int minLevel = 0, + int maxLevel = 30, + int maxCells = 8, + double bufferInMeters = 0.0); + + private: + static std::vector coveringCellIds(const S2Region& r, + const S2RegionCoverer::Options& opts); + + static double distanceOfS2PolylineWithS2Point(const S2Polyline* aLine, const S2Point& bPoint); + + static double distanceOfS2PolygonWithS2Polyline(const S2Polygon* aPolygon, + const S2Polyline* bLine); + + static double distanceOfS2PolygonWithS2Point(const S2Polygon* aPolygon, const S2Point& bPoint); + + static bool s2PointAndS2PolylineAreWithinDistance(const S2Point& aPoint, + const S2Polyline* bLine, + double distance, + bool inclusive); + + static bool s2PointAndS2PolygonAreWithinDistance(const S2Point& aPoint, + const S2Polygon* bPolygon, + double distance, + bool inclusive); + + static bool s2PolylineAndS2PolygonAreWithinDistance(const S2Polyline* aLine, + const S2Polygon* bPolygon, + double distance, + bool inclusive); +}; + +} // namespace geo +} // namespace nebula diff --git a/src/common/geo/GeoShape.h b/src/common/geo/GeoShape.h deleted file mode 100644 index 6622433c408..00000000000 --- a/src/common/geo/GeoShape.h +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License, - * attached with Common Clause Condition 1.0, found in the LICENSES directory. - */ - -#pragma once - -namespace nebula { - -enum class GeoShape : uint32_t { - UNKNOWN = 0, // illegal - POINT = 1, - LINESTRING = 2, - POLYGON = 3, -}; - -} // namespace nebula diff --git a/src/common/geo/GeoUtils.h b/src/common/geo/GeoUtils.h index b3d0c083786..7424399f76c 100644 --- a/src/common/geo/GeoUtils.h +++ b/src/common/geo/GeoUtils.h @@ -11,80 +11,42 @@ #include "common/base/StatusOr.h" #include "common/datatypes/Geography.h" -#include "common/geo/io/Geometry.h" namespace nebula { +namespace geo { class GeoUtils final { public: - static std::unique_ptr s2RegionFromGeomtry(const Geometry& geom) { - switch (geom.shape()) { + static std::unique_ptr s2RegionFromGeography(Geography geog) { + switch (geog.shape()) { case GeoShape::POINT: { - const auto& point = geom.point(); + const auto& point = geog.point(); auto s2Point = s2PointFromCoordinate(point.coord); - if (!S2::IsUnitLength(s2Point)) { // S2LatLng::IsValid() - return nullptr; - } return std::make_unique(s2Point); } case GeoShape::LINESTRING: { - const auto& lineString = geom.lineString(); + const auto& lineString = geog.lineString(); auto coordList = lineString.coordList; - if (UNLIKELY(coordList.size() < 2)) { - LOG(ERROR) << "LineString must have at least 2 coordinates"; - return nullptr; - } - removeAdjacentDuplicateCoordinates(coordList); - if (coordList.size() < 2) { - LOG(ERROR) - << "Invalid LineString, adjacent coordinates must not be identical or antipodal."; - return nullptr; - } - auto s2Points = s2PointsFromCoordinateList(coordList); auto s2Polyline = std::make_unique(s2Points, S2Debug::DISABLE); - if (!s2Polyline->IsValid()) { - LOG(ERROR) << "Invalid S2Polyline"; - return nullptr; - } return s2Polyline; } case GeoShape::POLYGON: { - const auto& polygon = geom.polygon(); - uint32_t numRings = polygon.numRings(); + const auto& polygon = geog.polygon(); + uint32_t numCoordList = polygon.numCoordList(); std::vector> s2Loops; - s2Loops.reserve(numRings); - for (size_t i = 0; i < numRings; ++i) { + s2Loops.reserve(numCoordList); + for (size_t i = 0; i < numCoordList; ++i) { auto coordList = polygon.coordListList[i]; - if (UNLIKELY(coordList.size() < 4)) { - LOG(ERROR) << "Polygon's LinearRing must have at least 4 coordinates"; - return nullptr; - } - if (UNLIKELY(isLoopClosed(coordList))) { - return nullptr; - } - removeAdjacentDuplicateCoordinates(coordList); - if (coordList.size() < 4) { - LOG(ERROR) - << "Invalid linearRing in polygon, must have at least 4 distinct coordinates."; - return nullptr; + if (!coordList.empty()) { + coordList.pop_back(); // Remove redundant last coordinate } - coordList.pop_back(); // Remove redundant last coordinate auto s2Points = s2PointsFromCoordinateList(coordList); auto s2Loop = std::make_unique(std::move(s2Points), S2Debug::DISABLE); - if (!s2Loop->IsValid()) { // TODO(jie) May not need to check S2loop here, S2Polygon's - // IsValid() will check its loops - LOG(ERROR) << "Invalid S2Loop"; - return nullptr; - } s2Loop->Normalize(); // All loops must be oriented CCW(counterclockwise) for S2 s2Loops.emplace_back(std::move(s2Loop)); } auto s2Polygon = std::make_unique(std::move(s2Loops), S2Debug::DISABLE); - if (!s2Polygon->IsValid()) { // Exterior loop must contain other interior loops - LOG(ERROR) << "Invalid S2Polygon"; - return nullptr; - } return s2Polygon; } default: @@ -113,13 +75,6 @@ class GeoUtils final { return s2Points; } - static bool isLoopClosed(const std::vector& coordList) { - if (coordList.size() < 2) { - return false; - } - return coordList.front() == coordList.back(); - } - static void removeAdjacentDuplicateCoordinates(std::vector& coordList) { if (coordList.size() < 2) { return; @@ -130,4 +85,5 @@ class GeoUtils final { } }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/CMakeLists.txt b/src/common/geo/io/CMakeLists.txt index 180b9b77034..0987dab88f9 100644 --- a/src/common/geo/io/CMakeLists.txt +++ b/src/common/geo/io/CMakeLists.txt @@ -25,7 +25,8 @@ nebula_add_library( wkt/WKTWriter.cpp wkb/WKBReader.cpp wkb/WKBWriter.cpp + wkb/ByteOrderDataIOStream.cpp ) nebula_add_subdirectory(wkt) -# nebula_add_subdirectory(wkb) +nebula_add_subdirectory(wkb) diff --git a/src/common/geo/io/Geometry.h b/src/common/geo/io/Geometry.h deleted file mode 100644 index 174a4f32e48..00000000000 --- a/src/common/geo/io/Geometry.h +++ /dev/null @@ -1,102 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License, - * attached with Common Clause Condition 1.0, found in the LICENSES directory. - */ - -#pragma once - -#include -#include -#include -#include - -#include "common/base/Base.h" -#include "common/geo/GeoShape.h" - -namespace nebula { - -// These Geometry structs are just for parsing wkt/wkb -struct Coordinate { - double x, y; - - Coordinate() = default; - Coordinate(double lng, double lat) : x(lng), y(lat) {} - - // TODO(jie) compare double correctly - bool operator==(const Coordinate& rhs) const { return x == rhs.x && y == rhs.y; } - bool operator!=(const Coordinate& rhs) const { return !(*this == rhs); } - - static bool isValidLng(double lng) { return std::abs(lng) <= 180; } - - static bool isValidLat(double lat) { return std::abs(lat) <= 90; } -}; - -struct Point { - Coordinate coord; - - explicit Point(const Coordinate& v) : coord(v) {} - explicit Point(Coordinate&& v) : coord(std::move(v)) {} - ~Point() {} -}; - -struct LineString { - std::vector coordList; - - explicit LineString(const std::vector& v) : coordList(v) {} - explicit LineString(std::vector&& v) : coordList(std::move(v)) {} - ~LineString() {} - - uint32_t numCoords() const { return coordList.size(); } -}; - -struct Polygon { - std::vector> coordListList; - - explicit Polygon(const std::vector>& v) : coordListList(v) {} - explicit Polygon(std::vector>&& v) : coordListList(std::move(v)) {} - ~Polygon() {} - - uint32_t numRings() const { return coordListList.size(); } - uint32_t numInteriorRing() const { return numRings() - 1; } -}; - -struct Geometry { - std::variant geom; - - Geometry(const Point& v) : geom(v) {} // NOLINT - Geometry(Point&& v) : geom(std::move(v)) {} // NOLINT - Geometry(const LineString& v) : geom(v) {} // NOLINT - Geometry(LineString&& v) : geom(std::move(v)) {} // NOLINT - Geometry(const Polygon& v) : geom(v) {} // NOLINT - Geometry(Polygon&& v) : geom(std::move(v)) {} // NOLINT - - GeoShape shape() const { - switch (geom.index()) { - case 0: - return GeoShape::POINT; - case 1: - return GeoShape::LINESTRING; - case 2: - return GeoShape::POLYGON; - default: - return GeoShape::UNKNOWN; - } - } - const Point& point() const { - CHECK(std::holds_alternative(geom)); - return std::get(geom); - } - - const LineString& lineString() const { - CHECK(std::holds_alternative(geom)); - return std::get(geom); - } - - const Polygon& polygon() const { - CHECK(std::holds_alternative(geom)); - return std::get(geom); - } -}; - -} // namespace nebula diff --git a/src/common/geo/io/wkb/ByteOrder.h b/src/common/geo/io/wkb/ByteOrder.h index 668461d72bf..69138142f43 100644 --- a/src/common/geo/io/wkb/ByteOrder.h +++ b/src/common/geo/io/wkb/ByteOrder.h @@ -6,9 +6,12 @@ #pragma once +#include + #include "common/base/Base.h" namespace nebula { +namespace geo { enum class ByteOrder : uint8_t { BigEndian = 0, @@ -24,27 +27,19 @@ struct ByteOrderData { static uint32_t getUint32(const uint8_t *buf, ByteOrder byteOrder) { if (byteOrder == ByteOrder::BigEndian) { - return ((uint32_t)(buf[0] & 0xff) << 24) | ((uint32_t)(buf[1] & 0xff) << 16) | - ((uint32_t)(buf[2] & 0xff) << 8) | ((uint32_t)(buf[3] & 0xff)); + return boost::endian::load_big_u32(buf); } else { DCHECK(byteOrder == ByteOrder::LittleEndian); - return ((uint32_t)(buf[3] & 0xff) << 24) | ((uint32_t)(buf[2] & 0xff) << 16) | - ((uint32_t)(buf[1] & 0xff) << 8) | ((uint32_t)(buf[0] & 0xff)); + return boost::endian::load_little_u32(buf); } } static uint64_t getUint64(const uint8_t *buf, ByteOrder byteOrder) { if (byteOrder == ByteOrder::BigEndian) { - return (uint64_t)(buf[0]) << 56 | (uint64_t)(buf[1] & 0xff) << 48 | - (uint64_t)(buf[2] & 0xff) << 40 | (uint64_t)(buf[3] & 0xff) << 32 | - (uint64_t)(buf[4] & 0xff) << 24 | (uint64_t)(buf[5] & 0xff) << 16 | - (uint64_t)(buf[6] & 0xff) << 8 | (uint64_t)(buf[7] & 0xff); + return boost::endian::load_big_u64(buf); } else { DCHECK(byteOrder == ByteOrder::LittleEndian); - return (uint64_t)(buf[7]) << 56 | (uint64_t)(buf[6] & 0xff) << 48 | - (uint64_t)(buf[5] & 0xff) << 40 | (uint64_t)(buf[4] & 0xff) << 32 | - (uint64_t)(buf[3] & 0xff) << 24 | (uint64_t)(buf[2] & 0xff) << 16 | - (uint64_t)(buf[1] & 0xff) << 8 | (uint64_t)(buf[0] & 0xff); + return boost::endian::load_little_u64(buf); } } @@ -54,6 +49,31 @@ struct ByteOrderData { std::memcpy(&ret, &v, sizeof(double)); return ret; } + + static void putUint32(uint8_t *buf, ByteOrder byteOrder, uint32_t v) { + if (byteOrder == ByteOrder::BigEndian) { + boost::endian::store_big_u32(buf, v); + } else { + DCHECK(byteOrder == ByteOrder::LittleEndian); + boost::endian::store_little_u32(buf, v); + } + } + + static void putUint64(uint8_t *buf, ByteOrder byteOrder, uint64_t v) { + if (byteOrder == ByteOrder::BigEndian) { + boost::endian::store_big_u64(buf, v); + } else { + DCHECK(byteOrder == ByteOrder::LittleEndian); + boost::endian::store_little_u64(buf, v); + } + } + + static void putDouble(uint8_t *buf, ByteOrder byteOrder, double v) { + const char *c = reinterpret_cast(&v); + uint64_t v2 = *reinterpret_cast(c); + putUint64(buf, byteOrder, v2); + } }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkb/ByteOrderDataIOStream.cpp b/src/common/geo/io/wkb/ByteOrderDataIOStream.cpp new file mode 100644 index 00000000000..6e7620f51eb --- /dev/null +++ b/src/common/geo/io/wkb/ByteOrderDataIOStream.cpp @@ -0,0 +1,52 @@ +/* Copyright (c) 2018 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/geo/io/wkb/ByteOrderDataIOStream.h" + +namespace nebula { +namespace geo { + +StatusOr ByteOrderDataInStream::readUint8() { + stream_.read(reinterpret_cast(buf_), 1); + if (stream_.eof()) { + return Status::Error("Unexpected EOF when reading uint8_t"); + } + return buf_[0]; +} + +StatusOr ByteOrderDataInStream::readUint32() { + stream_.read(reinterpret_cast(buf_), 4); + if (stream_.eof()) { + return Status::Error("Unexpected EOF when reading uint32_t"); + } + return ByteOrderData::getUint32(buf_, byteOrder_); +} + +StatusOr ByteOrderDataInStream::readDouble() { + stream_.read(reinterpret_cast(buf_), 8); + if (stream_.eof()) { + return Status::Error("Unexpected EOF when reading double"); + } + return ByteOrderData::getDouble(buf_, byteOrder_); +} + +void ByteOrderDataOutStream::writeUint8(uint8_t v) { + buf_[0] = v; + stream_.write(reinterpret_cast(buf_), 1); +} + +void ByteOrderDataOutStream::writeUint32(uint32_t v) { + ByteOrderData::putUint32(buf_, byteOrder_, v); + stream_.write(reinterpret_cast(buf_), 4); +} + +void ByteOrderDataOutStream::writeDouble(double v) { + ByteOrderData::putDouble(buf_, byteOrder_, v); + stream_.write(reinterpret_cast(buf_), 8); +} + +} // namespace geo +} // namespace nebula diff --git a/src/common/geo/io/wkb/ByteOrderDataIOStream.h b/src/common/geo/io/wkb/ByteOrderDataIOStream.h new file mode 100644 index 00000000000..c8b47e7e09c --- /dev/null +++ b/src/common/geo/io/wkb/ByteOrderDataIOStream.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include + +#include "common/base/Base.h" +#include "common/base/StatusOr.h" +#include "common/geo/io/wkb/ByteOrder.h" + +namespace nebula { +namespace geo { + +class ByteOrderDataInStream { + public: + ByteOrderDataInStream() = default; + explicit ByteOrderDataInStream(const std::string& s) : stream_(s) {} + + ~ByteOrderDataInStream() = default; + + std::string str() const { return stream_.str(); } + + void setInput(const std::string& s) { stream_.str(s); } + + void setByteOrder(ByteOrder order) { byteOrder_ = order; } + + StatusOr readUint8(); + + StatusOr readUint32(); + + StatusOr readDouble(); + + private: + ByteOrder byteOrder_; + std::istringstream stream_; + unsigned char buf_[8]; +}; + +class ByteOrderDataOutStream { + public: + ByteOrderDataOutStream() = default; + + ~ByteOrderDataOutStream() = default; + + std::string str() const { return stream_.str(); } + + void setByteOrder(ByteOrder order) { byteOrder_ = order; } + + void writeUint8(uint8_t v); + + void writeUint32(uint32_t v); + + void writeDouble(double v); + + private: + ByteOrder byteOrder_; + std::ostringstream stream_; + unsigned char buf_[8]; +}; + +} // namespace geo +} // namespace nebula diff --git a/src/common/geo/io/wkb/CMakeLists.txt b/src/common/geo/io/wkb/CMakeLists.txt new file mode 100644 index 00000000000..f359a95626a --- /dev/null +++ b/src/common/geo/io/wkb/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. + +nebula_add_subdirectory(test) diff --git a/src/common/geo/io/wkb/WKBReader.cpp b/src/common/geo/io/wkb/WKBReader.cpp index 8c3223b34bb..fa70f4729df 100644 --- a/src/common/geo/io/wkb/WKBReader.cpp +++ b/src/common/geo/io/wkb/WKBReader.cpp @@ -7,46 +7,29 @@ #include "common/geo/io/wkb/WKBReader.h" namespace nebula { +namespace geo { -StatusOr WKBReader::read(const std::string &wkb) const { - const uint8_t *beg = reinterpret_cast(wkb.data()); - const uint8_t *end = beg + wkb.size(); - return read(beg, end); -} +StatusOr WKBReader::read(const std::string &wkb) { + is_.setInput(wkb); -StatusOr WKBReader::read(const uint8_t *&beg, const uint8_t *end) const { - auto byteOrderRet = readByteOrder(beg, end); + auto byteOrderRet = readByteOrder(); NG_RETURN_IF_ERROR(byteOrderRet); ByteOrder byteOrder = byteOrderRet.value(); + is_.setByteOrder(byteOrder); - auto shapeTypeRet = readShapeType(beg, end, byteOrder); + auto shapeTypeRet = readShapeType(); NG_RETURN_IF_ERROR(shapeTypeRet); GeoShape shapeType = shapeTypeRet.value(); switch (shapeType) { case GeoShape::POINT: { - auto coordRet = readCoordinate(beg, end, byteOrder); - NG_RETURN_IF_ERROR(coordRet); - Coordinate coord = coordRet.value(); - return Point(coord); + return readPoint(); } case GeoShape::LINESTRING: { - auto numPointsRet = readUint32(beg, end, byteOrder); - NG_RETURN_IF_ERROR(numPointsRet); - uint32_t numPoints = numPointsRet.value(); - auto coordListRet = readCoordinateList(beg, end, byteOrder, numPoints); - NG_RETURN_IF_ERROR(coordListRet); - std::vector coordList = coordListRet.value(); - return LineString(coordList); + return readLineString(); } case GeoShape::POLYGON: { - auto numRingsRet = readUint32(beg, end, byteOrder); - NG_RETURN_IF_ERROR(numRingsRet); - uint32_t numRings = numRingsRet.value(); - auto coordListListRet = readCoordinateListList(beg, end, byteOrder, numRings); - NG_RETURN_IF_ERROR(coordListListRet); - std::vector> coordListList = coordListListRet.value(); - return Polygon(coordListList); + return readPolygon(); } default: LOG(FATAL) @@ -56,8 +39,35 @@ StatusOr WKBReader::read(const uint8_t *&beg, const uint8_t *end) cons } } -StatusOr WKBReader::readByteOrder(const uint8_t *&beg, const uint8_t *end) const { - auto vRet = readUint8(beg, end); +StatusOr WKBReader::readPoint() { + auto coordRet = readCoordinate(); + NG_RETURN_IF_ERROR(coordRet); + Coordinate coord = coordRet.value(); + return Point(std::move(coord)); +} + +StatusOr WKBReader::readLineString() { + auto numCoordRet = is_.readUint32(); + NG_RETURN_IF_ERROR(numCoordRet); + uint32_t numCoord = numCoordRet.value(); + auto coordListRet = readCoordinateList(numCoord); + NG_RETURN_IF_ERROR(coordListRet); + std::vector coordList = coordListRet.value(); + return LineString(std::move(coordList)); +} + +StatusOr WKBReader::readPolygon() { + auto numCoordListRet = is_.readUint32(); + NG_RETURN_IF_ERROR(numCoordListRet); + uint32_t numCoordList = numCoordListRet.value(); + auto coordListListRet = readCoordinateListList(numCoordList); + NG_RETURN_IF_ERROR(coordListListRet); + std::vector> coordListList = coordListListRet.value(); + return Polygon(std::move(coordListList)); +} + +StatusOr WKBReader::readByteOrder() { + auto vRet = is_.readUint8(); NG_RETURN_IF_ERROR(vRet); uint8_t v = vRet.value(); if (v != 0 && v != 1) { @@ -67,10 +77,8 @@ StatusOr WKBReader::readByteOrder(const uint8_t *&beg, const uint8_t return byteOrder; } -StatusOr WKBReader::readShapeType(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const { - auto vRet = readUint32(beg, end, byteOrder); +StatusOr WKBReader::readShapeType() { + auto vRet = is_.readUint32(); NG_RETURN_IF_ERROR(vRet); uint32_t v = vRet.value(); if (v != 1 && v != 2 && v != 3) { @@ -80,81 +88,42 @@ StatusOr WKBReader::readShapeType(const uint8_t *&beg, return shapeType; } -StatusOr WKBReader::readCoordinate(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const { - auto xRet = readDouble(beg, end, byteOrder); +StatusOr WKBReader::readCoordinate() { + auto xRet = is_.readDouble(); NG_RETURN_IF_ERROR(xRet); double x = xRet.value(); - auto yRet = readDouble(beg, end, byteOrder); + auto yRet = is_.readDouble(); NG_RETURN_IF_ERROR(yRet); double y = yRet.value(); return Coordinate(x, y); } -StatusOr> WKBReader::readCoordinateList(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder, - uint32_t num) const { +StatusOr> WKBReader::readCoordinateList(uint32_t num) { std::vector coordList; coordList.reserve(num); for (size_t i = 0; i < num; ++i) { - auto coordRet = readCoordinate(beg, end, byteOrder); + auto coordRet = readCoordinate(); NG_RETURN_IF_ERROR(coordRet); Coordinate coord = coordRet.value(); - coordList.emplace_back(coord); + coordList.emplace_back(std::move(coord)); } return coordList; } -StatusOr>> WKBReader::readCoordinateListList( - const uint8_t *&beg, const uint8_t *end, ByteOrder byteOrder, uint32_t num) const { +StatusOr>> WKBReader::readCoordinateListList(uint32_t num) { std::vector> coordListList; coordListList.reserve(num); for (size_t i = 0; i < num; ++i) { - auto numPointsRet = readUint32(beg, end, byteOrder); - NG_RETURN_IF_ERROR(numPointsRet); - uint32_t numPoints = numPointsRet.value(); - auto coordListRet = readCoordinateList(beg, end, byteOrder, numPoints); + auto numCoordRet = is_.readUint32(); + NG_RETURN_IF_ERROR(numCoordRet); + uint32_t numCoord = numCoordRet.value(); + auto coordListRet = readCoordinateList(numCoord); NG_RETURN_IF_ERROR(coordListRet); std::vector coordList = coordListRet.value(); - coordListList.emplace_back(coordList); + coordListList.emplace_back(std::move(coordList)); } return coordListList; } -StatusOr WKBReader::readUint8(const uint8_t *&beg, const uint8_t *end) const { - auto requiredSize = static_cast(sizeof(uint8_t)); - if (end - beg < requiredSize) { - return Status::Error("Unable to parse uint8_t"); - } - uint8_t v = *beg; - beg += requiredSize; - return v; -} - -StatusOr WKBReader::readUint32(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const { - auto requiredSize = static_cast(sizeof(uint32_t)); - if (end - beg < requiredSize) { - return Status::Error("Unable to parse uint32_t"); - } - uint32_t v = ByteOrderData::getUint32(beg, byteOrder); - beg += requiredSize; - return v; -} - -StatusOr WKBReader::readDouble(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const { - auto requiredSize = static_cast(sizeof(double)); - if (end - beg < requiredSize) { - return Status::Error("Unable to parse double"); - } - double v = ByteOrderData::getDouble(beg, byteOrder); - beg += requiredSize; - return v; -} - +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkb/WKBReader.h b/src/common/geo/io/wkb/WKBReader.h index 597d3dfe50a..5d2a710cab6 100644 --- a/src/common/geo/io/wkb/WKBReader.h +++ b/src/common/geo/io/wkb/WKBReader.h @@ -8,10 +8,12 @@ #include "common/base/Base.h" #include "common/base/StatusOr.h" -#include "common/geo/io/Geometry.h" +#include "common/datatypes/Geography.h" #include "common/geo/io/wkb/ByteOrder.h" +#include "common/geo/io/wkb/ByteOrderDataIOStream.h" namespace nebula { +namespace geo { class WKBReader { public: @@ -19,35 +21,28 @@ class WKBReader { ~WKBReader() {} - // TODO(jie) Check the validity of geometry when reading the wkb - StatusOr read(const std::string &wkb) const; + StatusOr read(const std::string &wkb); - StatusOr read(const uint8_t *&beg, const uint8_t *end) const; + private: + StatusOr readPoint(); - StatusOr readByteOrder(const uint8_t *&beg, const uint8_t *end) const; + StatusOr readLineString(); - StatusOr readShapeType(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const; + StatusOr readPolygon(); - StatusOr readCoordinate(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder) const; + StatusOr readByteOrder(); - StatusOr> readCoordinateList(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder, - uint32_t num) const; + StatusOr readShapeType(); - StatusOr>> readCoordinateListList(const uint8_t *&beg, - const uint8_t *end, - ByteOrder byteOrder, - uint32_t num) const; - StatusOr readUint8(const uint8_t *&beg, const uint8_t *end) const; + StatusOr readCoordinate(); - StatusOr readUint32(const uint8_t *&beg, const uint8_t *end, ByteOrder byteOrder) const; + StatusOr> readCoordinateList(uint32_t num); - StatusOr readDouble(const uint8_t *&beg, const uint8_t *end, ByteOrder byteOrder) const; + StatusOr>> readCoordinateListList(uint32_t num); + + private: + ByteOrderDataInStream is_; }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkb/WKBWriter.cpp b/src/common/geo/io/wkb/WKBWriter.cpp index fa28cdf21a6..181e807c4ab 100644 --- a/src/common/geo/io/wkb/WKBWriter.cpp +++ b/src/common/geo/io/wkb/WKBWriter.cpp @@ -7,39 +7,30 @@ #include "common/geo/io/wkb/WKBWriter.h" namespace nebula { +namespace geo { -// TODO(jie) Check the validity of geom when writing wkb -std::string WKBWriter::write(const Geometry& geom) const { - std::string wkb = ""; +std::string WKBWriter::write(const Geography& geog, ByteOrder byteOrder) { + os_.setByteOrder(byteOrder); + os_.writeUint8(folly::to(byteOrder)); - uint8_t byteOrder = - static_cast>(ByteOrderData::getMachineByteOrder()); - writeUint8(wkb, byteOrder); - - auto shape = geom.shape(); + GeoShape shape = geog.shape(); uint32_t shapeType = folly::to(shape); - writeUint32(wkb, shapeType); + os_.writeUint32(shapeType); switch (shape) { case GeoShape::POINT: { - const Point& point = geom.point(); - writeCoordinate(wkb, point.coord); - return wkb; + const Point& point = geog.point(); + writePoint(point); + return os_.str(); } case GeoShape::LINESTRING: { - const LineString& line = geom.lineString(); - auto coordList = line.coordList; - uint32_t numPoints = coordList.size(); - writeUint32(wkb, numPoints); - writeCoordinateList(wkb, coordList); - return wkb; + const LineString& line = geog.lineString(); + writeLineString(line); + return os_.str(); } case GeoShape::POLYGON: { - const Polygon& polygon = geom.polygon(); - auto coordListList = polygon.coordListList; - uint32_t numRings = coordListList.size(); - writeUint32(wkb, numRings); - writeCoordinateListList(wkb, coordListList); - return wkb; + const Polygon& polygon = geog.polygon(); + writePolygon(polygon); + return os_.str(); } default: LOG(FATAL) @@ -48,38 +39,41 @@ std::string WKBWriter::write(const Geometry& geom) const { } } -void WKBWriter::writeCoordinate(std::string& wkb, const Coordinate& coord) const { - writeDouble(wkb, coord.x); - writeDouble(wkb, coord.y); -} +void WKBWriter::writePoint(const Point& point) { writeCoordinate(point.coord); } -void WKBWriter::writeCoordinateList(std::string& wkb, - const std::vector& coordList) const { - for (size_t i = 0; i < coordList.size(); ++i) { - writeCoordinate(wkb, coordList[i]); - } +void WKBWriter::writeLineString(const LineString& line) { + auto coordList = line.coordList; + uint32_t numCoord = coordList.size(); + os_.writeUint32(numCoord); + writeCoordinateList(coordList); } -void WKBWriter::writeCoordinateListList( - std::string& wkb, const std::vector>& coordListList) const { - for (size_t i = 0; i < coordListList.size(); ++i) { - const auto& coordList = coordListList[i]; - uint32_t numPoints = coordList.size(); - writeUint32(wkb, numPoints); - writeCoordinateList(wkb, coordList); - } +void WKBWriter::writePolygon(const Polygon& polygon) { + auto coordListList = polygon.coordListList; + uint32_t numCoordList = coordListList.size(); + os_.writeUint32(numCoordList); + writeCoordinateListList(coordListList); } -void WKBWriter::writeUint8(std::string& wkb, uint8_t v) const { - wkb.append(reinterpret_cast(&v), sizeof(v)); +void WKBWriter::writeCoordinate(const Coordinate& coord) { + os_.writeDouble(coord.x); + os_.writeDouble(coord.y); } -void WKBWriter::writeUint32(std::string& wkb, uint32_t v) const { - wkb.append(reinterpret_cast(&v), sizeof(v)); +void WKBWriter::writeCoordinateList(const std::vector& coordList) { + for (size_t i = 0; i < coordList.size(); ++i) { + writeCoordinate(coordList[i]); + } } -void WKBWriter::writeDouble(std::string& wkb, double v) const { - wkb.append(reinterpret_cast(&v), sizeof(v)); +void WKBWriter::writeCoordinateListList(const std::vector>& coordListList) { + for (size_t i = 0; i < coordListList.size(); ++i) { + const auto& coordList = coordListList[i]; + uint32_t numCoord = coordList.size(); + os_.writeUint32(numCoord); + writeCoordinateList(coordList); + } } +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkb/WKBWriter.h b/src/common/geo/io/wkb/WKBWriter.h index d43cf7eb4b7..9bb5236e74d 100644 --- a/src/common/geo/io/wkb/WKBWriter.h +++ b/src/common/geo/io/wkb/WKBWriter.h @@ -7,10 +7,12 @@ #pragma once #include "common/base/Base.h" -#include "common/geo/io/Geometry.h" +#include "common/datatypes/Geography.h" #include "common/geo/io/wkb/ByteOrder.h" +#include "common/geo/io/wkb/ByteOrderDataIOStream.h" namespace nebula { +namespace geo { class WKBWriter { public: @@ -18,20 +20,27 @@ class WKBWriter { ~WKBWriter() {} - std::string write(const Geometry& geom) const; + std::string write(const Geography& geog, ByteOrder byteOrder = ByteOrder::LittleEndian); - void writeCoordinate(std::string& wkb, const Coordinate& coord) const; + void writePoint(const Point& point); - void writeCoordinateList(std::string& wkb, const std::vector& coordList) const; + void writeLineString(const LineString& line); - void writeCoordinateListList(std::string& wkb, - const std::vector>& coordListList) const; + void writePolygon(const Polygon& polygon); - void writeUint8(std::string& wkb, uint8_t v) const; + void writeByteOrder(ByteOrder byteOrder); - void writeUint32(std::string& wkb, uint32_t v) const; + void writeShapeType(GeoShape geoShape); - void writeDouble(std::string& wkb, double v) const; + void writeCoordinate(const Coordinate& coord); + + void writeCoordinateList(const std::vector& coordList); + + void writeCoordinateListList(const std::vector>& coordListList); + + private: + ByteOrderDataOutStream os_; }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkb/test/CMakeLists.txt b/src/common/geo/io/wkb/test/CMakeLists.txt new file mode 100644 index 00000000000..8923d630868 --- /dev/null +++ b/src/common/geo/io/wkb/test/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. + +set(WKB_TEST_LIBS + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ +) + +nebula_add_test( + NAME wkb_test + SOURCES WKBTest.cpp + OBJECTS ${WKB_TEST_LIBS} + LIBRARIES gtest gtest_main ${THRIFT_LIBRARIES} ${PROXYGEN_LIBRARIES} +) diff --git a/src/common/geo/io/wkb/test/WKBTest.cpp b/src/common/geo/io/wkb/test/WKBTest.cpp new file mode 100644 index 00000000000..e93c3ef1e2b --- /dev/null +++ b/src/common/geo/io/wkb/test/WKBTest.cpp @@ -0,0 +1,181 @@ +/* Copyright (c) 2018 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include + +#include "common/base/Base.h" +#include "common/datatypes/Geography.h" +#include "common/geo/io/wkb/WKBReader.h" +#include "common/geo/io/wkb/WKBWriter.h" + +namespace nebula { +namespace geo { + +class WKBTest : public ::testing::Test { + public: + void SetUp() override {} + void TearDown() override {} + + protected: + StatusOr read(const Geography& geog) { + auto wkb = WKBWriter().write(geog); + auto geogRet = WKBReader().read(wkb); + NG_RETURN_IF_ERROR(geogRet); + NG_RETURN_IF_ERROR(check(geog, geogRet.value())); + return geogRet; + } + + Status check(const Geography& geog1, const Geography& geog2) { + if (geog1 != geog2) { + return Status::Error("The reparsed Geography is different from the origin."); + } + return Status::OK(); + } +}; + +TEST_F(WKBTest, TestWKB) { + // Point + { + Point v(Coordinate(24.7, 36.842)); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + Point v(Coordinate(-179, 36.842)); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + Point v(Coordinate(24.7, 36.842)); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + Point v(Coordinate(298.4, 499.99)); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(false, v.isValid()); + } + { + Point v(Coordinate(24.7, 36.842)); + auto wkb = WKBWriter().write(v); + wkb.pop_back(); + auto geogRet = WKBReader().read(wkb); + ASSERT_FALSE(geogRet.ok()); + EXPECT_EQ("Unexpected EOF when reading double", geogRet.status().toString()); + } + // LineString + { + LineString v(std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(3, 4)}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + LineString v(std::vector{Coordinate(26.4, 78.9), Coordinate(138.725, 91.0)}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + LineString v(std::vector{Coordinate(0, 1), Coordinate(2, 3), Coordinate(0, 1)}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + LineString v(std::vector{Coordinate(0, 1), Coordinate(0, 1)}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(false, v.isValid()); + } + // LineString must have at least 2 points + { + LineString v(std::vector{Coordinate(0, 1)}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(false, v.isValid()); + } + { + LineString v(std::vector{Coordinate(26.4, 78.9), Coordinate(138.725, 91.0)}); + auto wkb = WKBWriter().write(v); + wkb.pop_back(); + auto geogRet = WKBReader().read(wkb); + ASSERT_FALSE(geogRet.ok()); + EXPECT_EQ("Unexpected EOF when reading double", geogRet.status().toString()); + } + { + LineString v(std::vector{Coordinate(26.4, 78.9), Coordinate(138.725, 91.0)}); + auto wkb = WKBWriter().write(v); + wkb.erase(sizeof(uint8_t) + sizeof(uint32_t) + 3); // Now the numCoord field is missing 1 byte + auto geogRet = WKBReader().read(wkb); + ASSERT_FALSE(geogRet.ok()); + EXPECT_EQ("Unexpected EOF when reading uint32_t", geogRet.status().toString()); + } + // Polygon + { + Polygon v(std::vector>{std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(0, 1)}}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + { + Polygon v(std::vector>{ + std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(0, 1)}, + std::vector{Coordinate(4, 5), + Coordinate(5, 6), + Coordinate(6, 7), + Coordinate(9, 9), + Coordinate(4, 5)}}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(true, v.isValid()); + } + // The loop is not closed + { + Polygon v(std::vector>{std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(3, 4)}}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(false, v.isValid()); + } + // Loop must have at least 4 points + { + Polygon v(std::vector>{ + std::vector{Coordinate(0, 1), Coordinate(1, 2), Coordinate(0, 1)}}); + auto result = read(v); + ASSERT_TRUE(result.ok()) << result.status(); + EXPECT_EQ(false, v.isValid()); + } + { + Polygon v(std::vector>{std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(0, 1)}}); + auto wkb = WKBWriter().write(v); + wkb.pop_back(); + auto geogRet = WKBReader().read(wkb); + ASSERT_FALSE(geogRet.ok()); + EXPECT_EQ("Unexpected EOF when reading double", geogRet.status().toString()); + } + { + Polygon v(std::vector>{std::vector{ + Coordinate(0, 1), Coordinate(1, 2), Coordinate(2, 3), Coordinate(0, 1)}}); + auto wkb = WKBWriter().write(v); + wkb.erase(sizeof(uint8_t) + sizeof(uint32_t) + + 3); // Now the numCoordList field is missing 1 byte + auto geogRet = WKBReader().read(wkb); + ASSERT_FALSE(geogRet.ok()); + EXPECT_EQ("Unexpected EOF when reading uint32_t", geogRet.status().toString()); + } +} + +} // namespace geo +} // namespace nebula diff --git a/src/common/geo/io/wkt/WKTReader.cpp b/src/common/geo/io/wkt/WKTReader.cpp index 6869d86a408..1b735393d8c 100644 --- a/src/common/geo/io/wkt/WKTReader.cpp +++ b/src/common/geo/io/wkt/WKTReader.cpp @@ -6,4 +6,4 @@ #include "common/geo/io/wkt/WKTReader.h" -namespace nebula {} // namespace nebula +namespace nebula::geo {} // namespace nebula::geo diff --git a/src/common/geo/io/wkt/WKTReader.h b/src/common/geo/io/wkt/WKTReader.h index f1f16cacff6..f3aead93daf 100644 --- a/src/common/geo/io/wkt/WKTReader.h +++ b/src/common/geo/io/wkt/WKTReader.h @@ -8,15 +8,16 @@ #include "common/base/Base.h" #include "common/base/StatusOr.h" -#include "common/geo/io/Geometry.h" +#include "common/datatypes/Geography.h" #include "common/geo/io/wkt/WKTParser.hpp" #include "common/geo/io/wkt/WKTScanner.h" namespace nebula { +namespace geo { class WKTReader { public: - WKTReader() : parser_(scanner_, error_, &geom_) { + WKTReader() : parser_(scanner_, error_, &geog_) { // Callback invoked by WKTScanner auto readBuffer = [this](char *buf, int maxSize) -> int { // Reach the end @@ -35,10 +36,10 @@ class WKTReader { } ~WKTReader() { - if (geom_ != nullptr) delete geom_; + if (geog_ != nullptr) delete geog_; } - StatusOr read(std::string wkt) { + StatusOr read(std::string wkt) { // Since WKTScanner needs a writable buffer, we have to copy the query string buffer_ = std::move(wkt); pos_ = &buffer_[0]; @@ -50,22 +51,22 @@ class WKTReader { end_ = nullptr; // To flush the internal buffer to recover from a failure scanner_.flushBuffer(); - if (geom_ != nullptr) { - delete geom_; - geom_ = nullptr; + if (geog_ != nullptr) { + delete geog_; + geog_ = nullptr; } scanner_.setWKT(nullptr); return Status::SyntaxError(error_); } - if (geom_ == nullptr) { + if (geog_ == nullptr) { return Status::StatementEmpty(); // WKTEmpty() } - auto geom = geom_; - geom_ = nullptr; + auto geog = geog_; + geog_ = nullptr; scanner_.setWKT(nullptr); - auto tmp = std::move(*geom); - delete geom; + auto tmp = std::move(*geog); + delete geog; return tmp; } @@ -73,10 +74,11 @@ class WKTReader { std::string buffer_; const char *pos_{nullptr}; const char *end_{nullptr}; - nebula::WKTScanner scanner_; - nebula::WKTParser parser_; + nebula::geo::WKTScanner scanner_; + nebula::geo::WKTParser parser_; std::string error_; - Geometry *geom_{nullptr}; + Geography *geog_{nullptr}; }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkt/WKTScanner.h b/src/common/geo/io/wkt/WKTScanner.h index 7e9c88a9cd8..0216070b6ba 100644 --- a/src/common/geo/io/wkt/WKTScanner.h +++ b/src/common/geo/io/wkt/WKTScanner.h @@ -18,16 +18,18 @@ // Override the interface for yylex since we namespaced it #undef YY_DECL -#define YY_DECL int nebula::WKTScanner::yylex() +#define YY_DECL int nebula::geo::WKTScanner::yylex() #include "common/geo/io/wkt/WKTParser.hpp" namespace nebula { +namespace geo { // TODO(jie) Try to reuse the class GraphScanner class WKTScanner : public yyFlexLexer { public: - int yylex(nebula::WKTParser::semantic_type *lval, nebula::WKTParser::location_type *loc) { + int yylex(nebula::geo::WKTParser::semantic_type *lval, + nebula::geo::WKTParser::location_type *loc) { yylval = lval; yylloc = loc; return yylex(); @@ -52,16 +54,17 @@ class WKTScanner : public yyFlexLexer { // Called when YY_INPUT is invoked int LexerInput(char *buf, int maxSize) override { return readBuffer_(buf, maxSize); } - using TokenType = nebula::WKTParser::token; + using TokenType = nebula::geo::WKTParser::token; private: // friend class Scanner_Basic_Test; TODO(jie) add it int yylex() override; - nebula::WKTParser::semantic_type *yylval{nullptr}; - nebula::WKTParser::location_type *yylloc{nullptr}; + nebula::geo::WKTParser::semantic_type *yylval{nullptr}; + nebula::geo::WKTParser::location_type *yylloc{nullptr}; std::function readBuffer_; std::string *wkt_{nullptr}; }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkt/WKTWriter.cpp b/src/common/geo/io/wkt/WKTWriter.cpp index 7e992579a84..84bfcb7ab64 100644 --- a/src/common/geo/io/wkt/WKTWriter.cpp +++ b/src/common/geo/io/wkt/WKTWriter.cpp @@ -7,15 +7,16 @@ #include "common/geo/io/wkt/WKTWriter.h" namespace nebula { +namespace geo { -std::string WKTWriter::write(const Geometry& geom) const { +std::string WKTWriter::write(const Geography& geog) const { std::string wkt = ""; - auto shape = geom.shape(); + auto shape = geog.shape(); switch (shape) { case GeoShape::POINT: { wkt.append("POINT"); - const Point& point = geom.point(); + const Point& point = geog.point(); wkt.append("("); writeCoordinate(wkt, point.coord); wkt.append(")"); @@ -23,10 +24,10 @@ std::string WKTWriter::write(const Geometry& geom) const { } case GeoShape::LINESTRING: { wkt.append("LINESTRING"); - const LineString& line = geom.lineString(); + const LineString& line = geog.lineString(); auto coordList = line.coordList; - uint32_t numPoints = coordList.size(); - UNUSED(numPoints); + uint32_t numCoord = coordList.size(); + UNUSED(numCoord); wkt.append("("); writeCoordinateList(wkt, coordList); wkt.append(")"); @@ -34,10 +35,10 @@ std::string WKTWriter::write(const Geometry& geom) const { } case GeoShape::POLYGON: { wkt.append("POLYGON"); - const Polygon& polygon = geom.polygon(); + const Polygon& polygon = geog.polygon(); auto coordListList = polygon.coordListList; - uint32_t numRings = coordListList.size(); - UNUSED(numRings); + uint32_t numCoordList = coordListList.size(); + UNUSED(numCoordList); wkt.append("("); writeCoordinateListList(wkt, coordListList); wkt.append(")"); @@ -69,8 +70,8 @@ void WKTWriter::WKTWriter::writeCoordinateListList( std::string& wkt, const std::vector>& coordListList) const { for (size_t i = 0; i < coordListList.size(); ++i) { const auto& coordList = coordListList[i]; - uint32_t numPoints = coordList.size(); - UNUSED(numPoints); + uint32_t numCoord = coordList.size(); + UNUSED(numCoord); wkt.append("("); writeCoordinateList(wkt, coordList); wkt.append(")"); @@ -83,4 +84,5 @@ void WKTWriter::writeDouble(std::string& wkt, double v) const { wkt.append(folly::to(v)); } +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkt/WKTWriter.h b/src/common/geo/io/wkt/WKTWriter.h index bcfbef81748..dc76d9e6356 100644 --- a/src/common/geo/io/wkt/WKTWriter.h +++ b/src/common/geo/io/wkt/WKTWriter.h @@ -7,9 +7,10 @@ #pragma once #include "common/base/Base.h" -#include "common/geo/io/Geometry.h" +#include "common/datatypes/Geography.h" namespace nebula { +namespace geo { class WKTWriter { public: @@ -17,7 +18,7 @@ class WKTWriter { ~WKTWriter() {} - std::string write(const Geometry& geom) const; + std::string write(const Geography& geog) const; void writeCoordinate(std::string& wkt, const Coordinate& coord) const; @@ -29,4 +30,5 @@ class WKTWriter { void writeDouble(std::string& wkt, double v) const; }; +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkt/test/CMakeLists.txt b/src/common/geo/io/wkt/test/CMakeLists.txt index 808082b9d60..98e3d6236c1 100644 --- a/src/common/geo/io/wkt/test/CMakeLists.txt +++ b/src/common/geo/io/wkt/test/CMakeLists.txt @@ -3,7 +3,7 @@ # This source code is licensed under Apache 2.0 License, # attached with Common Clause Condition 1.0, found in the LICENSES directory. -set(WKT_PARSER_TEST_LIBS +set(WKT_TEST_LIBS $ $ $ @@ -48,8 +48,8 @@ set(WKT_PARSER_TEST_LIBS ) nebula_add_test( - NAME wkt_parser_test - SOURCES WKTParserTest.cpp - OBJECTS ${WKT_PARSER_TEST_LIBS} + NAME wkt_test + SOURCES WKTTest.cpp + OBJECTS ${WKT_TEST_LIBS} LIBRARIES gtest gtest_main ${THRIFT_LIBRARIES} ${PROXYGEN_LIBRARIES} ) diff --git a/src/common/geo/io/wkt/test/WKTParserTest.cpp b/src/common/geo/io/wkt/test/WKTTest.cpp similarity index 53% rename from src/common/geo/io/wkt/test/WKTParserTest.cpp rename to src/common/geo/io/wkt/test/WKTTest.cpp index ef4c4512a28..02ce9ee5217 100644 --- a/src/common/geo/io/wkt/test/WKTParserTest.cpp +++ b/src/common/geo/io/wkt/test/WKTTest.cpp @@ -11,28 +11,29 @@ #include "common/geo/io/wkt/WKTWriter.h" namespace nebula { +namespace geo { -class WKTParserTest : public ::testing::Test { +class WKTTEST : public ::testing::Test { public: void SetUp() override {} void TearDown() override {} protected: - StatusOr parse(const std::string& wkt) { - auto geomRet = WKTReader().read(wkt); - NG_RETURN_IF_ERROR(geomRet); - NG_RETURN_IF_ERROR(check(geomRet.value())); - return geomRet; + StatusOr read(const std::string& wkt) { + auto geogRet = WKTReader().read(wkt); + NG_RETURN_IF_ERROR(geogRet); + NG_RETURN_IF_ERROR(check(geogRet.value())); + return geogRet; } - Status check(const Geometry& geom) { - auto wkt = WKTWriter().write(geom); - auto geomCopyRet = WKTReader().read(wkt); - auto geomCopy = geomCopyRet.value(); - auto wktCopy = WKTWriter().write(geomCopy); + Status check(const Geography& geog) { + auto wkt = WKTWriter().write(geog); + auto geogCopyRet = WKTReader().read(wkt); + auto geogCopy = geogCopyRet.value(); + auto wktCopy = WKTWriter().write(geogCopy); if (wkt != wktCopy) { - return Status::Error("The reparsed geometry `%s' is different from origin `%s'.", + return Status::Error("The reparsed Geography `%s' is different from origin `%s'.", wktCopy.c_str(), wkt.c_str()); } @@ -40,98 +41,71 @@ class WKTParserTest : public ::testing::Test { } }; -TEST_F(WKTParserTest, TestWKTParser) { +TEST_F(WKTTEST, TestWKT) { // Point { std::string wkt = "POINT(24.7 36.842)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "POINT(-179 36.842)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "POINT(24.7, 36.842)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().toString(), "SyntaxError: syntax error near `, 36.842'"); } { std::string wkt = "POINT(179,"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().toString(), "SyntaxError: syntax error near `,'"); } - { - std::string wkt = "POINT(-190 36.842)"; - auto result = parse(wkt); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.status().toString(), - "SyntaxError: Longitude must be between -180 and 180 degrees near `-190'"); - } - { - std::string wkt = "POINT(179 91)"; - auto result = parse(wkt); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.status().toString(), - "SyntaxError: Latitude must be between -90 and 90 degrees near `91'"); - } // LineString { std::string wkt = "LINESTRING(0 1, 1 2, 2 3, 3 4)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "LINESTRING(26.4 78.9, 138.725 52)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "LINESTRING(0 1, 2 3,)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().toString(), "SyntaxError: syntax error near `)'"); } - { - std::string wkt = "LINESTRING(0 1)"; - auto result = parse(wkt); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.status().toString(), - "SyntaxError: LineString must have at least 2 coordinates near `0 1'"); - } // Polygon { std::string wkt = "POLYGON((0 1, 1 2, 2 3, 0 1))"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "POLYGON((0 1, 1 2, 2 3, 0 1), (4 5, 5 6, 6 7, 9 9, 4 5))"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_TRUE(result.ok()) << result.status(); } { std::string wkt = "POLYGON(0 1, 1 2, 2 3, 0 1)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().toString(), "SyntaxError: syntax error near `0 1, 1 2'"); } { std::string wkt = "POLYGON((0 1, 1 2, 0 1)"; - auto result = parse(wkt); + auto result = read(wkt); ASSERT_FALSE(result.ok()); EXPECT_EQ(result.status().toString(), "SyntaxError: syntax error near `)'"); } - { - std::string wkt = "POLYGON((0 1, 1 2, 2 3, 3 4))"; - auto result = parse(wkt); - ASSERT_FALSE(result.ok()); - EXPECT_EQ(result.status().toString(), - "SyntaxError: Polygon's LinearRing must be closed near `(0 1, 1 2, 2 3, 3 4)'"); - } } +} // namespace geo } // namespace nebula diff --git a/src/common/geo/io/wkt/wkt_parser.yy b/src/common/geo/io/wkt/wkt_parser.yy index ee92a150bd3..00d3af12b32 100644 --- a/src/common/geo/io/wkt/wkt_parser.yy +++ b/src/common/geo/io/wkt/wkt_parser.yy @@ -2,38 +2,38 @@ %skeleton "lalr1.cc" %no-lines %locations -%define api.namespace { nebula } +%define api.namespace { nebula::geo } %define parser_class_name { WKTParser } -%lex-param { nebula::WKTScanner& scanner } -%parse-param { nebula::WKTScanner& scanner } +%lex-param { nebula::geo::WKTScanner& scanner } +%parse-param { nebula::geo::WKTScanner& scanner } %parse-param { std::string &errmsg } -%parse-param { nebula::Geometry** geom } +%parse-param { nebula::Geography** geog } %code requires { #include #include #include #include -#include "common/geo/io/Geometry.h" +#include "common/datatypes/Geography.h" namespace nebula { - +namespace geo { class WKTScanner; - +} } } %code { #include "common/geo/io/wkt/WKTScanner.h" - static int yylex(nebula::WKTParser::semantic_type* yylval, - nebula::WKTParser::location_type *yylloc, - nebula::WKTScanner& scanner); + static int yylex(nebula::geo::WKTParser::semantic_type* yylval, + nebula::geo::WKTParser::location_type *yylloc, + nebula::geo::WKTScanner& scanner); } %union { double doubleVal; - Geometry* geomVal; + Geography* geogVal; Point* pointVal; LineString* lineVal; Polygon* polygonVal; @@ -44,7 +44,7 @@ class WKTScanner; /* destructors */ %destructor {} -%destructor {} +%destructor {} %destructor { delete $$; } <*> /* wkt shape type prefix */ @@ -56,7 +56,7 @@ class WKTScanner; /* token type specification */ %token DOUBLE -%type geometry +%type geometry %type point %type linestring %type polygon @@ -72,19 +72,19 @@ class WKTScanner; geometry : point { - $$ = new Geometry(std::move(*$1)); + $$ = new Geography(std::move(*$1)); delete $1; - *geom = $$; + *geog = $$; } | linestring { - $$ = new Geometry(std::move(*$1)); + $$ = new Geography(std::move(*$1)); delete $1; - *geom = $$; + *geog = $$; } | polygon { - $$ = new Geometry(std::move(*$1)); + $$ = new Geography(std::move(*$1)); delete $1; - *geom = $$; + *geog = $$; } ; @@ -96,10 +96,6 @@ point linestring : KW_LINESTRING L_PAREN coordinate_list R_PAREN { - if ($3->size() < 2) { - delete $3; - throw nebula::WKTParser::syntax_error(@3, "LineString must have at least 2 coordinates"); - } $$ = new LineString(std::move(*$3)); delete $3; } @@ -107,17 +103,6 @@ linestring polygon : KW_POLYGON L_PAREN coordinate_list_list R_PAREN { - for (size_t i = 0; i < $3->size(); ++i) { - const auto &coordList = (*$3)[i]; - if (coordList.size() < 4) { - delete $3; - throw nebula::WKTParser::syntax_error(@3, "Polygon's LinearRing must have at least 4 coordinates"); - } - if (coordList.front() != coordList.back()) { - delete $3; - throw nebula::WKTParser::syntax_error(@3, "Polygon's LinearRing must be closed"); - } - } $$ = new Polygon(std::move(*$3)); delete $3; } @@ -125,12 +110,6 @@ polygon coordinate : DOUBLE DOUBLE { - if (!Coordinate::isValidLng($1)) { - throw nebula::WKTParser::syntax_error(@1, "Longitude must be between -180 and 180 degrees"); - } - if (!Coordinate::isValidLat($2)) { - throw nebula::WKTParser::syntax_error(@2, "Latitude must be between -90 and 90 degrees"); - } $$.x = $1; $$.y = $2; } @@ -162,7 +141,7 @@ coordinate_list_list %% -void nebula::WKTParser::error(const nebula::WKTParser::location_type& loc, +void nebula::geo::WKTParser::error(const nebula::geo::WKTParser::location_type& loc, const std::string &msg) { std::ostringstream os; if (msg.empty()) { @@ -198,9 +177,9 @@ void nebula::WKTParser::error(const nebula::WKTParser::location_type& loc, errmsg = os.str(); } -static int yylex(nebula::WKTParser::semantic_type* yylval, - nebula::WKTParser::location_type *yylloc, - nebula::WKTScanner& scanner) { +static int yylex(nebula::geo::WKTParser::semantic_type* yylval, + nebula::geo::WKTParser::location_type *yylloc, + nebula::geo::WKTScanner& scanner) { auto token = scanner.yylex(yylval, yylloc); return token; } diff --git a/src/common/geo/test/CMakeLists.txt b/src/common/geo/test/CMakeLists.txt new file mode 100644 index 00000000000..e0593ca7652 --- /dev/null +++ b/src/common/geo/test/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. + +nebula_add_test( + NAME + geo_function_test + SOURCES + GeoFunctionTest.cpp + OBJECTS + $ + $ + $ + $ + $ + $ + $ + LIBRARIES + gtest + gtest_main +) diff --git a/src/common/geo/test/GeoFunctionTest.cpp b/src/common/geo/test/GeoFunctionTest.cpp new file mode 100644 index 00000000000..d525536691e --- /dev/null +++ b/src/common/geo/test/GeoFunctionTest.cpp @@ -0,0 +1,1167 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include + +#include "common/base/Base.h" +#include "common/geo/GeoFunction.h" + +namespace nebula { +namespace geo { + +TEST(Intersects, point2Point) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::intersects(point1, point2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + bool b = GeoFunction::intersects(point1, point2); + EXPECT_EQ(false, b); + } + // { + // auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + // auto point2 = Geography::fromWKT("POINT(1.0 1.00000000001)").value(); + // bool b = GeoFunction::intersects(point1, point2); + // EXPECT_EQ(false, b); // The error of GeoFunction::intersects should be 1e-11 + // } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.000000000001)").value(); + bool b = GeoFunction::intersects(point1, point2); + EXPECT_EQ(true, b); + } +} + +TEST(Intersects, point2LineString) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + bool b = GeoFunction::intersects(point1, line2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(3.0 3.000000000001, 2.0 2.0)").value(); + bool b = GeoFunction::intersects(point1, line2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + bool b = GeoFunction::intersects(point1, line2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.2 3.8)").value(); + auto point2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + bool b = GeoFunction::intersects(point1, point2); + EXPECT_EQ(false, b); + } +} + +TEST(Intersects, point2Polygon) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::intersects(point1, polygon2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.000000000001)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::intersects(point1, polygon2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.9)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::intersects(point1, polygon2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(0.3 0.3)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::intersects(point1, polygon2); + EXPECT_EQ(false, b); + } +} + +TEST(Intersects, lineString2LineString) { + { + auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + bool b = GeoFunction::intersects(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + bool b = GeoFunction::intersects(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.5499860 1.5501575, 1.5 1.5001714)").value(); + bool b = GeoFunction::intersects(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 2.0 1.0)").value(); + bool b = GeoFunction::intersects(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 1.0 4.0)").value(); + bool b = GeoFunction::intersects(line1, line2); + EXPECT_EQ(false, b); + } +} + +TEST(Intersects, lineString2Polygon) { + // { + // auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + // auto polygon2 = + // Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + // bool b = GeoFunction::intersects(line1, polygon2); + // EXPECT_EQ(true, b); // Expect true, got false + // } + { + auto line1 = Geography::fromWKT("LINESTRING(0.2 0.2, 0.4 0.4)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, 0.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, 1.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, -1.5 -1.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.3 0.3, 0.35 0.35)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.3 0.3, 0.5 0.5)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.4 0.3, 0.6 0.3)").value(); + auto polygon2 = + Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.3 0.3, 0.4 0.2, 0.5 0.3, " + "0.4 0.4, 0.3 0.3), (0.5 0.3, 0.6 0.2, 0.7 0.3, 0.6 0.4, 0.5 0.3))") + .value(); + bool b = GeoFunction::intersects(line1, polygon2); + EXPECT_EQ(true, b); + } +} + +TEST(Intersects, polygon2Polygon) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.2 0.2, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.2 0.2))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.2, 0.1 0.1))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(true, b); + } + + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((-1.0 0.0, 1.0 0.0, 1.0 1.0, -1.0 1.0, -1.0 0.0))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(true, b); + } + + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(false, b); + } + + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.2, 0.1 0.1))").value(); + bool b = GeoFunction::intersects(polygon1, polygon2); + EXPECT_EQ(true, b); + } +} + +TEST(Covers, point2Point) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::covers(point1, point2); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + bool b = GeoFunction::covers(point1, point2); + EXPECT_EQ(false, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.00000000001)").value(); + bool b = GeoFunction::covers(point1, point2); + EXPECT_EQ(false, b); + } + // { + // auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + // auto point2 = Geography::fromWKT("POINT(1.0 1.000000000001)").value(); + // bool b = GeoFunction::covers(point1, point2); + // EXPECT_EQ(true, b); // The error should be 1e-11? + // } +} + +TEST(Covers, point2LineString) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + bool b = GeoFunction::covers(point1, line2); + EXPECT_EQ(false, b); + } +} + +TEST(Covers, point2Polygon) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::covers(point1, polygon2); + EXPECT_EQ(false, b); + } +} + +TEST(Covers, lineString2Point) { + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::covers(line1, point2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto point2 = Geography::fromWKT("POINT(2.0 2.0)").value(); + bool b = GeoFunction::covers(line1, point2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto point2 = Geography::fromWKT("POINT(3.0 3.0)").value(); + bool b = GeoFunction::covers(line1, point2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto point2 = Geography::fromWKT("POINT(2.0 4.0)").value(); + bool b = GeoFunction::covers(line1, point2); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.5 1.5001714)").value(); + bool b = GeoFunction::covers(line1, point2); + EXPECT_EQ(true, b); // false + } +} + +TEST(Covers, lineString2LineString) { + { + auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + bool b = GeoFunction::covers(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(4.0 4.0, 3.0 3.0, 2.0 2.0)").value(); + bool b = GeoFunction::covers(line1, line2); + EXPECT_EQ(true, b); // Line should GeoFunction::covers its reverse + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + bool b = GeoFunction::covers(line1, line2); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + bool b = GeoFunction::covers(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(5.0 5.0, 4.0 4.0)").value(); + bool b = GeoFunction::covers(line1, line2); + EXPECT_EQ(false, b); + } +} + +TEST(Covers, lineString2Polygon) { + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::covers(line1, polygon2); + EXPECT_EQ(false, b); + } +} + +TEST(Covers, polygon2Point) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto point2 = Geography::fromWKT("POINT(0.5 0.5)").value(); + bool b = GeoFunction::covers(polygon1, point2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto point2 = Geography::fromWKT("POINT(0.5 7.7)").value(); + bool b = GeoFunction::covers(polygon1, point2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + auto point2 = Geography::fromWKT("POINT(0.3 0.3)").value(); + bool b = GeoFunction::covers(polygon1, point2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto point2 = Geography::fromWKT("POINT(1.0 0.0)").value(); + bool b = GeoFunction::covers(polygon1, point2); + EXPECT_EQ(true, b); + } +} + +TEST(Covers, polygon2LineString) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto line2 = Geography::fromWKT("LINESTRING(0.1 0.1, 0.4 0.4)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + auto line2 = Geography::fromWKT("LINESTRING(0.3 0.3, 0.35 0.35)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + auto line2 = Geography::fromWKT("LINESTRING(0.3 0.3, 0.55 0.55)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + auto line2 = Geography::fromWKT("LINESTRING(0.3 0.3, 4.0 4.0)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto line2 = Geography::fromWKT("LINESTRING(-1 -1, -2 -2)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto line2 = Geography::fromWKT("LINESTRING(-0.5 -0.5, 0.5 0.5)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto line2 = Geography::fromWKT("LINESTRING(0.0 0.0, 1.0 0.0)").value(); + bool b = GeoFunction::covers(polygon1, line2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 0.0, 0.0 0.0)").value(); + bool b = GeoFunction::covers( + polygon1, line2); // The line is equal to the first edge of the polygon, but their vertices + // are in reverse order. (0.0, 0.0, 1.0 0.0), (1.0 0.0, 0.0 0.0) + EXPECT_EQ(true, b); + } +} + +TEST(Covers, polygon2Polygon) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::covers(polygon1, polygon2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.2 0.2, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.2 0.2))").value(); + bool b = GeoFunction::covers(polygon1, polygon2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.2, 0.1 0.1))").value(); + bool b = GeoFunction::covers(polygon1, polygon2); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((-1.0 0.0, 1.0 0.0, 1.0 1.0, -1.0 1.0, -1.0 0.0))").value(); + bool b = GeoFunction::covers(polygon1, polygon2); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0))").value(); + bool b = GeoFunction::covers(polygon1, polygon2); + EXPECT_EQ(false, b); + } +} + +TEST(CoveredBy, basic) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + bool b = GeoFunction::coveredBy(point1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0, 3.0 3.0, 4.0 4.0)").value(); + bool b = GeoFunction::coveredBy(line1, line2); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::coveredBy(line1, polygon2); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 1.0, 1.5 1.5, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::coveredBy(line1, polygon2); + EXPECT_EQ(true, b); + } +} + +TEST(DWithin, point2Point) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::dWithin(point1, point2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::dWithin(point1, point2, 0.0, false); + EXPECT_EQ(false, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = GeoFunction::dWithin(point1, point2, 0.1, false); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + bool b = GeoFunction::dWithin(point1, point2, 22239.020235496788, false); + EXPECT_EQ(false, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + bool b = GeoFunction::dWithin(point1, point2, 22240, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(-118.4079 33.9434)").value(); + auto point2 = Geography::fromWKT("POINT(2.5559 49.0083)").value(); + bool b = GeoFunction::dWithin(point1, point2, 10000, false); + EXPECT_EQ(false, b); + } +} + +TEST(DWithin, point2LineString) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.2, 1.5 1.6)").value(); + bool b = GeoFunction::dWithin(point1, line2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.2 3.8)").value(); + auto point2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + bool b = GeoFunction::dWithin(point1, point2, 1.0, false); + EXPECT_EQ(false, b); + } +} + +TEST(DWithin, point2Polygon) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + EXPECT_EQ(true, b); + } + // Point lies on the interior of the polygon + { + auto point1 = Geography::fromWKT("POINT(1.5 1.9)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(0.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(point1, polygon2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(point1, polygon2, 99999, false); + EXPECT_EQ(true, b); + } + { + auto point1 = Geography::fromWKT("POINT(0.3 0.3)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::dWithin(point1, polygon2, 10000, true); + EXPECT_EQ(false, b); + } +} + +TEST(DWithin, lineString2LineString) { + { + auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + bool b = GeoFunction::dWithin(line1, line2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + bool b = GeoFunction::dWithin(line1, line2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.0 0.0, 1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(5.0 5.0, 6.0 6.0)").value(); + bool b = GeoFunction::dWithin(line1, line2, 628519, true); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 2.0 1.0)").value(); + bool b = GeoFunction::dWithin(line1, line2, 1.0, false); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 1.0 4.0)").value(); + bool b = GeoFunction::dWithin(line1, line2, 78609, false); + EXPECT_EQ(true, b); + } +} + +TEST(DWithin, lineString2Polygon) { + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(line1, polygon2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.2 0.2, 0.4 0.4)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(line1, polygon2, 0.0, false); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, 0.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(line1, polygon2, 9999, false); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.25 0.25, 0.35 0.35)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::dWithin(line1, polygon2, 5555, true); + EXPECT_EQ(false, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.25 0.25, 0.6 0.6)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::dWithin(line1, polygon2, 0.1, false); + EXPECT_EQ(true, b); + } + { + auto line1 = Geography::fromWKT("LINESTRING(7.0 7.0, 5.0 5.0)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::dWithin(line1, polygon2, 628520, false); + EXPECT_EQ(true, b); + } +} + +TEST(DWithin, polygon2Polygon) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + bool b = GeoFunction::dWithin(polygon1, polygon2, 0.0, true); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.2 0.2, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.2 0.2))").value(); + bool b = GeoFunction::dWithin(polygon1, polygon2, 1.1, false); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0))").value(); + bool b = GeoFunction::dWithin(polygon1, polygon2, 314402, true); + EXPECT_EQ(false, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.25 0.25, 0.35 0.25, 0.35 0.35, 0.25 0.35, 0.25 0.25))") + .value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + bool b = GeoFunction::dWithin(polygon1, polygon2, 5560, false); + EXPECT_EQ(true, b); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((-8.0 -8.0, -4.0 -8.0, -4.0 -4.0, -8.0 -4.0, -8.0 -8.0))") + .value(); + bool b = GeoFunction::dWithin(polygon1, polygon2, 628750, false); + EXPECT_EQ(false, b); + } +} + +TEST(Distance, point2Point) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.0)").value(); + double d = GeoFunction::distance(point1, point2); + EXPECT_EQ(0.0, d); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.2)").value(); + double d = GeoFunction::distance(point1, point2); + EXPECT_EQ(22239.020235496788, d); + } + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto point2 = Geography::fromWKT("POINT(1.0 1.00001)").value(); + double d = GeoFunction::distance(point1, point2); + EXPECT_EQ(1.1119510117584994, d); + } + { + auto point1 = Geography::fromWKT("POINT(-118.4079 33.9434)").value(); + auto point2 = Geography::fromWKT("POINT(2.5559 49.0083)").value(); + double d = GeoFunction::distance(point1, point2); + EXPECT_EQ(9103089.738448681, d); + } +} + +TEST(Distance, point2LineString) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + double d = GeoFunction::distance(point1, line2); + EXPECT_EQ(0.0, d); + } + // { + // auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + // auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + // double d = GeoFunction::distance(point1, line2); + // EXPECT_EQ(0.0, d); // Expect 0.0, got 0.000000000031290120680325526 + // } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.2, 1.5 1.6)").value(); + double d = GeoFunction::distance(point1, line2); + EXPECT_EQ(0.0, d); + } + { + auto point1 = Geography::fromWKT("POINT(1.2 3.8)").value(); + auto point2 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.6, 2.0 2.2)").value(); + double d = GeoFunction::distance(point1, point2); + EXPECT_EQ(198856.0525640426, d); + } +} + +TEST(Distance, point2Polygon) { + { + auto point1 = Geography::fromWKT("POINT(1.0 1.0)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + double d = GeoFunction::distance(point1, polygon2); + EXPECT_EQ(0.0, d); + } + // Point lies on the interior of the polygon + { + auto point1 = Geography::fromWKT("POINT(1.5 1.9)").value(); + auto polygon2 = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + double d = GeoFunction::distance(point1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto point1 = Geography::fromWKT("POINT(0.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(point1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto point1 = Geography::fromWKT("POINT(1.5 1.6)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(point1, polygon2); + EXPECT_EQ(86836.82688844757, d); + } + { + auto point1 = Geography::fromWKT("POINT(0.3 0.3)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + double d = GeoFunction::distance(point1, polygon2); + EXPECT_EQ(11119.357694100196, d); + } +} + +TEST(Distance, lineString2LineString) { + { + auto line1 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + double d = GeoFunction::distance(line1, line2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(2.0 2.0, 3.0 3.0)").value(); + double d = GeoFunction::distance(line1, line2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.0 0.0, 1.0 1.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(5.0 5.0, 6.0 6.0)").value(); + double d = GeoFunction::distance(line1, line2); + EXPECT_EQ(628519.1549911008, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 2.0 1.0)").value(); + double d = GeoFunction::distance(line1, line2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto line2 = Geography::fromWKT("LINESTRING(1.0 2.0, 1.0 4.0)").value(); + double d = GeoFunction::distance(line1, line2); + EXPECT_EQ(78608.33038471815, d); + } +} + +TEST(Distance, lineString2Polygon) { + { + auto line1 = Geography::fromWKT("LINESTRING(1.0 1.0, 1.5 1.5, 2.0 2.0)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.2 0.2, 0.4 0.4)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(-0.5 0.5, 0.5 0.5)").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.25 0.25, 0.35 0.35)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(5559.651326278197, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(0.25 0.25, 0.6 0.6)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto line1 = Geography::fromWKT("LINESTRING(7.0 7.0, 5.0 5.0)").value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + double d = GeoFunction::distance(line1, polygon2); + EXPECT_EQ(628519.1549911008, d); + } +} + +TEST(Distance, polygon2Polygon) { + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + double d = GeoFunction::distance(polygon1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((0.2 0.2, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.2 0.2))").value(); + double d = GeoFunction::distance(polygon1, polygon2); + EXPECT_EQ(0.0, d); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((3.0 3.0, 4.0 3.0, 4.0 4.0, 3.0 4.0, 3.0 3.0))").value(); + double d = GeoFunction::distance(polygon1, polygon2); + EXPECT_EQ(314403.44451436587, d); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.25 0.25, 0.35 0.25, 0.35 0.35, 0.25 0.35, 0.25 0.25))") + .value(); + auto polygon2 = Geography::fromWKT( + "POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.2 0.2, 0.2 0.4, " + "0.4 0.4, 0.4 0.2, 0.2 0.2))") + .value(); + double d = GeoFunction::distance(polygon1, polygon2); + EXPECT_EQ(5559.651326278197, d); + } + { + auto polygon1 = + Geography::fromWKT("POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0))").value(); + auto polygon2 = + Geography::fromWKT("POLYGON((-8.0 -8.0, -4.0 -8.0, -4.0 -4.0, -8.0 -4.0, -8.0 -8.0))") + .value(); + double d = GeoFunction::distance(polygon1, polygon2); + EXPECT_EQ(628758.784267869, d); + } +} + +TEST(s2CellIdFromPoint, point) { + { + auto point = Geography::fromWKT("POINT(1.0 1.0)").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(point); + EXPECT_EQ(1153277837650709461, i); + } + { + auto point = Geography::fromWKT("POINT(179.0 89.9)").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(point); + EXPECT_EQ(6533220958669205147, i); + } + { + auto point = Geography::fromWKT("POINT(-45.4 28.7652)").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(point); + int64_t expectInt64 = -8444974090143026723; + const char* c = reinterpret_cast(&expectInt64); + uint64_t expect = *reinterpret_cast(c); + EXPECT_EQ(expect, i); + } + { + auto point = Geography::fromWKT("POINT(0.0 0.0)").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(point); + EXPECT_EQ(1152921504606846977, i); + } +} + +// GeoFunction::s2CellIdFromPoint() just supports point +TEST(s2CellIdFromPoint, lineString) { + { + auto line = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(line); + EXPECT_EQ(-1, i); + } +} + +// GeoFunction::s2CellIdFromPoint() just supports point +TEST(s2CellIdFromPoint, polygon) { + { + auto polygon = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + uint64_t i = GeoFunction::s2CellIdFromPoint(polygon); + EXPECT_EQ(-1, i); + } +} + +TEST(isValid, point) { + { + auto point = Geography::fromWKT("POINT(1.0 1.0)").value(); + bool b = point.isValid(); + EXPECT_EQ(true, b); + } + { + auto point = Geography::fromWKT("POINT(181.0 1.0)").value(); + bool b = point.isValid(); + EXPECT_EQ(false, b); + } + { + auto point = Geography::fromWKT("POINT(1.0 91.0)").value(); + bool b = point.isValid(); + EXPECT_EQ(false, b); + } + { + auto point = Geography::fromWKT("POINT(-181.0 -91.0)").value(); + bool b = point.isValid(); + EXPECT_EQ(false, b); + } +} + +TEST(isValid, lineString) { + { + auto line = Geography::fromWKT("LINESTRING(1.0 1.0, 2.0 2.0)").value(); + bool b = line.isValid(); + EXPECT_EQ(true, b); + } + { + auto line = Geography::fromWKT("LINESTRING(1 1, 2 3, 4 8, -6 3)").value(); + bool b = line.isValid(); + EXPECT_EQ(true, b); + } + { + auto line = Geography::fromWKT("LINESTRING(1.0 1.0)").value(); + bool b = line.isValid(); + EXPECT_EQ(false, b); + } + { + auto line = Geography::fromWKT("LINESTRING(1.0 1.0, 1.0 1.0)").value(); + bool b = line.isValid(); + EXPECT_EQ(false, b); + } +} + +TEST(isValid, polygon) { + { + auto polygon = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0))").value(); + bool b = polygon.isValid(); + EXPECT_EQ(true, b); + } + { + auto polygon = Geography::fromWKT("POLYGON((-20 -20, -20 20, 20 20, 20 -20, -20 -20))").value(); + bool b = polygon.isValid(); + EXPECT_EQ(true, b); + } + { + auto polygon = + Geography::fromWKT( + "POLYGON((-20 -20, -20 20, 20 20, 20 -20, -20 -20), (10 0, 0 10, 0 -10, 10 0))") + .value(); + bool b = polygon.isValid(); + EXPECT_EQ(true, b); + } + { + auto polygon = Geography::fromWKT( + "POLYGON((-20 -20, -20 20, 20 20, 20 -20, -20 -20), (10 0, 0 10, 0 -10, 10 " + "0), (-10 0, 0 10, -5 -10, -10 0))") + .value(); + bool b = polygon.isValid(); + EXPECT_EQ(true, b); + } + { + auto polygon = Geography::fromWKT( + "POLYGON((-20 -20, -20 20, 20 20, 20 -20, -20 -20), (1.0 1.0, 2.0 2.0, 0.0 " + "2.0, 1.0 1.0))") + .value(); + bool b = polygon.isValid(); + EXPECT_EQ(true, b); + } + { + auto polygon = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0))").value(); + bool b = polygon.isValid(); + EXPECT_EQ(false, b); + } + { + auto polygon = Geography::fromWKT("POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.2 1.2))").value(); + bool b = polygon.isValid(); + EXPECT_EQ(false, b); + } + // The first loop doesn't contain the second loop + // { + // auto polygon = Geography::fromWKT( + // "POLYGON((1.0 1.0, 2.0 2.0, 0.0 2.0, 1.0 1.0), (-20 -20, -20 20, 20 20, 20 + // " + // "-20, -20 -20))") + // .value(); + // bool b = polygon.isValid(); + // EXPECT_EQ(false, b); // Expect false, got true + // } +} + +} // namespace geo +} // namespace nebula + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + folly::init(&argc, &argv, true); + google::SetStderrLogging(google::INFO); + + return RUN_ALL_TESTS(); +} diff --git a/src/interface/common.thrift b/src/interface/common.thrift index 71ba8b0714b..c50fed85db6 100644 --- a/src/interface/common.thrift +++ b/src/interface/common.thrift @@ -149,8 +149,27 @@ struct DataSet { 2: list rows; } (cpp.type = "nebula::DataSet") -struct Geography { - 1: string wkb; +struct Coordinate { + 1: double x; + 2: double y; +} (cpp.type = "nebula::Coordinate") + +struct Point { + 1: Coordinate coord; +} (cpp.type = "nebula::Point") + +struct LineString { + 1: list coordList; +} (cpp.type = "nebula::LineString") + +struct Polygon { + 1: list> coordListList; +} (cpp.type = "nebula::Polygon") + +union Geography { + 1: Point ptVal (cpp.ref_type = "unique"); + 2: LineString lsVal (cpp.ref_type = "unique"); + 3: Polygon pgVal (cpp.ref_type = "unique"); } (cpp.type = "nebula::Geography")