Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions be/src/exprs/function/geo/functions_geo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <algorithm>
#include <boost/iterator/iterator_facade.hpp>
#include <cmath>
#include <utility>

#include "common/compiler_util.h"
Expand Down Expand Up @@ -196,6 +197,166 @@ struct StY {
}
};

// Bounding box accessors, Trino compatible:
// st_xmax / st_xmin / st_ymax / st_ymin return the max/min X (longitude) and
// Y (latitude) of a geometry. All shape types are decoded via GeoShape, and the
// result is NULL when the value is not available.
struct StXMax {
static constexpr auto NAME = "st_xmax";
static const size_t NUM_ARGS = 1;
using Type = DataTypeFloat64;
static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) {
DCHECK_EQ(arguments.size(), 1);
auto& input = block.get_by_position(arguments[0]).column;

auto size = input->size();

auto res = ColumnFloat64::create();
auto null_map = ColumnUInt8::create(size, 0);
auto& null_map_data = null_map->get_data();
res->reserve(size);

for (int row = 0; row < size; ++row) {
auto value = input->get_data_at(row);
auto shape = GeoShape::from_encoded(value.data, value.size);

if (shape == nullptr) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
const double x_max = shape->bounding_box().x_max;
if (std::isnan(x_max)) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
res->insert_value(x_max);
}
block.replace_by_position(result,
ColumnNullable::create(std::move(res), std::move(null_map)));

return Status::OK();
}
};

struct StXMin {
static constexpr auto NAME = "st_xmin";
static const size_t NUM_ARGS = 1;
using Type = DataTypeFloat64;
static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) {
DCHECK_EQ(arguments.size(), 1);
auto& input = block.get_by_position(arguments[0]).column;

auto size = input->size();

auto res = ColumnFloat64::create();
auto null_map = ColumnUInt8::create(size, 0);
auto& null_map_data = null_map->get_data();
res->reserve(size);

for (int row = 0; row < size; ++row) {
auto value = input->get_data_at(row);
auto shape = GeoShape::from_encoded(value.data, value.size);

if (shape == nullptr) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
const double x_min = shape->bounding_box().x_min;
if (std::isnan(x_min)) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
res->insert_value(x_min);
}
block.replace_by_position(result,
ColumnNullable::create(std::move(res), std::move(null_map)));

return Status::OK();
}
};

struct StYMax {
static constexpr auto NAME = "st_ymax";
static const size_t NUM_ARGS = 1;
using Type = DataTypeFloat64;
static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) {
DCHECK_EQ(arguments.size(), 1);
auto& input = block.get_by_position(arguments[0]).column;

auto size = input->size();

auto res = ColumnFloat64::create();
auto null_map = ColumnUInt8::create(size, 0);
auto& null_map_data = null_map->get_data();
res->reserve(size);

for (int row = 0; row < size; ++row) {
auto value = input->get_data_at(row);
auto shape = GeoShape::from_encoded(value.data, value.size);

if (shape == nullptr) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
const double y_max = shape->bounding_box().y_max;
if (std::isnan(y_max)) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
res->insert_value(y_max);
}
block.replace_by_position(result,
ColumnNullable::create(std::move(res), std::move(null_map)));

return Status::OK();
}
};

struct StYMin {
static constexpr auto NAME = "st_ymin";
static const size_t NUM_ARGS = 1;
using Type = DataTypeFloat64;
static Status execute(Block& block, const ColumnNumbers& arguments, size_t result) {
DCHECK_EQ(arguments.size(), 1);
auto& input = block.get_by_position(arguments[0]).column;

auto size = input->size();

auto res = ColumnFloat64::create();
auto null_map = ColumnUInt8::create(size, 0);
auto& null_map_data = null_map->get_data();
res->reserve(size);

for (int row = 0; row < size; ++row) {
auto value = input->get_data_at(row);
auto shape = GeoShape::from_encoded(value.data, value.size);

if (shape == nullptr) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
const double y_min = shape->bounding_box().y_min;
if (std::isnan(y_min)) {
null_map_data[row] = 1;
res->insert_default();
continue;
}
res->insert_value(y_min);
}
block.replace_by_position(result,
ColumnNullable::create(std::move(res), std::move(null_map)));

return Status::OK();
}
};

