Skip to content

Commit

Permalink
feat: Port S2Cell operations to s2geography (#35)
Browse files Browse the repository at this point in the history
These previously lived only in the R package (
https://github.com/r-spatial/s2/blob/main/src/s2-cell.cpp ), but there's
no reason they can't live here, too!
  • Loading branch information
paleolimbot authored Oct 26, 2024
1 parent 1c67ddc commit d6f3614
Show file tree
Hide file tree
Showing 7 changed files with 667 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ add_library(s2geography
src/s2geography/geoarrow.cc
src/s2geography/wkt-reader.cc
src/s2geography/wkt-writer.cc
src/s2geography/op/cell.cc
src/s2geography/op/point.cc
# geoarrow sources
src/vendored/geoarrow/geoarrow.c
src/vendored/ryu/d2s.c
Expand Down Expand Up @@ -256,6 +258,7 @@ if(S2GEOGRAPHY_BUILD_TESTS)
add_executable(geoarrow_test src/s2geography/geoarrow_test.cc)
add_executable(geography_test src/s2geography/geography_test.cc)
add_executable(wkt_writer_test src/s2geography/wkt-writer_test.cc)
add_executable(op_cell_test src/s2geography/op/cell_test.cc)

if (S2GEOGRAPHY_CODE_COVERAGE)
target_compile_options(coverage_config INTERFACE -O0 -g --coverage)
Expand All @@ -267,12 +270,14 @@ if(S2GEOGRAPHY_BUILD_TESTS)
target_link_libraries(geoarrow_test s2geography nanoarrow GTest::gtest_main)
target_link_libraries(geography_test s2geography nanoarrow GTest::gtest_main)
target_link_libraries(wkt_writer_test s2geography GTest::gtest_main)
target_link_libraries(op_cell_test s2geography GTest::gtest_main)

include(GoogleTest)
gtest_discover_tests(distance_test)
gtest_discover_tests(geoarrow_test)
gtest_discover_tests(geography_test)
gtest_discover_tests(wkt_writer_test)
gtest_discover_tests(op_cell_test)
endif()

if(S2GEOGRAPHY_BUILD_EXAMPLES)
Expand Down
199 changes: 199 additions & 0 deletions src/s2geography/op/cell.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@

#include "s2geography/op/cell.h"

#include <array>
#include <cstdint>
#include <string_view>

#include "s2/s2cell.h"
#include "s2/s2cell_id.h"
#include "s2/s2latlng.h"

namespace s2geography::op::cell {

uint64_t FromToken::ExecuteScalar(const std::string_view cell_token) {
// This constructor may not work for all s2 versions
return S2CellId::FromToken({cell_token.data(), cell_token.size()}).id();
}

uint64_t FromDebugString::ExecuteScalar(const std::string_view debug_string) {
// This constructor may not work for all s2 versions
return S2CellId::FromDebugString({debug_string.data(), debug_string.size()})
.id();
}

uint64_t FromPoint::ExecuteScalar(Point point) {
S2Point pt(point[0], point[1], point[2]);
return S2CellId(pt).id();
}

Point ToPoint::ExecuteScalar(const uint64_t cell_id) {
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return point::kInvalidPoint;
} else {
S2Point point = S2CellId(cell_id).ToPoint();
return {point.x(), point.y(), point.z()};
}
}

std::string_view ToToken::ExecuteScalar(const uint64_t cell_id) {
last_result_ = S2CellId(cell_id).ToToken();
return last_result_;
}

std::string_view ToDebugString::ExecuteScalar(const uint64_t cell_id) {
last_result_ = S2CellId(cell_id).ToString();
return last_result_;
}

bool IsValid::ExecuteScalar(const uint64_t cell_id) {
return S2CellId(cell_id).is_valid();
}

Point CellCenter::ExecuteScalar(const uint64_t cell_id) {
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return point::kInvalidPoint;
}

S2Point pt = S2Cell(cell).GetCenter();
return {pt.x(), pt.y(), pt.z()};
}

Point CellVertex::ExecuteScalar(const uint64_t cell_id,
const int8_t vertex_id) {
S2CellId cell(cell_id);

if (vertex_id < 0 || !cell.is_valid()) {
return point::kInvalidPoint;
}

S2Point pt = S2Cell(cell).GetVertex(vertex_id);
return {pt.x(), pt.y(), pt.z()};
}

int8_t Level::ExecuteScalar(const uint64_t cell_id) {
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return -1;
}

return cell.level();
}

double Area::ExecuteScalar(const uint64_t cell_id) {
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return NAN;
}

return S2Cell(cell).ExactArea();
}

double AreaApprox::ExecuteScalar(const uint64_t cell_id) {
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return NAN;
}

return S2Cell(cell).ApproxArea();
}

uint64_t Parent::ExecuteScalar(const uint64_t cell_id, const int8_t level) {
// allow negative numbers to relate to current level
S2CellId cell(cell_id);
if (!cell.is_valid()) {
return kCellIdSentinel;
}

int8_t level_final;
int8_t cell_level = cell.level();
if (level < 0) {
level_final = cell.level() + level;
} else {
level_final = level;
}

if (level_final > cell.level() || level_final < 0) {
return kCellIdSentinel;
} else {
return cell.parent(level_final).id();
}
}

uint64_t Child::ExecuteScalar(const uint64_t cell_id, const int8_t k) {
if (k < 0 || k > 3) {
return kCellIdSentinel;
}

return S2CellId(cell_id).child(k).id();
}

uint64_t EdgeNeighbor::ExecuteScalar(const uint64_t cell_id, const int8_t k) {
S2CellId cell(cell_id);
if (k < 0 || k > 3) {
return kCellIdSentinel;
}

S2CellId neighbours[4];
cell.GetEdgeNeighbors(neighbours);
return neighbours[k].id();
}

bool Contains::ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) {
S2CellId cell(cell_id);
S2CellId cell_test(cell_id_test);
if (!cell.is_valid() || !cell_test.is_valid()) {
return false;
}

return cell.contains(cell_test);
}

bool MayIntersect::ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) {
S2CellId cell(cell_id);
S2CellId cell_test(cell_id_test);
if (!cell.is_valid() || !cell_test.is_valid()) {
return false;
}

return S2Cell(cell).MayIntersect(S2Cell(cell_test));
}

double Distance::ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) {
S2CellId cell(cell_id);
S2CellId cell_test(cell_id_test);
if (!cell.is_valid() || !cell_test.is_valid()) {
return NAN;
}

return S2Cell(cell).GetDistance(S2Cell(cell_test)).radians();
}

double MaxDistance::ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) {
S2CellId cell(cell_id);
S2CellId cell_test(cell_id_test);
if (!cell.is_valid() || !cell_test.is_valid()) {
return NAN;
}

return S2Cell(cell).GetMaxDistance(S2Cell(cell_test)).radians();
}

int8_t CommonAncestorLevel::ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) {
S2CellId cell(cell_id);
S2CellId cell_test(cell_id_test);
if (!cell.is_valid() || !cell_test.is_valid()) {
return -1;
}

return cell.GetCommonAncestorLevel(cell_test);
}

} // namespace s2geography::op::cell
162 changes: 162 additions & 0 deletions src/s2geography/op/cell.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#pragma once

#include <array>
#include <cstdint>
#include <string>
#include <string_view>

#include "s2geography/op/op.h"
#include "s2geography/op/point.h"