struct StDistanceSphere {
static constexpr auto NAME = "st_distance_sphere";
static const size_t NUM_ARGS = 4;
Expand Down Expand Up @@ -1084,6 +1245,10 @@ void register_function_geo(SimpleFunctionFactory& factory) {
factory.register_function<GeoFunction<StAsText<StAsTextName>>>();
factory.register_function<GeoFunction<StX>>();
factory.register_function<GeoFunction<StY>>();
factory.register_function<GeoFunction<StXMax>>();
factory.register_function<GeoFunction<StXMin>>();
factory.register_function<GeoFunction<StYMax>>();
factory.register_function<GeoFunction<StYMin>>();
factory.register_function<GeoFunction<StDistanceSphere>>();
factory.register_function<GeoFunction<StAngleSphere>>();
factory.register_function<GeoFunction<StAngle>>();
Expand Down
83 changes: 83 additions & 0 deletions be/src/exprs/function/geo/geo_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

#include "core/assert_cast.h"
// IWYU pragma: no_include <bits/std_abs.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <limits>
Expand All @@ -53,6 +54,31 @@ namespace doris {

constexpr double TOLERANCE = 1e-6;

namespace {

// NaN-safe min/max, used to merge bounding boxes of multiple geometries.
double nan_min(double a, double b) {
if (std::isnan(a)) {
return b;
}
if (std::isnan(b)) {
return a;
}
return std::min(a, b);
}

double nan_max(double a, double b) {
if (std::isnan(a)) {
return b;
}
if (std::isnan(b)) {
return a;
}
return std::max(a, b);
}

} // namespace

GeoPoint::GeoPoint() : _point(new S2Point()) {}
GeoPoint::~GeoPoint() = default;

Expand Down Expand Up @@ -613,6 +639,11 @@ double GeoPoint::y() const {
return std::stod(absl::StrFormat("%.13f", S2LatLng::Latitude(*_point).degrees()));
}

BoundingBox GeoPoint::bounding_box() const {
// A point degenerates to a box with zero width and height.
return {.x_max = x(), .x_min = x(), .y_max = y(), .y_min = y()};
}

std::string GeoPoint::as_wkt() const {
std::stringstream ss;
ss << "POINT (";
Expand Down Expand Up @@ -787,6 +818,20 @@ const S2Point* GeoLine::getPoint(int i) const {
return &(_polyline->vertex(i));
}

BoundingBox GeoLine::bounding_box() const {
BoundingBox box;
for (int i = 0; i < numPoint(); ++i) {
const S2Point& p = *getPoint(i);
const double lon = S2LatLng::Longitude(p).degrees();
const double lat = S2LatLng::Latitude(p).degrees();
box.x_max = nan_max(box.x_max, lon);
box.x_min = nan_min(box.x_min, lon);
box.y_max = nan_max(box.y_max, lat);
box.y_min = nan_min(box.y_min, lat);
}
return box;
}

GeoParseStatus GeoPolygon::from_coords(const GeoCoordinateListList& list) {
return to_s2polygon(list, &_polygon);
}
Expand Down Expand Up @@ -1109,6 +1154,23 @@ S2Loop* GeoPolygon::getLoop(int i) const {
return _polygon->loop(i);
}

BoundingBox GeoPolygon::bounding_box() const {
BoundingBox box;
for (int loop_idx = 0; loop_idx < numLoops(); ++loop_idx) {
S2Loop* loop = getLoop(loop_idx);
for (int i = 0; i < loop->num_vertices(); ++i) {
const S2Point& p = loop->vertex(i);
const double lon = S2LatLng::Longitude(p).degrees();
const double lat = S2LatLng::Latitude(p).degrees();
box.x_max = nan_max(box.x_max, lon);
box.x_min = nan_min(box.x_min, lon);
box.y_max = nan_max(box.y_max, lat);
box.y_min = nan_min(box.y_min, lat);
}
}
return box;
}

GeoParseStatus GeoMultiPolygon::from_coords(const std::vector<GeoCoordinateListList>& list) {
_polygons.clear();
for (const auto& coords_list : list) {
Expand Down Expand Up @@ -1745,6 +1807,18 @@ double GeoMultiPolygon::Length() const {
return total_length;
}

BoundingBox GeoMultiPolygon::bounding_box() const {
BoundingBox box;
for (const auto& polygon : _polygons) {
BoundingBox sub_box = polygon->bounding_box();
box.x_max = nan_max(box.x_max, sub_box.x_max);
box.x_min = nan_min(box.x_min, sub_box.x_min);
box.y_max = nan_max(box.y_max, sub_box.y_max);
box.y_min = nan_min(box.y_min, sub_box.y_min);
}
return box;
}

double GeoCircle::Length() const {
// GeoCircle is always valid (guaranteed by constructor)
// Get the radius in meters
Expand All @@ -1754,6 +1828,15 @@ double GeoCircle::Length() const {
return 2.0 * M_PI * radius_meters;
}

BoundingBox GeoCircle::bounding_box() const {
// Treat the circle as its center point (radius is in meters, so the exact
// bounding box depends on the projection; keep it simple like a point).
const S2Point& center = _cap->center();
const double lon = S2LatLng::Longitude(center).degrees();
const double lat = S2LatLng::Latitude(center).degrees();
return {.x_max = lon, .x_min = lon, .y_max = lat, .y_min = lat};
}

double GeoPoint::Distance(const GeoShape* rhs) const {
// rhs is guaranteed to be valid by StDistance (functions_geo.cpp)
switch (rhs->type()) {
Expand Down
19 changes: 19 additions & 0 deletions be/src/exprs/function/geo/geo_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <stddef.h>

#include <limits>
#include <memory>
#include <string>
#include <vector>
Expand All @@ -40,6 +41,15 @@ using S2Point = Vector3_d;

namespace doris {

// Bounding box of a geometry, backing the Trino compatible functions
// ST_XMax / ST_XMin / ST_YMax / ST_YMin. Values are NaN when not available.
struct BoundingBox {
double x_max = std::numeric_limits<double>::quiet_NaN();
double x_min = std::numeric_limits<double>::quiet_NaN();
double y_max = std::numeric_limits<double>::quiet_NaN();
double y_min = std::numeric_limits<double>::quiet_NaN();
};

class GeoShape {
public:
virtual ~GeoShape() = default;
Expand Down Expand Up @@ -84,6 +94,10 @@ class GeoShape {
virtual int num_geometries() const { return 1; }
virtual int num_points() const { return -1; }

// Bounding box of the shape. Returns an all-NaN box for shape types that do
// not support the accessor.
virtual BoundingBox bounding_box() const { return {}; }

protected:
virtual void encode(std::string* buf) = 0;
virtual bool decode(const void* data, size_t size) = 0;
Expand Down Expand Up @@ -127,6 +141,7 @@ class GeoPoint : public GeoShape {

double x() const;
double y() const;
BoundingBox bounding_box() const override;

int num_geometries() const override { return 1; }
int num_points() const override { return 1; }
Expand Down Expand Up @@ -166,6 +181,7 @@ class GeoLine : public GeoShape {

int numPoint() const;
const S2Point* getPoint(int i) const;
BoundingBox bounding_box() const override;

int num_geometries() const override { return 1; }
int num_points() const override { return numPoint(); }
Expand Down Expand Up @@ -206,6 +222,7 @@ class GeoPolygon : public GeoShape {
double getArea() const;
double Length() const override;
double Distance(const GeoShape* rhs) const override;
BoundingBox bounding_box() const override;
S2Loop* getLoop(int i) const;

int num_geometries() const override { return 1; }
Expand Down Expand Up @@ -243,6 +260,7 @@ class GeoMultiPolygon : public GeoShape {
double getArea() const;
double Length() const override;
double Distance(const GeoShape* rhs) const override;
BoundingBox bounding_box() const override;

int num_geometries() const override { return static_cast<int>(_polygons.size()); }
int num_points() const override;
Expand Down Expand Up @@ -278,6 +296,7 @@ class GeoCircle : public GeoShape {
double getArea() const;
double Length() const override;
double Distance(const GeoShape* rhs) const override;
BoundingBox bounding_box() const override;

protected:
void encode(std::string* buf) override;
Expand Down
Loading
Loading