namespace s2geography {

namespace op {

namespace cell {

using point::LngLat;
using point::Point;

/// \brief Cell identifier returned for invalid input
static constexpr uint64_t kCellIdNone = 0;

/// \brief Cell identifier that is greater than all other cells
static constexpr uint64_t kCellIdSentinel = ~uint64_t{0};

/// \brief Create a cell identifier from a token
class FromToken : public UnaryOp<uint64_t, std::string_view> {
public:
uint64_t ExecuteScalar(const std::string_view cell_token) override;
};

/// \brief Create a cell identifier from a debug string
class FromDebugString : public UnaryOp<uint64_t, std::string_view> {
public:
uint64_t ExecuteScalar(const std::string_view debug_string) override;
};

/// \brief Create a cell identifier from an xyz unit vector
class FromPoint : public UnaryOp<uint64_t, Point> {
public:
uint64_t ExecuteScalar(Point point) override;
};

/// \brief Calculate the cell centre as an xyz vector
class ToPoint : public UnaryOp<Point, uint64_t> {
public:
Point ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Get the token string of a cell identifier
class ToToken : public UnaryOp<std::string_view, uint64_t> {
public:
std::string_view ExecuteScalar(const uint64_t cell_id) override;

private:
std::string last_result_;
};

/// \brief Get the debug string of a cell identifier
class ToDebugString : public UnaryOp<std::string_view, uint64_t> {
public:
std::string_view ExecuteScalar(const uint64_t cell_id) override;

private:
std::string last_result_;
};

/// \brief Returns true if the ID is a valid cell identifier or false otherwise
class IsValid : public UnaryOp<bool, uint64_t> {
public:
bool ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Retrieve the corners of a cell
class CellCenter : public UnaryOp<Point, uint64_t> {
public:
Point ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Retrieve the corners of a cell
class CellVertex : public BinaryOp<Point, uint64_t, int8_t> {
public:
Point ExecuteScalar(const uint64_t cell_id, const int8_t vertex_id) override;
};

/// \brief Calculate the level represented by the cell
class Level : public UnaryOp<int8_t, uint64_t> {
public:
int8_t ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Calculate the area of a given cell
class Area : public UnaryOp<double, uint64_t> {
public:
double ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Calculate the approximate area of a given cell
class AreaApprox : public UnaryOp<double, uint64_t> {
public:
double ExecuteScalar(const uint64_t cell_id) override;
};

/// \brief Calculate the parent cell at a given level
class Parent : public BinaryOp<uint64_t, uint64_t, int8_t> {
public:
uint64_t ExecuteScalar(const uint64_t cell_id, const int8_t level) override;
};

/// \brief Calculate the child cell at the next level
class Child : public BinaryOp<uint64_t, uint64_t, int8_t> {
public:
uint64_t ExecuteScalar(const uint64_t cell_id, const int8_t k) override;
};

/// \brief Get the edge neighbours of a given cell
class EdgeNeighbor : public BinaryOp<uint64_t, uint64_t, int8_t> {
public:
uint64_t ExecuteScalar(const uint64_t cell_id, const int8_t k) override;
};

/// \brief Returns true if cell_id contains cell_id_test or false otherwise
class Contains : public BinaryOp<bool, uint64_t, uint64_t> {
public:
bool ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) override;
};

/// \brief Returns true if cell_id might intersect cell_id_test or false
/// otherwise
class MayIntersect : public BinaryOp<bool, uint64_t, uint64_t> {
public:
bool ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) override;
};

/// \brief Returns the minimum spherical distance (in radians) between two cells
class Distance : public BinaryOp<double, uint64_t, uint64_t> {
public:
double ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) override;
};

/// \brief Returns the maximum spherical distance (in radians) between two cells
class MaxDistance : public BinaryOp<double, uint64_t, uint64_t> {
public:
double ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) override;
};

/// \brief Returns the level at which the two cells have a common ancestor
class CommonAncestorLevel : public BinaryOp<int8_t, uint64_t, uint64_t> {
public:
int8_t ExecuteScalar(const uint64_t cell_id,
const uint64_t cell_id_test) override;
};

} // namespace cell

} // namespace op

} // namespace s2geography
Loading

0 comments on commit d6f3614

Please sign in to comment.