1
0
Fork 0

Upgrade S2 Library (#7519)

This commit is contained in:
Simon 2018-11-29 15:49:17 +01:00 committed by Jan
parent 5e8002adc7
commit 8b0e0b0b75
434 changed files with 13680 additions and 3931 deletions

View File

@ -1,16 +0,0 @@
include(${SWIG_USE_FILE})
include_directories(${PYTHON_INCLUDE_PATH})
set(CMAKE_SWIG_FLAGS "")
set_property(SOURCE s2.i PROPERTY SWIG_FLAGS "-module" "pywraps2")
set_property(SOURCE s2.i PROPERTY CPLUSPLUS ON)
swig_add_module(pywraps2 python s2.i)
swig_link_libraries(pywraps2 ${PYTHON_LIBRARIES} s2)
enable_testing()
add_test(NAME pywraps2_test COMMAND
${PYTHON_EXECUTABLE}
"${PROJECT_SOURCE_DIR}/src/python/pywraps2_test.py")
set_property(TEST pywraps2_test PROPERTY ENVIRONMENT
"PYTHONPATH=$ENV{PYTHONPATH}:${PROJECT_BINARY_DIR}/python")
install(TARGETS _pywraps2 DESTINATION share/python)
install(FILES "${PROJECT_BINARY_DIR}/python/pywraps2.py"
DESTINATION share/python)

View File

@ -1,46 +0,0 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "s2/base/sysinfo.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/resource.h> // for rusage, RUSAGE_SELF
#include <sys/time.h>
#endif
namespace base {
absl::Duration CPUUsage() {
#ifdef _WIN32
FILETIME creation, exit, kernel, user;
int res = GetProcessTimes(GetCurrentProcess(), &creation,
&exit, &kernel, &user);
if (res != 0) return absl::ZeroDuration();
// high and low combine to 64-bit int
ULARGE_INTEGER conv;
conv.LowPart = user.dwLowDateTime;
conv.HighPart = user.dwHighDateTime;
// time uses 100-nanosecond intervals
return absl::Duration(conv.QuadPart / 1e7);
#else
struct rusage ru;
if (getrusage(RUSAGE_SELF, &ru) != 0) return absl::ZeroDuration();
return absl::Duration(ru.ru_utime.tv_sec + ru.ru_utime.tv_usec / 1e6);
#endif
}
} // namespace base

View File

@ -1,51 +0,0 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef S2_BASE_SYSINFO_H_
#define S2_BASE_SYSINFO_H_
// TODO(user): Remove this hacky Duration implementation
// when we use the absl one.
namespace absl {
// Don't use any Duration functions outside of sysinfo.h/cc.
class Duration {
public:
explicit Duration(double seconds) : seconds_(seconds) {}
double seconds() const { return seconds_; }
private:
double seconds_;
};
bool operator!=(Duration lhs, Duration rhs) {
return lhs.seconds() != rhs.seconds();
}
double ToDoubleSeconds(Duration d) { return d.seconds(); }
Duration ZeroDuration() { return Duration(0.0); }
} // namespace absl
namespace base {
// Return the total CPU time used by all threads in the current process.
absl::Duration CPUUsage();
} // namespace base
#endif // S2_BASE_SYSINFO_H_

View File

@ -1,40 +0,0 @@
// Copyright 2005 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/s2centroids.h"
#include <gtest/gtest.h>
#include "s2/s2testing.h"
TEST(S2, TrueCentroid) {
// Test TrueCentroid() with very small triangles. This test assumes that
// the triangle is small enough so that it is nearly planar.
for (int i = 0; i < 100; ++i) {
Vector3_d p, x, y;
S2Testing::GetRandomFrame(&p, &x, &y);
double d = 1e-4 * pow(1e-4, S2Testing::rnd.RandDouble());
S2Point p0 = (p - d * x).Normalize();
S2Point p1 = (p + d * x).Normalize();
S2Point p2 = (p + 3 * d * y).Normalize();
S2Point centroid = S2::TrueCentroid(p0, p1, p2).Normalize();
// The centroid of a planar triangle is at the intersection of its
// medians, which is two-thirds of the way along each median.
S2Point expected_centroid = (p + d * y).Normalize();
EXPECT_LE(centroid.Angle(expected_centroid), 2e-8);
}
}

View File

@ -1,176 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
//
// This file contains some basic tests of the templating support. Testing of
// the actual algorithms is in s2closest_edge_query_test.cc.
#include "s2/s2closest_edge_query_base.h"
#include <vector>
#include <gtest/gtest.h>
#include "s2/mutable_s2shape_index.h"
#include "s2/s1angle.h"
#include "s2/s2cap.h"
#include "s2/s2contains_point_query.h"
#include "s2/s2edge_distances.h"
#include "s2/s2text_format.h"
using std::vector;
namespace {
// This is a proof-of-concept prototype of a possible S2FurthestEdgeQuery
// class. The purpose of this test is just to make sure that the code
// compiles and does something reasonable. (A real implementation would need
// to be more careful about error bounds, it would implement a greater range
// of target types, etc.)
//
// It is based on the principle that for any two geometric objects X and Y on
// the sphere,
//
// max_dist(X, Y) = Pi - min_dist(-X, Y)
//
// where "-X" denotes the reflection of X through the origin (i.e., to the
// opposite side of the sphere).
// MaxDistance is a class that allows maximum distances to be computed using a
// minimum distance algorithm. It essentially treats a distance "x" as the
// supplementary distance (Pi - x).
class MaxDistance {
public:
// Expose only the methods that are documented as necessary in the API.
class Delta {
public:
Delta() {}
Delta(const Delta& x) : a_(x.a_) {}
Delta& operator=(const Delta& x) { a_ = x.a_; return *this; }
friend bool operator==(Delta x, Delta y) { return x.a_ == y.a_; }
static Delta Zero() { Delta r; r.a_ = S1ChordAngle::Zero(); return r; }
// This method is needed to implement Distance::operator-.
explicit operator S1ChordAngle() const { return a_; }
private:
S1ChordAngle a_;
};
MaxDistance() : distance_() {}
explicit MaxDistance(S1ChordAngle x) : distance_(x) {}
explicit operator S1ChordAngle() const { return distance_; }
static MaxDistance Zero() {
return MaxDistance(S1ChordAngle::Straight());
}
static MaxDistance Infinity() {
return MaxDistance(S1ChordAngle::Negative());
}
static MaxDistance Negative() {
return MaxDistance(S1ChordAngle::Infinity());
}
friend bool operator==(MaxDistance x, MaxDistance y) {
return x.distance_ == y.distance_;
}
friend bool operator<(MaxDistance x, MaxDistance y) {
return x.distance_ > y.distance_;
}
friend MaxDistance operator-(MaxDistance x, Delta delta) {
return MaxDistance(x.distance_ + S1ChordAngle(delta));
}
S1ChordAngle GetChordAngleBound() const {
return S1ChordAngle::Straight() - distance_;
}
// If (dist < *this), updates *this and returns true (used internally).
bool UpdateMin(const MaxDistance& dist) {
if (dist < *this) {
*this = dist;
return true;
}
return false;
}
private:
S1ChordAngle distance_;
};
using FurthestEdgeQuery = S2ClosestEdgeQueryBase<MaxDistance>;
class FurthestPointTarget final : public FurthestEdgeQuery::Target {
public:
explicit FurthestPointTarget(const S2Point& point) : point_(point) {}
int max_brute_force_index_size() const override { return 100; }
S2Cap GetCapBound() override {
return S2Cap(-point_, S1ChordAngle::Zero());
}
bool UpdateMinDistance(const S2Point& p, MaxDistance* min_dist) override {
return min_dist->UpdateMin(MaxDistance(S1ChordAngle(p, point_)));
}
bool UpdateMinDistance(const S2Point& v0, const S2Point& v1,
MaxDistance* min_dist) override {
S1ChordAngle dist180 =
S1ChordAngle(*min_dist).is_negative() ? S1ChordAngle::Infinity() :
S1ChordAngle::Straight() - S1ChordAngle(*min_dist);
if (!S2::UpdateMinDistance(-point_, v0, v1, &dist180)) return false;
*min_dist = MaxDistance(S1ChordAngle::Straight() - dist180);
return true;
}
bool UpdateMinDistance(const S2Cell& cell,
MaxDistance* min_dist) override {
return min_dist->UpdateMin(
MaxDistance(S1ChordAngle::Straight() - cell.GetDistance(-point_)));
}
bool VisitContainingShapes(const S2ShapeIndex& index,
const ShapeVisitor& visitor) override {
// For furthest points, we return the polygons whose interior contains the
// antipode of the target point. (These are the polygons whose
// MaxDistance() to the target is MaxDistance::Zero().)
//
// For target types consisting of multiple connected components (such as
// FurthestPointQuery::ShapeIndexTarget), this method should return the
// polygons containing the antipodal reflection of any connected
// component. (It is sufficient to test containment of one vertex per
// connected component, since the API allows us to also return any polygon
// whose boundary has MaxDistance::Zero() to the target.)
return MakeS2ContainsPointQuery(&index).VisitContainingShapes(
-point_, [this, &visitor](S2Shape* shape) {
return visitor(shape, point_);
});
}
private:
S2Point point_;
};
TEST(S2ClosestEdgeQueryBase, MaxDistance) {
auto index = s2textformat::MakeIndex("0:0 | 1:0 | 2:0 | 3:0 # #");
FurthestEdgeQuery query(index.get());
FurthestPointTarget target(s2textformat::MakePoint("4:0"));
FurthestEdgeQuery::Options options;
options.set_max_edges(1);
auto results = query.FindClosestEdges(&target, options);
ASSERT_EQ(1, results.size());
EXPECT_EQ(0, results[0].shape_id);
EXPECT_EQ(0, results[0].edge_id);
EXPECT_NEAR(4, S1ChordAngle(results[0].distance).ToAngle().degrees(), 1e-13);
}
} // namespace

View File

@ -1,153 +0,0 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
//
// This file contains some basic tests of the templating support. Testing of
// the actual algorithms is in s2closest_point_query_test.cc.
#include "s2/s2closest_point_query_base.h"
#include <vector>
#include <gtest/gtest.h>
#include "s2/s1angle.h"
#include "s2/s2cap.h"
#include "s2/s2contains_point_query.h"
#include "s2/s2edge_distances.h"
#include "s2/s2text_format.h"
using std::vector;
namespace {
// This is a proof-of-concept prototype of a possible S2FurthestPointQuery
// class. The purpose of this test is just to make sure that the code
// compiles and does something reasonable. (A real implementation would need
// to be more careful about error bounds, it would implement a greater range
// of target types, etc.)
//
// It is based on the principle that for any two geometric objects X and Y on
// the sphere,
//
// max_dist(X, Y) = Pi - min_dist(-X, Y)
//
// where "-X" denotes the reflection of X through the origin (i.e., to the
// opposite side of the sphere).
// MaxDistance is a class that allows maximum distances to be computed using a
// minimum distance algorithm. It essentially treats a distance "x" as the
// supplementary distance (Pi - x).
class MaxDistance {
public:
using Delta = S1ChordAngle;
MaxDistance() : distance_() {}
explicit MaxDistance(S1ChordAngle x) : distance_(x) {}
explicit operator S1ChordAngle() const { return distance_; }
static MaxDistance Zero() {
return MaxDistance(S1ChordAngle::Straight());
}
static MaxDistance Infinity() {
return MaxDistance(S1ChordAngle::Negative());
}
static MaxDistance Negative() {
return MaxDistance(S1ChordAngle::Infinity());
}
friend bool operator==(MaxDistance x, MaxDistance y) {
return x.distance_ == y.distance_;
}
friend bool operator<(MaxDistance x, MaxDistance y) {
return x.distance_ > y.distance_;
}
friend MaxDistance operator-(MaxDistance x, S1ChordAngle delta) {
return MaxDistance(x.distance_ + delta);
}
S1ChordAngle GetChordAngleBound() const {
return S1ChordAngle::Straight() - distance_;
}
// If (dist < *this), updates *this and returns true (used internally).
bool UpdateMin(const MaxDistance& dist) {
if (dist < *this) {
*this = dist;
return true;
}
return false;
}
private:
S1ChordAngle distance_;
};
using FurthestPointQueryOptions = S2ClosestPointQueryBaseOptions<MaxDistance>;
class FurthestPointQueryTarget final : public S2DistanceTarget<MaxDistance> {
public:
explicit FurthestPointQueryTarget(const S2Point& point) : point_(point) {}
int max_brute_force_index_size() const override { return 100; }
S2Cap GetCapBound() override {
return S2Cap(-point_, S1ChordAngle::Zero());
}
bool UpdateMinDistance(const S2Point& p, MaxDistance* min_dist) override {
return min_dist->UpdateMin(MaxDistance(S1ChordAngle(p, point_)));
}
bool UpdateMinDistance(const S2Point& v0, const S2Point& v1,
MaxDistance* min_dist) override {
S2_LOG(FATAL) << "Unimplemented";
return false;
}
bool UpdateMinDistance(const S2Cell& cell,
MaxDistance* min_dist) override {
return min_dist->UpdateMin(
MaxDistance(S1ChordAngle::Straight() - cell.GetDistance(-point_)));
}
bool VisitContainingShapes(const S2ShapeIndex& index,
const ShapeVisitor& visitor) override {
S2_LOG(FATAL) << "Unimplemented";
return false;
}
private:
S2Point point_;
};
template <class Data>
using FurthestPointQuery = S2ClosestPointQueryBase<MaxDistance, Data>;
TEST(S2ClosestPointQueryBase, MaxDistance) {
S2PointIndex<int> index;
auto points = s2textformat::ParsePointsOrDie("0:0, 1:0, 2:0, 3:0");
for (int i = 0; i < points.size(); ++i) {
index.Add(points[i], i);
}
FurthestPointQuery<int> query(&index);
FurthestPointQueryTarget target(s2textformat::MakePoint("4:0"));
FurthestPointQueryOptions options;
options.set_max_points(1);
auto results = query.FindClosestPoints(&target, options);
ASSERT_EQ(1, results.size());
EXPECT_EQ(points[0], results[0].point());
EXPECT_EQ(0, results[0].data());
EXPECT_NEAR(4, S1ChordAngle(results[0].distance()).ToAngle().degrees(),
1e-13);
}
} // namespace

View File

@ -1,251 +0,0 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/s2closest_point_query.h"
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/s1angle.h"
#include "s2/s2cap.h"
#include "s2/s2cell_id.h"
#include "s2/s2edge_distances.h"
#include "s2/s2loop.h"
#include "s2/s2pointutil.h"
#include "s2/s2testing.h"
using absl::make_unique;
using std::make_pair;
using std::pair;
using std::unique_ptr;
using std::vector;
using TestIndex = S2PointIndex<int>;
using TestQuery = S2ClosestPointQuery<int>;
TEST(S2ClosestPointQuery, NoPoints) {
TestIndex index;
TestQuery query(&index);
S2ClosestPointQueryPointTarget target(S2Point(1, 0, 0));
const auto results = query.FindClosestPoints(&target);
EXPECT_EQ(0, results.size());
}
TEST(S2ClosestPointQuery, ManyDuplicatePoints) {
static const int kNumPoints = 10000;
const S2Point kTestPoint(1, 0, 0);
TestIndex index;
for (int i = 0; i < kNumPoints; ++i) {
index.Add(kTestPoint, i);
}
TestQuery query(&index);
S2ClosestPointQueryPointTarget target(kTestPoint);
const auto results = query.FindClosestPoints(&target);
EXPECT_EQ(kNumPoints, results.size());
}
// An abstract class that adds points to an S2PointIndex for benchmarking.
struct PointIndexFactory {
public:
virtual ~PointIndexFactory() {}
// Given an index that will be queried using random points from "query_cap",
// adds approximately "num_points" points to "index". (Typically the
// indexed points will occupy some fraction of this cap.)
virtual void AddPoints(const S2Cap& query_cap, int num_points,
TestIndex* index) const = 0;
};
// Generates points that are regularly spaced along a circle. The circle is
// centered within the query cap and occupies 25% of its area, so that random
// query points have a 25% chance of being inside the circle.
//
// Points along a circle are nearly the worst case for distance calculations,
// since many points are nearly equidistant from any query point that is not
// immediately adjacent to the circle.
struct CirclePointIndexFactory : public PointIndexFactory {
void AddPoints(const S2Cap& query_cap, int num_points,
TestIndex* index) const override {
std::vector<S2Point> points = S2Testing::MakeRegularPoints(
query_cap.center(), 0.5 * query_cap.GetRadius(), num_points);
for (int i = 0; i < points.size(); ++i) {
index->Add(points[i], i);
}
}
};
// Generate the vertices of a fractal whose convex hull approximately
// matches the query cap.
struct FractalPointIndexFactory : public PointIndexFactory {
void AddPoints(const S2Cap& query_cap, int num_points,
TestIndex* index) const override {
S2Testing::Fractal fractal;
fractal.SetLevelForApproxMaxEdges(num_points);
fractal.set_fractal_dimension(1.5);
unique_ptr<S2Loop> loop(
fractal.MakeLoop(S2Testing::GetRandomFrameAt(query_cap.center()),
query_cap.GetRadius()));
for (int i = 0; i < loop->num_vertices(); ++i) {
index->Add(loop->vertex(i), i);
}
}
};
// Generate vertices on a square grid that includes the entire query cap.
struct GridPointIndexFactory : public PointIndexFactory {
void AddPoints(const S2Cap& query_cap, int num_points,
TestIndex* index) const override {
int sqrt_num_points = ceil(sqrt(num_points));
Matrix3x3_d frame = S2Testing::GetRandomFrameAt(query_cap.center());
double radius = query_cap.GetRadius().radians();
double spacing = 2 * radius / sqrt_num_points;
for (int i = 0; i < sqrt_num_points; ++i) {
for (int j = 0; j < sqrt_num_points; ++j) {
S2Point point(tan((i + 0.5) * spacing - radius),
tan((j + 0.5) * spacing - radius), 1.0);
index->Add(S2::FromFrame(frame, point.Normalize()),
i * sqrt_num_points + j);
}
}
}
};
// The approximate radius of S2Cap from which query points are chosen.
static const S1Angle kRadius = S2Testing::KmToAngle(10);
// An approximate bound on the distance measurement error for "reasonable"
// distances (say, less than Pi/2) due to using S1ChordAngle.
static double kChordAngleError = 1e-15;
// TODO(user): Remove Result and use TestQuery::Result.
using Result = pair<S2MinDistance, int>;
static S1Angle GetDistance(TestQuery::Target* target,
const S2Point& point) {
TestQuery::Distance distance = TestQuery::Distance::Infinity();
target->UpdateMinDistance(point, &distance);
return distance.ToAngle();
}
// Use "query" to find the closest point(s) to the given target, and extract
// the query results into the given vector. Also verify that the results
// satisfy the search criteria.
static void GetClosestPoints(TestQuery::Target* target, TestQuery* query,
std::vector<Result>* results) {
const auto query_results = query->FindClosestPoints(target);
EXPECT_LE(query_results.size(), query->options().max_points());
if (!query->options().region() &&
query->options().max_distance() == S1ChordAngle::Infinity()) {
// We can predict exactly how many points should be returned.
EXPECT_EQ(std::min(query->options().max_points(),
query->index().num_points()),
query_results.size());
}
for (const auto& result : query_results) {
// Check that query->distance() is approximately equal to the
// S1Angle(point, target) distance. They may be slightly different
// because query->distance() is computed using S1ChordAngle. Note that
// the error gets considerably larger (1e-7) as the angle approaches Pi.
EXPECT_NEAR(GetDistance(target, result.point()).radians(),
result.distance().radians(), kChordAngleError);
// Check that the point satisfies the region() condition.
if (query->options().region()) {
EXPECT_TRUE(query->options().region()->Contains(result.point()));
}
// Check that it satisfies the max_distance() condition.
EXPECT_LE(result.distance(), query->options().max_distance());
results->push_back(make_pair(result.distance(), result.data()));
}
}
static void TestFindClosestPoints(TestQuery::Target* target, TestQuery *query) {
std::vector<Result> expected, actual;
query->mutable_options()->set_use_brute_force(true);
GetClosestPoints(target, query, &expected);
query->mutable_options()->set_use_brute_force(false);
GetClosestPoints(target, query, &actual);
EXPECT_TRUE(
CheckDistanceResults(expected, actual, query->options().max_points(),
query->options().max_distance(),
S2MinDistance::Delta::Zero()))
<< "max_points=" << query->options().max_points()
<< ", max_distance=" << query->options().max_distance();
}
// The running time of this test is proportional to
// num_indexes * num_points * num_queries.
static void TestWithIndexFactory(const PointIndexFactory& factory,
int num_indexes, int num_points,
int num_queries) {
TestIndex index;
for (int i_index = 0; i_index < num_indexes; ++i_index) {
// Generate a point set and index it.
S2Cap query_cap = S2Cap(S2Testing::RandomPoint(), kRadius);
index.Clear();
factory.AddPoints(query_cap, num_points, &index);
for (int i_query = 0; i_query < num_queries; ++i_query) {
// Use a new query each time to avoid resetting default parameters.
TestQuery::Options options;
options.set_max_points(1 + S2Testing::rnd.Uniform(100));
if (S2Testing::rnd.OneIn(2)) {
options.set_max_distance(S2Testing::rnd.RandDouble() * kRadius);
}
S2LatLngRect rect = S2LatLngRect::FromCenterSize(
S2LatLng(S2Testing::SamplePoint(query_cap)),
S2LatLng(S2Testing::rnd.RandDouble() * kRadius,
S2Testing::rnd.RandDouble() * kRadius));
if (S2Testing::rnd.OneIn(5)) {
options.set_region(&rect);
}
TestQuery query(&index, options);
if (S2Testing::rnd.OneIn(2)) {
S2ClosestPointQueryPointTarget target(
S2Testing::SamplePoint(query_cap));
TestFindClosestPoints(&target, &query);
} else {
S2Point a = S2Testing::SamplePoint(query_cap);
S2Point b = S2Testing::SamplePoint(
S2Cap(a, pow(1e-4, S2Testing::rnd.RandDouble()) * kRadius));
S2ClosestPointQueryEdgeTarget target(a, b);
TestFindClosestPoints(&target, &query);
}
}
}
}
static const int kNumIndexes = 10;
static const int kNumVertices = 1000;
static const int kNumQueries = 50;
TEST(S2ClosestPointQueryTest, CirclePoints) {
TestWithIndexFactory(CirclePointIndexFactory(),
kNumIndexes, kNumVertices, kNumQueries);
}
TEST(S2ClosestPointQueryTest, FractalPoints) {
TestWithIndexFactory(FractalPointIndexFactory(),
kNumIndexes, kNumVertices, kNumQueries);
}
TEST(S2ClosestPointQueryTest, GridPoints) {
TestWithIndexFactory(GridPointIndexFactory(),
kNumIndexes, kNumVertices, kNumQueries);
}

View File

@ -1,176 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/s2lax_polygon_shape.h"
#include "s2/s2shapeutil_get_reference_point.h"
using absl::Span;
using std::vector;
using ChainPosition = S2Shape::ChainPosition;
S2LaxPolygonShape::S2LaxPolygonShape(
const vector<S2LaxPolygonShape::Loop>& loops) {
Init(loops);
}
S2LaxPolygonShape::S2LaxPolygonShape(const S2Polygon& polygon) {
Init(polygon);
}
void S2LaxPolygonShape::Init(const vector<S2LaxPolygonShape::Loop>& loops) {
vector<Span<const S2Point>> spans;
for (const S2LaxPolygonShape::Loop& loop : loops) {
spans.emplace_back(loop);
}
Init(spans);
}
void S2LaxPolygonShape::Init(const S2Polygon& polygon) {
vector<Span<const S2Point>> spans;
for (int i = 0; i < polygon.num_loops(); ++i) {
const S2Loop* loop = polygon.loop(i);
if (loop->is_full()) {
spans.emplace_back(); // Empty span.
} else {
spans.emplace_back(&loop->vertex(0), loop->num_vertices());
}
}
Init(spans);
}
void S2LaxPolygonShape::Init(const vector<Span<const S2Point>>& loops) {
num_loops_ = loops.size();
if (num_loops_ == 0) {
num_vertices_ = 0;
vertices_ = nullptr;
} else if (num_loops_ == 1) {
num_vertices_ = loops[0].size();
vertices_.reset(new S2Point[num_vertices_]);
std::copy(loops[0].begin(), loops[0].end(), vertices_.get());
} else {
cumulative_vertices_ = new int32[num_loops_ + 1];
int32 num_vertices = 0;
for (int i = 0; i < num_loops_; ++i) {
cumulative_vertices_[i] = num_vertices;
num_vertices += loops[i].size();
}
cumulative_vertices_[num_loops_] = num_vertices;
vertices_.reset(new S2Point[num_vertices]);
for (int i = 0; i < num_loops_; ++i) {
std::copy(loops[i].begin(), loops[i].end(),
vertices_.get() + cumulative_vertices_[i]);
}
}
}
S2LaxPolygonShape::~S2LaxPolygonShape() {
if (num_loops() > 1) {
delete[] cumulative_vertices_;
}
}
int S2LaxPolygonShape::num_vertices() const {
if (num_loops() <= 1) {
return num_vertices_;
} else {
return cumulative_vertices_[num_loops()];
}
}
int S2LaxPolygonShape::num_loop_vertices(int i) const {
S2_DCHECK_LT(i, num_loops());
if (num_loops() == 1) {
return num_vertices_;
} else {
return cumulative_vertices_[i + 1] - cumulative_vertices_[i];
}
}
const S2Point& S2LaxPolygonShape::loop_vertex(int i, int j) const {
S2_DCHECK_LT(i, num_loops());
S2_DCHECK_LT(j, num_loop_vertices(i));
if (num_loops() == 1) {
return vertices_[j];
} else {
return vertices_[cumulative_vertices_[i] + j];
}
}
S2Shape::Edge S2LaxPolygonShape::edge(int e0) const {
S2_DCHECK_LT(e0, num_edges());
int e1 = e0 + 1;
if (num_loops() == 1) {
if (e1 == num_vertices_) { e1 = 0; }
} else {
// Find the index of the first vertex of the loop following this one.
const int kMaxLinearSearchLoops = 12; // From benchmarks.
int* next = cumulative_vertices_ + 1;
if (num_loops() <= kMaxLinearSearchLoops) {
while (*next <= e0) ++next;
} else {
next = std::lower_bound(next, next + num_loops(), e1);
}
// Wrap around to the first vertex of the loop if necessary.
if (e1 == *next) { e1 = next[-1]; }
}
return Edge(vertices_[e0], vertices_[e1]);
}
S2Shape::ReferencePoint S2LaxPolygonShape::GetReferencePoint() const {
return s2shapeutil::GetReferencePoint(*this);
}
S2Shape::Chain S2LaxPolygonShape::chain(int i) const {
S2_DCHECK_LT(i, num_loops());
if (num_loops() == 1) {
return Chain(0, num_vertices_);
} else {
int start = cumulative_vertices_[i];
return Chain(start, cumulative_vertices_[i + 1] - start);
}
}
S2Shape::Edge S2LaxPolygonShape::chain_edge(int i, int j) const {
S2_DCHECK_LT(i, num_loops());
S2_DCHECK_LT(j, num_loop_vertices(i));
int n = num_loop_vertices(i);
int k = (j + 1 == n) ? 0 : j + 1;
if (num_loops() == 1) {
return Edge(vertices_[j], vertices_[k]);
} else {
int base = cumulative_vertices_[i];
return Edge(vertices_[base + j], vertices_[base + k]);
}
}
S2Shape::ChainPosition S2LaxPolygonShape::chain_position(int e) const {
S2_DCHECK_LT(e, num_edges());
const int kMaxLinearSearchLoops = 12; // From benchmarks.
if (num_loops() == 1) {
return ChainPosition(0, e);
} else {
// Find the index of the first vertex of the loop following this one.
int* next = cumulative_vertices_ + 1;
if (num_loops() <= kMaxLinearSearchLoops) {
while (*next <= e) ++next;
} else {
next = std::upper_bound(next, next + num_loops(), e);
}
return ChainPosition(next - (cumulative_vertices_ + 1), e - next[-1]);
}
}

View File

@ -1,68 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_S2POINT_VECTOR_SHAPE_H_
#define S2_S2POINT_VECTOR_SHAPE_H_
#include <vector>
#include "s2/s2shape.h"
// S2PointVectorShape is an S2Shape representing a set of S2Points. Each point
// is reprsented as a degenerate edge with the same starting and ending
// vertices.
//
// This class is useful for adding a collection of points to an S2ShapeIndex.
class S2PointVectorShape : public S2Shape {
public:
// Constructs an empty point vector.
S2PointVectorShape() {}
// Constructs an S2PointVectorShape from a vector of points.
explicit S2PointVectorShape(std::vector<S2Point> points) {
points_ = std::move(points);
}
~S2PointVectorShape() override = default;
int num_points() const { return static_cast<int>(points_.size()); }
const S2Point& point(int i) const { return points_[i]; }
// S2Shape interface:
int num_edges() const final { return static_cast<int>(points_.size()); }
Edge edge(int e) const final {
return Edge(points_[e], points_[e]);
}
int dimension() const final { return 0; }
ReferencePoint GetReferencePoint() const final {
return ReferencePoint::Contained(false);
}
int num_chains() const final { return static_cast<int>(points_.size()); }
Chain chain(int i) const final { return Chain(i, 1); }
Edge chain_edge(int i, int j) const final {
S2_DCHECK_EQ(j, 0);
return Edge(points_[i], points_[i]);
}
ChainPosition chain_position(int e) const final {
return ChainPosition(e, 0);
}
private:
std::vector<S2Point> points_;
};
#endif // S2_S2POINT_VECTOR_SHAPE_H_

View File

@ -1,55 +0,0 @@
// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/s2shape_index.h"
bool S2ClippedShape::ContainsEdge(int id) const {
// Linear search is fast because the number of edges per shape is typically
// very small (less than 10).
for (int e = 0; e < num_edges(); ++e) {
if (edge(e) == id) return true;
}
return false;
}
S2ShapeIndexCell::~S2ShapeIndexCell() {
// Free memory for all shapes owned by this cell.
for (S2ClippedShape& s : shapes_)
s.Destruct();
shapes_.clear();
}
const S2ClippedShape*
S2ShapeIndexCell::find_clipped(int shape_id) const {
// Linear search is fine because the number of shapes per cell is typically
// very small (most often 1), and is large only for pathological inputs
// (e.g. very deeply nested loops).
for (const auto& s : shapes_) {
if (s.shape_id() == shape_id) return &s;
}
return nullptr;
}
// Allocate room for "n" additional clipped shapes in the cell, and return a
// pointer to the first new clipped shape. Expects that all new clipped
// shapes will have a larger shape id than any current shape, and that shapes
// will be added in increasing shape id order.
S2ClippedShape* S2ShapeIndexCell::add_shapes(int n) {
int size = shapes_.size();
shapes_.resize(size + n);
return &shapes_[size];
}

View File

@ -1,152 +0,0 @@
// Copyright 2017 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Basic integer type definitions for various platforms
// This file is used for both C and C++!
//
// This code is compiled directly on many platforms, including client
// platforms like Windows, Mac, and embedded systems. Before making
// any changes here, make sure that you're not breaking any platforms.
//
// NOTE FOR GOOGLERS:
//
// IWYU pragma: private, include "base/integral_types.h"
#ifndef S2_THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_
#define S2_THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_
// These typedefs are also defined in base/swig/google.swig. In the
// SWIG environment, we use those definitions and avoid duplicate
// definitions here with an ifdef. The definitions should be the
// same in both files, and ideally be only defined in this file.
#ifndef SWIG
// Standard typedefs
// unsigned and therefore doesn't need a "uchar" type.
// Signed integer types with width of exactly 8, 16, 32, or 64 bits
// respectively, for use when exact sizes are required.
typedef signed char schar;
typedef signed char int8;
typedef short int16;
typedef int int32;
#ifdef _MSC_VER
typedef __int64 int64;
#else
typedef long long int64;
#endif /* _MSC_VER */
// NOTE: unsigned types are DANGEROUS in loops and other arithmetical
// places. Use the signed types unless your variable represents a bit
// pattern (eg a hash value) or you really need the extra bit. Do NOT
// use 'unsigned' to express "this value should always be positive";
// use assertions for this.
// Unsigned integer types with width of exactly 8, 16, 32, or 64 bits
// respectively, for use when exact sizes are required.
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
#ifdef _MSC_VER
typedef unsigned __int64 uint64;
#else
typedef unsigned long long uint64;
#endif /* _MSC_VER */
// A type to represent a Unicode code-point value. As of Unicode 4.0,
// such values require up to 21 bits.
// (For type-checking on pointers, make this explicitly signed,
// and it should always be the signed version of whatever int32 is.)
typedef signed int char32;
// A type to represent a natural machine word (for e.g. efficiently
// scanning through memory for checksums or index searching). Don't use
// this for storing normal integers. Ideally this would be just
// unsigned int, but our 64-bit architectures use the LP64 model
// (http://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models), hence
// their ints are only 32 bits. We want to use the same fundamental
// type on all archs if possible to preserve *printf() compatability.
typedef unsigned long uword_t;
#endif /* SWIG */
// long long macros to be used because gcc and vc++ use different suffixes,
// and different size specifiers in format strings
#undef GG_LONGLONG
#undef GG_ULONGLONG
#undef GG_LL_FORMAT
#ifdef _MSC_VER /* if Visual C++ */
// VC++ long long suffixes
#define GG_LONGLONG(x) x##I64
#define GG_ULONGLONG(x) x##UI64
// Length modifier in printf format string for int64's (e.g. within %d)
#define GG_LL_FORMAT "I64" // As in printf("%I64d", ...)
#define GG_LL_FORMAT_W L"I64"
#else /* not Visual C++ */
#define GG_LONGLONG(x) x##LL
#define GG_ULONGLONG(x) x##ULL
#define GG_LL_FORMAT "ll" // As in "%lld". Note that "q" is poor form also.
#define GG_LL_FORMAT_W L"ll"
#endif // _MSC_VER
// There are still some requirements that we build these headers in
// C-compatibility mode. Unfortunately, -Wall doesn't like c-style
// casts, and C doesn't know how to read braced-initialization for
// integers.
#if defined(__cplusplus)
const uint8 kuint8max{0xFF};
const uint16 kuint16max{0xFFFF};
const uint32 kuint32max{0xFFFFFFFF};
const uint64 kuint64max{GG_ULONGLONG(0xFFFFFFFFFFFFFFFF)};
const int8 kint8min{~0x7F};
const int8 kint8max{0x7F};
const int16 kint16min{~0x7FFF};
const int16 kint16max{0x7FFF};
const int32 kint32min{~0x7FFFFFFF};
const int32 kint32max{0x7FFFFFFF};
const int64 kint64min{GG_LONGLONG(~0x7FFFFFFFFFFFFFFF)};
const int64 kint64max{GG_LONGLONG(0x7FFFFFFFFFFFFFFF)};
#else // not __cplusplus, this branch exists only for C-compat
static const uint8 kuint8max = (( uint8) 0xFF);
static const uint16 kuint16max = ((uint16) 0xFFFF);
static const uint32 kuint32max = ((uint32) 0xFFFFFFFF);
static const uint64 kuint64max = ((uint64) GG_LONGLONG(0xFFFFFFFFFFFFFFFF));
static const int8 kint8min = (( int8) ~0x7F);
static const int8 kint8max = (( int8) 0x7F);
static const int16 kint16min = (( int16) ~0x7FFF);
static const int16 kint16max = (( int16) 0x7FFF);
static const int32 kint32min = (( int32) ~0x7FFFFFFF);
static const int32 kint32max = (( int32) 0x7FFFFFFF);
static const int64 kint64min = (( int64) GG_LONGLONG(~0x7FFFFFFFFFFFFFFF));
static const int64 kint64max = (( int64) GG_LONGLONG(0x7FFFFFFFFFFFFFFF));
#endif // __cplusplus
// The following are not real constants, but we list them so CodeSearch and
// other tools find them, in case people are looking for the above constants
// under different names:
// kMaxUint8, kMaxUint16, kMaxUint32, kMaxUint64
// kMinInt8, kMaxInt8, kMinInt16, kMaxInt16, kMinInt32, kMaxInt32,
// kMinInt64, kMaxInt64
// TODO(jyrki): remove this eventually.
// No object has kIllegalFprint as its Fingerprint.
typedef uint64 Fprint;
static const Fprint kIllegalFprint = 0;
static const Fprint kMaxFprint = GG_ULONGLONG(0xFFFFFFFFFFFFFFFF);
#endif // S2_THIRD_PARTY_ABSL_BASE_INTEGRAL_TYPES_H_

View File

@ -5,7 +5,7 @@ project(s2-geometry C CXX)
set(FAIL_ON_WARNINGS OFF CACHE BOOL "do not enable -Werror")
set(ARANGO_S2GEOMETRY_VERSION "552f562")
set(ARANGO_S2GEOMETRY_VERSION "dfefe0c")
set(ARANGO_S2GEOMETRY_VERSION "${ARANGO_S2GEOMETRY_VERSION}" PARENT_SCOPE)
set(CMAKE_USE_LIBSSH2 OFF CACHE type BOOL)

94
3rdParty/s2geometry/dfefe0c/.travis.yml vendored Normal file
View File

@ -0,0 +1,94 @@
dist: trusty
language: cpp
matrix:
include:
- os: linux
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-7
- libgflags-dev
- libgoogle-glog-dev
- libgtest-dev
- libssl-dev
- swig3.0
env:
- MATRIX_EVAL="CC=gcc-7 && CXX=g++-7"
- GTEST_ROOT=/usr/src/gtest
- os: linux
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-6
- libgflags-dev
- libgoogle-glog-dev
- libgtest-dev
- libssl-dev
- swig3.0
env:
- MATRIX_EVAL="CC=gcc-6 && CXX=g++-6"
- GTEST_ROOT=/usr/src/gtest
- os: linux
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-5
- libgflags-dev
- libgoogle-glog-dev
- libgtest-dev
- libssl-dev
- swig3.0
env:
- MATRIX_EVAL="CC=gcc-5 && CXX=g++-5"
- GTEST_ROOT=/usr/src/gtest
- os: linux
addons:
apt:
sources:
- llvm-toolchain-trusty-5.0
- ubuntu-toolchain-r-test
packages:
- clang-5.0
- g++-7
- libgflags-dev
- libgoogle-glog-dev
- libgtest-dev
- libssl-dev
- swig3.0
env:
- MATRIX_EVAL="CC=clang-5.0 && CXX=clang++-5.0"
- GTEST_ROOT=/usr/src/gtest
- os: osx
osx_image: xcode8.3
env:
- MATRIX_EVAL=""
- GTEST_ROOT="$( /bin/pwd )/googletest-release-1.8.0/googletest"
- OPENSSL_ROOT_DIR=/usr/local/opt/openssl
before_install:
- eval "${MATRIX_EVAL}"
- ${CC} --version
- ${CXX} --version
install:
- if [[ "$TRAVIS_OS_NAME" == osx ]]; then brew install gflags glog; fi
- if [[ "$TRAVIS_OS_NAME" == osx ]]; then wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz; fi
- if [[ "$TRAVIS_OS_NAME" == osx ]]; then tar zxvf release-1.8.0.tar.gz; fi
script:
- mkdir build
- cd build
- cmake -DGTEST_ROOT=$GTEST_ROOT -DWITH_GFLAGS=ON -DWITH_GLOG=ON ..
- make && make CTEST_OUTPUT_ON_FAILURE=1 test

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
project(s2-geometry C CXX)
cmake_minimum_required(VERSION 3.1)
project(s2-geometry)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(CMakeDependentOption)
include(CheckCXXCompilerFlag)
@ -58,7 +58,10 @@ if (APPLE)
set(CMAKE_MACOSX_RPATH TRUE)
endif()
add_definitions(-std=c++11)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# No compiler-specific extensions, i.e. -std=c++11, not -std=gnu++11.
set(CMAKE_CXX_EXTENSIONS OFF)
if (WIN32)
# Use unsigned characters
@ -68,10 +71,14 @@ if (WIN32)
# Make sure Windows doesn't define min/max macros that interfere with STL
add_definitions(-DNOMINMAX)
else()
# Avoid megabytes of warnings like:
# util/math/vector.h:178:16: warning: optimization attribute on
# double sqrt(double) follows definition but the attribute doesnt
# match [-Wattributes]
add_definitions(-Wno-attributes)
add_definitions(-Wno-deprecated-declarations)
endif()
# If OpenSSL is installed in a non-standard location, configure with
# something like:
# OPENSSL_ROOT_DIR=/usr/local/opt/openssl cmake ..
@ -83,7 +90,10 @@ include_directories(src)
add_library(s2
src/s2/base/stringprintf.cc
src/s2/base/strtoint.cc
src/s2/base/sysinfo.cc
src/s2/encoded_s2cell_id_vector.cc
src/s2/encoded_s2point_vector.cc
src/s2/encoded_s2shape_index.cc
src/s2/encoded_string_vector.cc
src/s2/id_set_lexicon.cc
src/s2/mutable_s2shape_index.cc
src/s2/r2rect.cc
@ -100,12 +110,13 @@ add_library(s2
src/s2/s2builderutil_s2polyline_layer.cc
src/s2/s2builderutil_s2polyline_vector_layer.cc
src/s2/s2builderutil_snap_functions.cc
src/s2/s2builderutil_testing.cc
src/s2/s2cap.cc
src/s2/s2cell.cc
src/s2/s2cell_id.cc
src/s2/s2cell_index.cc
src/s2/s2cell_union.cc
src/s2/s2centroids.cc
src/s2/s2closest_cell_query.cc
src/s2/s2closest_edge_query.cc
src/s2/s2closest_point_query.cc
src/s2/s2contains_vertex_query.cc
@ -128,6 +139,7 @@ add_library(s2
src/s2/s2lax_polygon_shape.cc
src/s2/s2lax_polyline_shape.cc
src/s2/s2loop.cc
src/s2/s2loop_measures.cc
src/s2/s2measures.cc
src/s2/s2metrics.cc
src/s2/s2max_distance_targets.cc
@ -139,6 +151,7 @@ add_library(s2
src/s2/s2polygon.cc
src/s2/s2polyline.cc
src/s2/s2polyline_alignment.cc
src/s2/s2polyline_measures.cc
src/s2/s2polyline_simplifier.cc
src/s2/s2predicates.cc
src/s2/s2projections.cc
@ -150,7 +163,10 @@ add_library(s2
src/s2/s2region_union.cc
src/s2/s2shape_index.cc
src/s2/s2shape_index_buffered_region.cc
src/s2/s2shape_index_measures.cc
src/s2/s2shape_measures.cc
src/s2/s2shapeutil_build_polygon_boundaries.cc
src/s2/s2shapeutil_coding.cc
src/s2/s2shapeutil_contains_brute_force.cc
src/s2/s2shapeutil_edge_iterator.cc
src/s2/s2shapeutil_get_reference_point.cc
@ -179,7 +195,10 @@ add_library(s2
src/s2/util/math/exactfloat/exactfloat.cc
src/s2/util/math/mathutil.cc
src/s2/util/units/length-units.cc)
add_library(s2testing STATIC src/s2/s2testing.cc)
#add_library(s2testing STATIC
# src/s2/s2builderutil_testing.cc
# src/s2/s2shapeutil_testing.cc
# src/s2/s2testing.cc)
target_link_libraries(
s2
${GFLAGS_LIBRARIES} ${GLOG_LIBRARIES} ${OPENSSL_LIBRARIES}
@ -195,6 +214,11 @@ if (INSTALL_HEADERS)
# We don't need to install all headers, only those
# transitively included by s2 headers we are exporting.
install(FILES src/s2/_fp_contract_off.h
src/s2/encoded_s2cell_id_vector.h
src/s2/encoded_s2point_vector.h
src/s2/encoded_s2shape_index.h
src/s2/encoded_string_vector.h
src/s2/encoded_uint_vector.h
src/s2/id_set_lexicon.h
src/s2/mutable_s2shape_index.h
src/s2/r1interval.h
@ -218,8 +242,11 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2cap.h
src/s2/s2cell.h
src/s2/s2cell_id.h
src/s2/s2cell_index.h
src/s2/s2cell_union.h
src/s2/s2centroids.h
src/s2/s2closest_cell_query.h
src/s2/s2closest_cell_query_base.h
src/s2/s2closest_edge_query.h
src/s2/s2closest_edge_query_base.h
src/s2/s2closest_point_query.h
@ -235,7 +262,6 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2earth.h
src/s2/s2edge_clipping.h
src/s2/s2edge_crosser.h
src/s2/s2edge_crossings_internal.h
src/s2/s2edge_crossings.h
src/s2/s2edge_distances.h
src/s2/s2edge_tessellator.h
@ -249,6 +275,7 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2lax_polygon_shape.h
src/s2/s2lax_polyline_shape.h
src/s2/s2loop.h
src/s2/s2loop_measures.h
src/s2/s2measures.h
src/s2/s2metrics.h
src/s2/s2max_distance_targets.h
@ -259,12 +286,13 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2point_compression.h
src/s2/s2point_index.h
src/s2/s2point_region.h
src/s2/s2point_span.h
src/s2/s2pointutil.h
src/s2/s2polygon.h
src/s2/s2polyline.h
src/s2/s2polyline_alignment.h
src/s2/s2polyline_measures.h
src/s2/s2polyline_simplifier.h
src/s2/s2predicates_internal.h
src/s2/s2predicates.h
src/s2/s2projections.h
src/s2/s2r2rect.h
@ -277,7 +305,9 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2shape_index.h
src/s2/s2shape_index_buffered_region.h
src/s2/s2shape_index_region.h
src/s2/s2shape_measures.h
src/s2/s2shapeutil_build_polygon_boundaries.h
src/s2/s2shapeutil_coding.h
src/s2/s2shapeutil_contains_brute_force.h
src/s2/s2shapeutil_count_edges.h
src/s2/s2shapeutil_edge_iterator.h
@ -285,6 +315,7 @@ install(FILES src/s2/_fp_contract_off.h
src/s2/s2shapeutil_range_iterator.h
src/s2/s2shapeutil_shape_edge.h
src/s2/s2shapeutil_shape_edge_id.h
src/s2/s2shapeutil_testing.h
src/s2/s2shapeutil_visit_crossing_edge_pairs.h
src/s2/s2testing.h
src/s2/s2text_format.h
@ -294,6 +325,7 @@ install(FILES src/s2/_fp_contract_off.h
DESTINATION include/s2)
install(FILES src/s2/base/casts.h
src/s2/base/commandlineflags.h
src/s2/base/integral_types.h
src/s2/base/log_severity.h
src/s2/base/logging.h
src/s2/base/mutex.h
@ -308,7 +340,6 @@ install(FILES src/s2/third_party/absl/base/attributes.h
src/s2/third_party/absl/base/casts.h
src/s2/third_party/absl/base/config.h
src/s2/third_party/absl/base/dynamic_annotations.h
src/s2/third_party/absl/base/integral_types.h
src/s2/third_party/absl/base/log_severity.h
src/s2/third_party/absl/base/macros.h
src/s2/third_party/absl/base/optimization.h
@ -324,6 +355,10 @@ install(FILES src/s2/third_party/absl/base/internal/identity.h
DESTINATION include/s2/third_party/absl/base/internal)
install(FILES src/s2/third_party/absl/container/inlined_vector.h
DESTINATION include/s2/third_party/absl/container)
install(FILES src/s2/third_party/absl/container/internal/compressed_tuple.h
src/s2/third_party/absl/container/internal/container_memory.h
src/s2/third_party/absl/container/internal/layout.h
DESTINATION include/s2/third_party/absl/container/internal)
install(FILES src/s2/third_party/absl/memory/memory.h
DESTINATION include/s2/third_party/absl/memory)
install(FILES src/s2/third_party/absl/meta/type_traits.h
@ -332,7 +367,9 @@ install(FILES src/s2/third_party/absl/numeric/int128.h
src/s2/third_party/absl/numeric/int128_have_intrinsic.inc
src/s2/third_party/absl/numeric/int128_no_intrinsic.inc
DESTINATION include/s2/third_party/absl/numeric)
install(FILES src/s2/third_party/absl/strings/string_view.h
install(FILES src/s2/third_party/absl/strings/numbers.h
src/s2/third_party/absl/strings/str_cat.h
src/s2/third_party/absl/strings/string_view.h
DESTINATION include/s2/third_party/absl/strings)
install(FILES src/s2/third_party/absl/types/span.h
DESTINATION include/s2/third_party/absl/types)
@ -354,10 +391,9 @@ install(FILES src/s2/util/gtl/btree.h
src/s2/util/gtl/dense_hash_set.h
src/s2/util/gtl/densehashtable.h
src/s2/util/gtl/hashtable_common.h
src/s2/util/gtl/layout.h
src/s2/util/gtl/libc_allocator_with_realloc.h
DESTINATION include/s2/util/gtl)
install(FILES src/s2/util/gtl/subtle/compressed_tuple.h
DESTINATION include/s2/util/gtl/subtle)
install(FILES src/s2/util/hash/mix.h
DESTINATION include/s2/util/hash)
install(FILES src/s2/util/math/mathutil.h
@ -368,13 +404,20 @@ install(FILES src/s2/util/math/mathutil.h
install(FILES src/s2/util/units/length-units.h
src/s2/util/units/physical-units.h
DESTINATION include/s2/util/units)
install(TARGETS s2 DESTINATION lib)
install(TARGETS s2 s2testing DESTINATION lib)
endif()
message("GTEST_ROOT: ${GTEST_ROOT}")
if (GTEST_ROOT)
add_subdirectory(${GTEST_ROOT} build_gtest)
include_directories(${GTEST_ROOT}/include)
set(S2TestFiles
src/s2/encoded_s2cell_id_vector_test.cc
src/s2/encoded_s2point_vector_test.cc
src/s2/encoded_s2shape_index_test.cc
src/s2/encoded_string_vector_test.cc
src/s2/encoded_uint_vector_test.cc
src/s2/id_set_lexicon_test.cc
src/s2/mutable_s2shape_index_test.cc
src/s2/r1interval_test.cc
@ -396,8 +439,11 @@ if (GTEST_ROOT)
src/s2/s2cap_test.cc
src/s2/s2cell_test.cc
src/s2/s2cell_id_test.cc
src/s2/s2cell_index_test.cc
src/s2/s2cell_union_test.cc
src/s2/s2centroids_test.cc
src/s2/s2closest_cell_query_base_test.cc
src/s2/s2closest_cell_query_test.cc
src/s2/s2closest_edge_query_base_test.cc
src/s2/s2closest_edge_query_test.cc
src/s2/s2closest_point_query_base_test.cc
@ -422,6 +468,7 @@ if (GTEST_ROOT)
src/s2/s2lax_loop_shape_test.cc
src/s2/s2lax_polygon_shape_test.cc
src/s2/s2lax_polyline_shape_test.cc
src/s2/s2loop_measures_test.cc
src/s2/s2loop_test.cc
src/s2/s2measures_test.cc
src/s2/s2metrics_test.cc
@ -437,6 +484,7 @@ if (GTEST_ROOT)
src/s2/s2polygon_test.cc
src/s2/s2polyline_alignment_test.cc
src/s2/s2polyline_simplifier_test.cc
src/s2/s2polyline_measures_test.cc
src/s2/s2polyline_test.cc
src/s2/s2predicates_test.cc
src/s2/s2projections_test.cc
@ -446,9 +494,12 @@ if (GTEST_ROOT)
src/s2/s2region_coverer_test.cc
src/s2/s2region_union_test.cc
src/s2/s2shape_index_buffered_region_test.cc
src/s2/s2shape_index_measures_test.cc
src/s2/s2shape_index_region_test.cc
src/s2/s2shape_index_test.cc
src/s2/s2shape_measures_test.cc
src/s2/s2shapeutil_build_polygon_boundaries_test.cc
src/s2/s2shapeutil_coding_test.cc
src/s2/s2shapeutil_contains_brute_force_test.cc
src/s2/s2shapeutil_count_edges_test.cc
src/s2/s2shapeutil_edge_iterator_test.cc

View File

@ -26,3 +26,4 @@ Dan Larkin-York <dan@arangodb.com>
Eric Veach <ericv@google.com>
Jesse Rosenstock <jmr@google.com>
Julien Basch <julienbasch@google.com>
Phil Elson <pelson.pub@gmail.com>

View File

@ -1,5 +1,7 @@
# S2 Geometry Library
[![Build Status](https://travis-ci.org/google/s2geometry.svg?branch=master)](https://travis-ci.org/google/s2geometry)
## Overview
This is a package for manipulating geometric shapes. Unlike many geometry
@ -16,7 +18,6 @@ S2 documentation can be found on [s2geometry.io](http://s2geometry.io).
## Requirements for End Users
* A POSIX system (for getrusage) or Windows.
* [CMake](http://www.cmake.org/)
* A C++ compiler with C++11 support, such as [g++](https://gcc.gnu.org/)
\>= 4.7.
@ -92,6 +93,8 @@ sudo make install
Enable gflags and glog with `cmake -DWITH_GFLAGS=ON -DWITH_GLOG=ON ...`.
Disable building of shared libraries with `-DBUILD_SHARED_LIBS=OFF`.
## Python
If you want the Python interface, you will also need:
@ -111,6 +114,12 @@ sudo port install swig
```
Expect to see some warnings if you build with swig 2.0.
## Other S2 implementations
* [Go](https://github.com/golang/geo) (Approximately 40% complete.)
* [Java](https://github.com/google/s2-geometry-library-java) (Older version;
last updated in 2011.)
## Disclaimer
This is not an official Google product.

View File

@ -0,0 +1,32 @@
include(${SWIG_USE_FILE})
include_directories(${PYTHON_INCLUDE_PATH})
set(CMAKE_SWIG_FLAGS "")
set_property(SOURCE s2.i PROPERTY SWIG_FLAGS "-module" "pywraps2")
set_property(SOURCE s2.i PROPERTY CPLUSPLUS ON)
# Starting in 3.8, swig_add_module is deprecated in favor of swig_add_library.
if (${CMAKE_VERSION} VERSION_LESS "3.8.0")
swig_add_module(pywraps2 python s2.i)
else()
swig_add_library(pywraps2 LANGUAGE python SOURCES s2.i)
endif()
swig_link_libraries(pywraps2 ${PYTHON_LIBRARIES} s2)
enable_testing()
add_test(NAME pywraps2_test COMMAND
${PYTHON_EXECUTABLE}
"${PROJECT_SOURCE_DIR}/src/python/pywraps2_test.py")
set_property(TEST pywraps2_test PROPERTY ENVIRONMENT
"PYTHONPATH=$ENV{PYTHONPATH}:${PROJECT_BINARY_DIR}/python")
execute_process(COMMAND "${PYTHON_EXECUTABLE}" -c "if True:
from distutils import sysconfig as sc;
print(sc.get_python_lib(prefix='', plat_specific=True))"
OUTPUT_VARIABLE PYTHON_SITE
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Install the wrapper.
install(TARGETS _pywraps2 DESTINATION ${PYTHON_SITE})
install(FILES "${PROJECT_BINARY_DIR}/python/pywraps2.py"
DESTINATION ${PYTHON_SITE})

View File

@ -414,5 +414,54 @@ class PyWrapS2TestCase(unittest.TestCase):
self.assertFalse(
rect_bound.Contains(s2.S2LatLng.FromDegrees(2.0, 2.0).ToPoint()))
def testS2CellIdCenterSiTi(self):
cell = s2.S2CellId.FromFacePosLevel(3, 0x12345678, s2.S2CellId.kMaxLevel)
# Check that the (si, ti) coordinates of the center end in a
# 1 followed by (30 - level) 0s.
# Leaf level, 30.
face, si, ti = cell.GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 0, si & 1)
self.assertEqual(1 << 0, ti & 1)
# Level 29.
face, si, ti = cell.parent(s2.S2CellId.kMaxLevel - 1).GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 1, si & 3)
self.assertEqual(1 << 1, ti & 3)
# Level 28.
face, si, ti = cell.parent(s2.S2CellId.kMaxLevel - 2).GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 2, si & 7)
self.assertEqual(1 << 2, ti & 7)
# Level 20.
face, si, ti = cell.parent(s2.S2CellId.kMaxLevel - 10).GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 10, si & ((1 << 11) - 1))
self.assertEqual(1 << 10, ti & ((1 << 11) - 1))
# Level 10.
face, si, ti = cell.parent(s2.S2CellId.kMaxLevel - 20).GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 20, si & ((1 << 21) - 1))
self.assertEqual(1 << 20, ti & ((1 << 21) - 1))
# Level 0.
face, si, ti = cell.parent(0).GetCenterSiTi()
self.assertEqual(3, face)
self.assertEqual(1 << 30, si & ((1 << 31) - 1))
self.assertEqual(1 << 30, ti & ((1 << 31) - 1))
def testS2CellIdToFromFaceIJ(self):
cell = s2.S2CellId.FromFaceIJ(3, 1234, 5678)
face, i, j, _ = cell.ToFaceIJOrientation()
self.assertEqual(3, face)
self.assertEqual(1234, i)
self.assertEqual(5678, j)
if __name__ == "__main__":
unittest.main()

View File

@ -66,6 +66,11 @@
%rename(InitFromS2Points) S2Polyline::Init(std::vector<S2Point> const& vertices);
%apply int *OUTPUT {int *next_vertex};
%apply int *OUTPUT {int *psi};
%apply int *OUTPUT {int *pti};
%apply int *OUTPUT {int *pi};
%apply int *OUTPUT {int *pj};
%apply int *OUTPUT {int *orientation};
%apply SWIGTYPE *DISOWN {S2Loop *loop_disown};
%typemap(in) std::vector<S2Loop *> * (std::vector<S2Loop *> loops){
@ -291,10 +296,14 @@ class S2Point {
%unignore S2CellId::~S2CellId;
%unignore S2CellId::Begin;
%unignore S2CellId::End;
%unignore S2CellId::FromFaceIJ(int, int, int);
%unignore S2CellId::FromFacePosLevel(int, uint64, int);
%unignore S2CellId::FromLatLng;
%unignore S2CellId::FromPoint;
%unignore S2CellId::FromToken;
%unignore S2CellId::GetCenterSiTi(int*, int*) const;
%unignore S2CellId::GetEdgeNeighbors;
%unignore S2CellId::ToFaceIJOrientation(int*, int*, int*) const;
%unignore S2CellId::ToLatLng;
%unignore S2CellId::ToPoint;
%unignore S2CellId::ToString;

View File

@ -24,7 +24,7 @@
#include <string>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#define DEFINE_bool(name, default_value, description) \
bool FLAGS_##name = default_value

View File

@ -0,0 +1,31 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#ifndef S2_BASE_INTEGRAL_TYPES_H_
#define S2_BASE_INTEGRAL_TYPES_H_
using int8 = signed char;
using int16 = short;
using int32 = int;
using int64 = long long;
using uint8 = unsigned char;
using uint16 = unsigned short;
using uint32 = unsigned int;
using uint64 = unsigned long long;
using uword_t = unsigned long;
#endif // S2_BASE_INTEGRAL_TYPES_H_

View File

@ -37,8 +37,8 @@
#include <cstdlib>
#include <cstring>
#include "s2/base/integral_types.h"
#include "s2/third_party/absl/base/config.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/third_party/absl/base/port.h" // IWYU pragma: export
#ifdef SWIG
@ -149,7 +149,9 @@
// OS_IOS
#if defined(__APPLE__)
// Currently, blaze supports iOS yet doesn't define a flag. Mac users have
// traditionally defined OS_IOS themselves via other build systems, since mac
// hasn't been supported by blaze.
// TODO(user): Remove this when all toolchains make the proper defines.
#include <TargetConditionals.h>
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
@ -276,6 +278,7 @@ inline void sized_delete_array(void *ptr, size_t size) {
// IS_LITTLE_ENDIAN, IS_BIG_ENDIAN
#if defined __linux__ || defined OS_ANDROID || defined(__ANDROID__)
// TODO(user): http://b/21460321; use one of OS_ANDROID or __ANDROID__.
// _BIG_ENDIAN
#include <endian.h>
@ -335,7 +338,7 @@ inline void sized_delete_array(void *ptr, size_t size) {
#define bswap_32(x) OSSwapInt32(x)
#define bswap_64(x) OSSwapInt64(x)
#elif defined(__GLIBC__) || defined(__BIONIC__) || defined(__GENCLAVE__)
#elif defined(__GLIBC__) || defined(__BIONIC__) || defined(__ASYLO__)
#include <byteswap.h> // IWYU pragma: export
#else
@ -356,14 +359,14 @@ static inline uint32 bswap_32(uint32 x) {
}
#define bswap_32(x) bswap_32(x)
static inline uint64 bswap_64(uint64 x) {
return (((x & GG_ULONGLONG(0xFF)) << 56) |
((x & GG_ULONGLONG(0xFF00)) << 40) |
((x & GG_ULONGLONG(0xFF0000)) << 24) |
((x & GG_ULONGLONG(0xFF000000)) << 8) |
((x & GG_ULONGLONG(0xFF00000000)) >> 8) |
((x & GG_ULONGLONG(0xFF0000000000)) >> 24) |
((x & GG_ULONGLONG(0xFF000000000000)) >> 40) |
((x & GG_ULONGLONG(0xFF00000000000000)) >> 56));
return (((x & 0xFFULL) << 56) |
((x & 0xFF00ULL) << 40) |
((x & 0xFF0000ULL) << 24) |
((x & 0xFF000000ULL) << 8) |
((x & 0xFF00000000ULL) >> 8) |
((x & 0xFF0000000000ULL) >> 24) |
((x & 0xFF000000000000ULL) >> 40) |
((x & 0xFF00000000000000ULL) >> 56));
}
#define bswap_64(x) bswap_64(x)
@ -859,22 +862,22 @@ inline void UnalignedCopy64(const void *src, void *dst) {
#endif // defined(__cplusplus), end of unaligned API
// aligned_malloc, aligned_free
#if defined(__ANDROID__) || defined(__GENCLAVE__)
#if defined(__ANDROID__) || defined(__ASYLO__)
#include <malloc.h> // for memalign()
#endif
// __GENCLAVE__ platform uses newlib without an underlying OS, which provides
// __ASYLO__ platform uses newlib without an underlying OS, which provides
// memalign, but not posix_memalign.
#if defined(__cplusplus) && \
(((defined(__GNUC__) || defined(__APPLE__) || \
defined(__NVCC__)) && \
!defined(SWIG)) || \
((__GNUC__ >= 3 || defined(__clang__)) && defined(__ANDROID__)) || \
defined(__GENCLAVE__))
defined(__ASYLO__))
inline void *aligned_malloc(size_t size, int minimum_alignment) {
#if defined(__ANDROID__) || defined(OS_ANDROID) || defined(__GENCLAVE__)
#if defined(__ANDROID__) || defined(OS_ANDROID) || defined(__ASYLO__)
return memalign(minimum_alignment, size);
#else // !__ANDROID__ && !OS_ANDROID && !__GENCLAVE__
#else // !__ANDROID__ && !OS_ANDROID && !__ASYLO__
void *ptr = nullptr;
// posix_memalign requires that the requested alignment be at least
// sizeof(void*). In this case, fall back on malloc which should return memory

View File

@ -20,6 +20,9 @@
#include <cerrno>
#include <climits>
#include <limits>
#include "s2/base/integral_types.h"
#include "s2/base/port.h"
#include "s2/base/strtoint.h"
@ -31,15 +34,15 @@ int32 strto32_adapter(const char *nptr, char **endptr, int base) {
errno = 0;
const long result = strtol(nptr, endptr, base);
if (errno == ERANGE && result == LONG_MIN) {
return kint32min;
return std::numeric_limits<int32>::min();
} else if (errno == ERANGE && result == LONG_MAX) {
return kint32max;
} else if (errno == 0 && result < kint32min) {
return std::numeric_limits<int32>::max();
} else if (errno == 0 && result < std::numeric_limits<int32>::min()) {
errno = ERANGE;
return kint32min;
} else if (errno == 0 && result > kint32max) {
return std::numeric_limits<int32>::min();
} else if (errno == 0 && result > std::numeric_limits<int32>::max()) {
errno = ERANGE;
return kint32max;
return std::numeric_limits<int32>::max();
}
if (errno == 0)
errno = saved_errno;
@ -51,10 +54,10 @@ uint32 strtou32_adapter(const char *nptr, char **endptr, int base) {
errno = 0;
const unsigned long result = strtoul(nptr, endptr, base);
if (errno == ERANGE && result == ULONG_MAX) {
return kuint32max;
} else if (errno == 0 && result > kuint32max) {
return std::numeric_limits<uint32>::max();
} else if (errno == 0 && result > std::numeric_limits<uint32>::max()) {
errno = ERANGE;
return kuint32max;
return std::numeric_limits<uint32>::max();
}
if (errno == 0)
errno = saved_errno;

View File

@ -46,9 +46,9 @@
#include <cstdlib> // For strtol* functions.
#include <string>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/third_party/absl/base/macros.h"
#include "s2/base/integral_types.h"
#include "s2/base/port.h"
#include "s2/third_party/absl/base/macros.h"
// Adapter functions for handling overflow and errno.
int32 strto32_adapter(const char *nptr, char **endptr, int base);

View File

@ -18,7 +18,7 @@
#include <chrono>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
class CycleTimer {
public:

View File

@ -0,0 +1,153 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2cell_id_vector.h"
using absl::Span;
using std::max;
using std::min;
using std::vector;
namespace s2coding {
void EncodeS2CellIdVector(Span<const S2CellId> v, Encoder* encoder) {
// v[i] is encoded as (base + (deltas[i] << shift)).
//
// "base" consists of 0-7 bytes, and is always shifted so that its bytes are
// the most-significant bytes of a uint64.
//
// "deltas" is an EncodedUintVector<uint64>, which means that all deltas
// have a fixed-length encoding determined by the largest delta.
//
// "shift" is in the range 0..56. The shift value is odd only if all
// S2CellIds are at the same level, in which case the bit at position
// (shift - 1) is automatically set to 1 in "base".
//
// "base" (3 bits) and "shift" (6 bits) are encoded in either one or two
// bytes as follows:
// - if (shift <= 4 or shift is even), then 1 byte
// - otherwise 2 bytes
// Note that (shift == 1) means that all S2CellIds are leaf cells, and
// (shift == 2) means that all S2CellIds are at level 29.
uint64 v_or = 0, v_and = ~0ULL, v_min = ~0ULL, v_max = 0;
for (auto cellid : v) {
v_or |= cellid.id();
v_and &= cellid.id();
v_min = min(v_min, cellid.id());
v_max = max(v_max, cellid.id());
}
// These variables represent the values that will used during encoding.
uint64 e_base = 0; // Base value.
int e_base_len = 0; // Number of bytes to represent "base".
int e_shift = 0; // Delta shift.
int e_max_delta_msb = 0; // Bit position of the MSB of the largest delta.
if (v_or > 0) {
// We only allow even shifts, unless all values have the same low bit (in
// which case the shift is odd and the preceding bit is implicitly on).
// There is no point in allowing shifts > 56 since deltas are encoded in
// at least 1 byte each.
e_shift = min(56, Bits::FindLSBSetNonZero64(v_or) & ~1);
if (v_and & (1ULL << e_shift)) ++e_shift; // All S2CellIds same level.
// "base" consists of the "base_len" most significant bytes of the minimum
// S2CellId. We consider all possible values of "base_len" (0..7) and
// choose the one that minimizes the total encoding size.
uint64 e_bytes = ~0ULL; // Best encoding size so far.
for (int len = 0; len < 8; ++len) {
// "t_base" is the base value being tested (first "len" bytes of v_min).
// "t_max_delta_msb" is the most-significant bit position of the largest
// delta (or zero if there are no deltas, i.e. if v.size() == 0).
// "t_bytes" is the total size of the variable portion of the encoding.
uint64 t_base = v_min & ~(~0ULL >> (8 * len));
int t_max_delta_msb =
max(0, Bits::Log2Floor64((v_max - t_base) >> e_shift));
uint64 t_bytes = len + v.size() * ((t_max_delta_msb >> 3) + 1);
if (t_bytes < e_bytes) {
e_base = t_base;
e_base_len = len;
e_max_delta_msb = t_max_delta_msb;
e_bytes = t_bytes;
}
}
// It takes one extra byte to encode odd shifts (i.e., the case where all
// S2CellIds are at the same level), so check whether we can get the same
// encoding size per delta using an even shift.
if ((e_shift & 1) && (e_max_delta_msb & 7) != 7) --e_shift;
}
S2_DCHECK_LE(e_base_len, 7);
S2_DCHECK_LE(e_shift, 56);
encoder->Ensure(2 + e_base_len);
// As described above, "shift" and "base_len" are encoded in 1 or 2 bytes.
// "shift_code" is 5 bits:
// values <= 28 represent even shifts in the range 0..56
// values 29, 30 represent odd shifts 1 and 3
// value 31 indicates that the shift is odd and encoded in the next byte
int shift_code = e_shift >> 1;
if (e_shift & 1) shift_code = min(31, shift_code + 29);
encoder->put8((shift_code << 3) | e_base_len);
if (shift_code == 31) {
encoder->put8(e_shift >> 1); // Shift is always odd, so 3 bits unused.
}
// Encode the "base_len" most-significant bytes of "base".
uint64 base_bytes = e_base >> (64 - 8 * max(1, e_base_len));
EncodeUintWithLength<uint64>(base_bytes, e_base_len, encoder);
// Finally, encode the vector of deltas.
vector<uint64> deltas;
deltas.reserve(v.size());
for (auto cellid : v) {
deltas.push_back((cellid.id() - e_base) >> e_shift);
}
EncodeUintVector<uint64>(deltas, encoder);
}
bool EncodedS2CellIdVector::Init(Decoder* decoder) {
// All encodings have at least 2 bytes (one for our header and one for the
// EncodedUintVector header), so this is safe.
if (decoder->avail() < 2) return false;
// Invert the encoding of (shift_code, base_len) described above.
int code_plus_len = decoder->get8();
int shift_code = code_plus_len >> 3;
if (shift_code == 31) {
shift_code = 29 + decoder->get8();
}
// Decode the "base_len" most-significant bytes of "base".
int base_len = code_plus_len & 7;
if (!DecodeUintWithLength(base_len, decoder, &base_)) return false;
base_ <<= 64 - 8 * max(1, base_len);
// Invert the encoding of "shift_code" described above.
if (shift_code >= 29) {
shift_ = 2 * (shift_code - 29) + 1;
base_ |= 1ULL << (shift_ - 1);
} else {
shift_ = 2 * shift_code;
}
return deltas_.Init(decoder);
}
vector<S2CellId> EncodedS2CellIdVector::Decode() const {
vector<S2CellId> result(size());
for (int i = 0; i < size(); ++i) {
result[i] = (*this)[i];
}
return result;
}
} // namespace s2coding

View File

@ -0,0 +1,110 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_ENCODED_S2CELL_ID_VECTOR_H_
#define S2_ENCODED_S2CELL_ID_VECTOR_H_
#include "s2/third_party/absl/types/span.h"
#include "s2/encoded_uint_vector.h"
#include "s2/s2cell_id.h"
namespace s2coding {
// Encodes a vector of S2CellIds in a format that can later be decoded as an
// EncodedS2CellIdVector. The S2CellIds do not need to be valid.
//
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
void EncodeS2CellIdVector(absl::Span<const S2CellId> v, Encoder* encoder);
// This class represents an encoded vector of S2CellIds. Values are decoded
// only when they are accessed. This allows for very fast initialization and
// no additional memory use beyond the encoded data. The encoded data is not
// owned by this class; typically it points into a large contiguous buffer
// that contains other encoded data as well.
//
// This is one of several helper classes that allow complex data structures to
// be initialized from an encoded format in constant time and then decoded on
// demand. This can be a big performance advantage when only a small part of
// the data structure is actually used.
//
// The S2CellIds do not need to be sorted or at the same level. The
// implementation is biased towards minimizing decoding times rather than
// space usage.
//
// NOTE: If your S2CellIds represent S2Points that have been snapped to
// S2CellId centers, then EncodedS2PointVector is both faster and smaller.
class EncodedS2CellIdVector {
public:
// Constructs an uninitialized object; requires Init() to be called.
EncodedS2CellIdVector() {}
// Initializes the EncodedS2CellIdVector.
//
// REQUIRES: The Decoder data buffer must outlive this object.
bool Init(Decoder* decoder);
// Returns the size of the original vector.
size_t size() const;
// Returns the element at the given index.
S2CellId operator[](int i) const;
// Returns the index of the first element x such that (x >= target), or
// size() if no such element exists.
//
// REQUIRES: The vector elements are sorted in non-decreasing order.
size_t lower_bound(S2CellId target) const;
// Decodes and returns the entire original vector.
std::vector<S2CellId> Decode() const;
private:
// Values are decoded as (base_ + (deltas_[i] << shift_)).
EncodedUintVector<uint64> deltas_;
uint64 base_;
uint8 shift_;
};
////////////////// Implementation details follow ////////////////////
inline size_t EncodedS2CellIdVector::size() const {
return deltas_.size();
}
inline S2CellId EncodedS2CellIdVector::operator[](int i) const {
return S2CellId((deltas_[i] << shift_) + base_);
}
inline size_t EncodedS2CellIdVector::lower_bound(S2CellId target) const {
// We optimize the search by converting "target" to the corresponding delta
// value and then searching directly in the deltas_ vector.
//
// To invert operator[], we essentially compute ((target - base_) >> shift_)
// except that we also need to round up when shifting. The first two cases
// ensure that "target" doesn't wrap around past zero when we do this.
if (target.id() <= base_) return 0;
if (target >= S2CellId::End(S2CellId::kMaxLevel)) return size();
return deltas_.lower_bound(
(target.id() - base_ + (1ULL << shift_) - 1) >> shift_);
}
} // namespace s2coding
#endif // S2_ENCODED_S2CELL_ID_VECTOR_H_

View File

@ -0,0 +1,232 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2cell_id_vector.h"
#include <vector>
#include <gtest/gtest.h>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/s2loop.h"
#include "s2/s2pointutil.h"
#include "s2/s2shape_index.h"
#include "s2/s2testing.h"
#include "s2/s2text_format.h"
using absl::make_unique;
using s2textformat::MakeCellIdOrDie;
using std::vector;
namespace s2coding {
// Encodes the given vector and returns the corresponding
// EncodedS2CellIdVector (which points into the Encoder's data buffer).
EncodedS2CellIdVector MakeEncodedS2CellIdVector(const vector<S2CellId>& input,
Encoder* encoder) {
EncodeS2CellIdVector(input, encoder);
Decoder decoder(encoder->base(), encoder->length());
EncodedS2CellIdVector cell_ids;
EXPECT_TRUE(cell_ids.Init(&decoder));
return cell_ids;
}
// Encodes the given vector and checks that it has the expected size and
// contents.
void TestEncodedS2CellIdVector(const vector<S2CellId>& expected,
size_t expected_bytes) {
Encoder encoder;
EncodedS2CellIdVector actual = MakeEncodedS2CellIdVector(expected, &encoder);
EXPECT_EQ(expected_bytes, encoder.length());
EXPECT_EQ(actual.Decode(), expected);
}
// Like the above, but accepts a vector<uint64> rather than a vector<S2CellId>.
void TestEncodedS2CellIdVector(const vector<uint64>& raw_expected,
size_t expected_bytes) {
vector<S2CellId> expected;
for (uint64 raw_id : raw_expected) {
expected.push_back(S2CellId(raw_id));
}
TestEncodedS2CellIdVector(expected, expected_bytes);
}
TEST(EncodedS2CellIdVector, Empty) {
TestEncodedS2CellIdVector(vector<S2CellId>{}, 2);
}
TEST(EncodedS2CellIdVector, None) {
TestEncodedS2CellIdVector({S2CellId::None()}, 3);
}
TEST(EncodedS2CellIdVector, NoneNone) {
TestEncodedS2CellIdVector({S2CellId::None(), S2CellId::None()}, 4);
}
TEST(EncodedS2CellIdVector, Sentinel) {
TestEncodedS2CellIdVector({S2CellId::Sentinel()}, 10);
}
TEST(EncodedS2CellIdVector, MaximumShiftCell) {
// Tests the encoding of a single cell at level 2, which corresponds the
// maximum encodable shift value (56).
TestEncodedS2CellIdVector({MakeCellIdOrDie("0/00")}, 3);
}
TEST(EncodedS2CellIdVector, SentinelSentinel) {
TestEncodedS2CellIdVector({S2CellId::Sentinel(), S2CellId::Sentinel()}, 11);
}
TEST(EncodedS2CellIdVector, NoneSentinelNone) {
TestEncodedS2CellIdVector(
{S2CellId::None(), S2CellId::Sentinel(), S2CellId::None()}, 26);
}
TEST(EncodedS2CellIdVector, InvalidCells) {
// Tests that cells with an invalid LSB can be encoded.
TestEncodedS2CellIdVector({0x6, 0xe, 0x7e}, 5);
}
TEST(EncodedS2CellIdVector, OneByteLeafCells) {
// Tests that (1) if all cells are leaf cells, the low bit is not encoded,
// and (2) this can be indicated using the standard 1-byte header.
TestEncodedS2CellIdVector({0x3, 0x7, 0x177}, 5);
}
TEST(EncodedS2CellIdVector, OneByteLevel29Cells) {
// Tests that (1) if all cells are at level 29, the low bit is not encoded,
// and (2) this can be indicated using the standard 1-byte header.
TestEncodedS2CellIdVector({0xc, 0x1c, 0x47c}, 5);
}
TEST(EncodedS2CellIdVector, OneByteLevel28Cells) {
// Tests that (1) if all cells are at level 28, the low bit is not encoded,
// and (2) this can be indicated using the extended 2-byte header.
TestEncodedS2CellIdVector({0x30, 0x70, 0x1770}, 6);
}
TEST(EncodedS2CellIdVector, OneByteMixedCellLevels) {
// Tests that cells at mixed levels can be encoded in one byte.
TestEncodedS2CellIdVector({0x300, 0x1c00, 0x7000, 0xff00}, 6);
}
TEST(EncodedS2CellIdVector, OneByteMixedCellLevelsWithPrefix) {
// Tests that cells at mixed levels can be encoded in one byte even when
// they share a multi-byte prefix.
TestEncodedS2CellIdVector({
0x1234567800000300, 0x1234567800001c00,
0x1234567800007000, 0x123456780000ff00}, 10);
}
TEST(EncodedS2CellIdVector, OneByteRangeWithBaseValue) {
// Tests that cells can be encoded in one byte by choosing a base value
// whose bit range overlaps the delta values.
// 1 byte header, 3 bytes base, 1 byte size, 4 bytes deltas
TestEncodedS2CellIdVector({
0x00ffff0000000000, 0x0100fc0000000000,
0x0100500000000000, 0x0100330000000000}, 9);
}
TEST(EncodedS2CellIdVector, SixFaceCells) {
vector<S2CellId> ids;
for (int face = 0; face < 6; ++face) {
ids.push_back(S2CellId::FromFace(face));
}
TestEncodedS2CellIdVector(ids, 8);
}
TEST(EncodedS2CellIdVector, FourLevel10Children) {
vector<S2CellId> ids;
S2CellId parent = MakeCellIdOrDie("3/012301230");
for (S2CellId id = parent.child_begin();
id != parent.child_end(); id = id.next()) {
ids.push_back(id);
}
TestEncodedS2CellIdVector(ids, 8);
}
TEST(EncodedS2CellIdVector, FractalS2ShapeIndexCells) {
S2Testing::Fractal fractal;
fractal.SetLevelForApproxMaxEdges(3 * 1024);
S2Point center = s2textformat::MakePointOrDie("47.677:-122.206");
MutableS2ShapeIndex index;
index.Add(make_unique<S2Loop::OwningShape>(
fractal.MakeLoop(S2::GetFrame(center), S1Angle::Degrees(1))));
vector<S2CellId> ids;
for (MutableS2ShapeIndex::Iterator it(&index, S2ShapeIndex::BEGIN);
!it.done(); it.Next()) {
ids.push_back(it.id());
}
EXPECT_EQ(966, ids.size());
TestEncodedS2CellIdVector(ids, 2902);
}
TEST(EncodedS2CellIdVector, CoveringCells) {
vector<uint64> ids {
0x414a617f00000000, 0x414a61c000000000, 0x414a624000000000,
0x414a63c000000000, 0x414a647000000000, 0x414a64c000000000,
0x414a653000000000, 0x414a704000000000, 0x414a70c000000000,
0x414a714000000000, 0x414a71b000000000, 0x414a7a7c00000000,
0x414a7ac000000000, 0x414a8a4000000000, 0x414a8bc000000000,
0x414a8c4000000000, 0x414a8d7000000000, 0x414a8dc000000000,
0x414a914000000000, 0x414a91c000000000, 0x414a924000000000,
0x414a942c00000000, 0x414a95c000000000, 0x414a96c000000000,
0x414ab0c000000000, 0x414ab14000000000, 0x414ab34000000000,
0x414ab3c000000000, 0x414ab44000000000, 0x414ab4c000000000,
0x414ab6c000000000, 0x414ab74000000000, 0x414ab8c000000000,
0x414ab94000000000, 0x414aba1000000000, 0x414aba3000000000,
0x414abbc000000000, 0x414abe4000000000, 0x414abec000000000,
0x414abf4000000000, 0x46b5454000000000, 0x46b545c000000000,
0x46b5464000000000, 0x46b547c000000000, 0x46b5487000000000,
0x46b548c000000000, 0x46b5494000000000, 0x46b54a5400000000,
0x46b54ac000000000, 0x46b54b4000000000, 0x46b54bc000000000,
0x46b54c7000000000, 0x46b54c8004000000, 0x46b54ec000000000,
0x46b55ad400000000, 0x46b55b4000000000, 0x46b55bc000000000,
0x46b55c4000000000, 0x46b55c8100000000, 0x46b55dc000000000,
0x46b55e4000000000, 0x46b5604000000000, 0x46b560c000000000,
0x46b561c000000000, 0x46ca424000000000, 0x46ca42c000000000,
0x46ca43c000000000, 0x46ca444000000000, 0x46ca45c000000000,
0x46ca467000000000, 0x46ca469000000000, 0x46ca5fc000000000,
0x46ca604000000000, 0x46ca60c000000000, 0x46ca674000000000,
0x46ca679000000000, 0x46ca67f000000000, 0x46ca684000000000,
0x46ca855000000000, 0x46ca8c4000000000, 0x46ca8cc000000000,
0x46ca8e5400000000, 0x46ca8ec000000000, 0x46ca8f0100000000,
0x46ca8fc000000000, 0x46ca900400000000, 0x46ca98c000000000,
0x46ca994000000000, 0x46ca99c000000000, 0x46ca9a4000000000,
0x46ca9ac000000000, 0x46ca9bd500000000, 0x46ca9e4000000000,
0x46ca9ec000000000, 0x46caf34000000000, 0x46caf4c000000000,
0x46caf54000000000
};
EXPECT_EQ(97, ids.size());
TestEncodedS2CellIdVector(ids, 488);
}
TEST(EncodedS2CellIdVector, LowerBoundLimits) {
// Test seeking before the beginning and past the end of the vector.
S2CellId first = S2CellId::Begin(S2CellId::kMaxLevel);
S2CellId last = S2CellId::End(S2CellId::kMaxLevel).prev();
Encoder encoder;
EncodedS2CellIdVector cell_ids = MakeEncodedS2CellIdVector(
{first, last}, &encoder);
EXPECT_EQ(0, cell_ids.lower_bound(S2CellId::None()));
EXPECT_EQ(0, cell_ids.lower_bound(first));
EXPECT_EQ(1, cell_ids.lower_bound(first.next()));
EXPECT_EQ(1, cell_ids.lower_bound(last.prev()));
EXPECT_EQ(1, cell_ids.lower_bound(last));
EXPECT_EQ(2, cell_ids.lower_bound(last.next()));
EXPECT_EQ(2, cell_ids.lower_bound(S2CellId::Sentinel()));
}
} // namespace s2coding

View File

@ -0,0 +1,68 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2point_vector.h"
using absl::Span;
using std::vector;
namespace s2coding {
// To save space (especially for vectors of length 0, 1, and 2), the encoding
// format is encoded in the low-order 3 bits of the vector size. Up to 7
// encoding formats are supported (only 2 are currently defined). Additional
// formats could be supported by using "7" as an overflow indicator and
// encoding the actual format separately, but it seems unlikely we will ever
// need to do that.
static const int kEncodingFormatBits = 3;
void EncodeS2PointVector(Span<const S2Point> v, CodingHint hint,
Encoder* encoder) {
// TODO(ericv): Implement CodingHint::COMPACT.
encoder->Ensure(Varint::kMax64 + v.size() * sizeof(S2Point));
uint64 size_format = (v.size() << kEncodingFormatBits |
EncodedS2PointVector::UNCOMPRESSED);
encoder->put_varint64(size_format);
encoder->putn(v.data(), v.size() * sizeof(S2Point));
}
bool EncodedS2PointVector::Init(Decoder* decoder) {
uint64 size_format;
if (!decoder->get_varint64(&size_format)) return false;
format_ = static_cast<Format>(size_format & ((1 << kEncodingFormatBits) - 1));
if (format_ != UNCOMPRESSED) return false;
// Note that the encoding format supports up to 2**59 vertices, but we
// currently only support decoding up to 2**32 vertices.
size_format >>= kEncodingFormatBits;
if (size_format > std::numeric_limits<uint32>::max()) return false;
size_ = size_format;
size_t bytes = size_t{size_} * sizeof(S2Point);
if (decoder->avail() < bytes) return false;
uncompressed_.points = reinterpret_cast<const S2Point*>(decoder->ptr());
decoder->skip(bytes);
return true;
}
vector<S2Point> EncodedS2PointVector::Decode() const {
return vector<S2Point>(uncompressed_.points, uncompressed_.points + size_);
}
} // namespace s2coding

View File

@ -0,0 +1,118 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_ENCODED_S2POINT_VECTOR_H_
#define S2_ENCODED_S2POINT_VECTOR_H_
#include "s2/third_party/absl/types/span.h"
#include "s2/encoded_uint_vector.h"
#include "s2/s2point.h"
namespace s2coding {
// Controls whether to optimize for speed or size when encoding points. (Note
// that encoding is always lossless, and that currently compact encodings are
// only possible when points have been snapped to S2CellId centers.)
enum CodingHint { FAST, COMPACT };
// Encodes a vector of S2Points in a format that can later be decoded as an
// EncodedS2PointVector.
//
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
void EncodeS2PointVector(absl::Span<const S2Point> v, CodingHint hint,
Encoder* encoder);
// This class represents an encoded vector of S2Points. Values are decoded
// only when they are accessed. This allows for very fast initialization and
// no additional memory use beyond the encoded data. The encoded data is not
// owned by this class; typically it points into a large contiguous buffer
// that contains other encoded data as well.
//
// This is one of several helper classes that allow complex data structures to
// be initialized from an encoded format in constant time and then decoded on
// demand. This can be a big performance advantage when only a small part of
// the data structure is actually used.
class EncodedS2PointVector {
public:
// Constructs an uninitialized object; requires Init() to be called.
EncodedS2PointVector() {}
// Initializes the EncodedS2PointVector.
//
// REQUIRES: The Decoder data buffer must outlive this object.
bool Init(Decoder* decoder);
// Returns the size of the original vector.
size_t size() const;
// Returns the element at the given index.
S2Point operator[](int i) const;
// Decodes and returns the entire original vector.
std::vector<S2Point> Decode() const;
private:
// We use a tagged union to represent multiple formats, as opposed to an
// abstract base class or templating. This represents the best compromise
// between performance, space, and convenience. Note that the overhead of
// checking the tag is trivial and will typically be branch-predicted
// perfectly.
//
// TODO(ericv): Once additional formats have been implemented, consider
// using std::variant<> instead. It's unclear whether this would have
// better or worse performance than the current approach.
enum Format : uint8 {
UNCOMPRESSED = 0,
CELL_ID = 1,
};
Format format_;
uint32 size_;
union {
struct {
const S2Point* points;
} uncompressed_;
struct {
// TODO(ericv): Implement.
} cell_id_;
};
friend void EncodeS2PointVector(absl::Span<const S2Point>, CodingHint,
Encoder*);
};
////////////////// Implementation details follow ////////////////////
inline size_t EncodedS2PointVector::size() const {
return size_;
}
inline S2Point EncodedS2PointVector::operator[](int i) const {
switch (format_) {
case UNCOMPRESSED:
return uncompressed_.points[i];
case CELL_ID:
S2_LOG(FATAL) << "Not implemented yet";
return S2Point();
}
}
} // namespace s2coding
#endif // S2_ENCODED_S2POINT_VECTOR_H_

View File

@ -0,0 +1,54 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2point_vector.h"
#include <vector>
#include <gtest/gtest.h>
using std::vector;
namespace s2coding {
void TestEncodedS2PointVector(const vector<S2Point>& expected,
size_t expected_bytes) {
Encoder encoder;
EncodeS2PointVector(expected, CodingHint::FAST, &encoder);
EXPECT_EQ(expected_bytes, encoder.length());
Decoder decoder(encoder.base(), encoder.length());
EncodedS2PointVector actual;
ASSERT_TRUE(actual.Init(&decoder));
EXPECT_EQ(actual.Decode(), expected);
}
TEST(EncodedS2PointVectorTest, Empty) {
TestEncodedS2PointVector({}, 1);
}
TEST(EncodedS2PointVectorTest, OnePoint) {
TestEncodedS2PointVector({S2Point(1, 0, 0)}, 25);
}
TEST(EncodedS2PointVectorTest, TenPoints) {
vector<S2Point> points;
for (int i = 0; i < 10; ++i) {
points.push_back(S2Point(1, i, 0).Normalize());
}
TestEncodedS2PointVector(points, 241);
}
} // namespace s2coding

View File

@ -0,0 +1,147 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2shape_index.h"
#include <memory>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/mutable_s2shape_index.h"
using absl::make_unique;
using std::unique_ptr;
using std::vector;
bool EncodedS2ShapeIndex::Iterator::Locate(const S2Point& target) {
return LocateImpl(target, this);
}
EncodedS2ShapeIndex::CellRelation EncodedS2ShapeIndex::Iterator::Locate(
S2CellId target) {
return LocateImpl(target, this);
}
unique_ptr<EncodedS2ShapeIndex::IteratorBase>
EncodedS2ShapeIndex::Iterator::Clone() const {
return make_unique<Iterator>(*this);
}
void EncodedS2ShapeIndex::Iterator::Copy(const IteratorBase& other) {
*this = *down_cast<const Iterator*>(&other);
}
S2Shape* EncodedS2ShapeIndex::GetShape(int id) const {
// This method is called when a shape has not been decoded yet.
unique_ptr<S2Shape> shape = (*shape_factory_)[id];
if (shape) shape->id_ = id;
S2Shape* expected = kUndecodedShape();
if (shapes_[id].compare_exchange_strong(expected, shape.get(),
std::memory_order_relaxed)) {
return shape.release(); // Ownership has been transferred to shapes_.
}
return shapes_[id].load(std::memory_order_relaxed);
}
inline const S2ShapeIndexCell* EncodedS2ShapeIndex::GetCell(int i) const {
// This method is called by Iterator::cell() when the cell has not been
// decoded yet. For thread safety, we first decode the cell and then assign
// it atomically using a compare-and-swap operation.
auto cell = make_unique<S2ShapeIndexCell>();
Decoder decoder = encoded_cells_.GetDecoder(i);
if (cell->Decode(num_shape_ids(), &decoder)) {
S2ShapeIndexCell* expected = nullptr;
if (cells_[i].compare_exchange_strong(expected, cell.get(),
std::memory_order_relaxed)) {
return cell.release(); // Ownership has been transferred to cells_.
}
}
return cells_[i].load(std::memory_order_relaxed);
}
const S2ShapeIndexCell* EncodedS2ShapeIndex::Iterator::GetCell() const {
return index_->GetCell(cell_pos_);
}
EncodedS2ShapeIndex::EncodedS2ShapeIndex() {
}
EncodedS2ShapeIndex::~EncodedS2ShapeIndex() {
// Optimization notes:
//
// - For large S2ShapeIndexes where very few cells need to be decoded,
// initialization/destruction of the cells_ vector can dominate benchmark
// times. (In theory this could also happen with shapes_.)
//
// - Although Minimize() does more than required (by resetting the vector
// elements to their default values), this does not affect benchmarks.
//
// - The time required to initialize and destroy cells_ and/or shapes_
// could be avoided by using more complex data structures, at the expense
// of increasing access times. One simple idea would be to use
// uninitialized memory for the atomic pointers, plus one bit per pointer
// indicating whether it has been initialized yet. This would reduce the
// linear initialization/destruction costs by a factor of 64.
Minimize();
}
bool EncodedS2ShapeIndex::Init(Decoder* decoder,
const ShapeFactory& shape_factory) {
Minimize();
uint64 max_edges_version;
if (!decoder->get_varint64(&max_edges_version)) return false;
int version = max_edges_version & 3;
if (version != MutableS2ShapeIndex::kCurrentEncodingVersionNumber) {
return false;
}
options_.set_max_edges_per_cell(max_edges_version >> 2);
// AtomicShape is a subtype of std::atomic<S2Shape*> that changes the
// default constructor value to kUndecodedShape(). This saves the effort of
// initializing all the elements twice.
shapes_ = std::vector<AtomicShape>(shape_factory.size());
shape_factory_ = shape_factory.Clone();
if (!cell_ids_.Init(decoder)) return false;
// The cells_ elements are default-initialized to nullptr.
cells_ = std::vector<std::atomic<S2ShapeIndexCell*>>(cell_ids_.size());
return encoded_cells_.Init(decoder);
}
void EncodedS2ShapeIndex::Minimize() {
for (auto& atomic_shape : shapes_) {
S2Shape* shape = atomic_shape.load(std::memory_order_relaxed);
if (shape != kUndecodedShape() && shape != nullptr) {
atomic_shape.store(kUndecodedShape(), std::memory_order_relaxed);
delete shape;
}
}
for (auto& atomic_cell : cells_) {
S2ShapeIndexCell* cell = atomic_cell.load(std::memory_order_relaxed);
if (cell != nullptr) {
atomic_cell.store(nullptr, std::memory_order_relaxed);
delete cell;
}
}
}
size_t EncodedS2ShapeIndex::SpaceUsed() const {
// TODO(ericv): Add SpaceUsed() method to S2Shape base class,and Include
// memory owned by the allocated S2Shapes (here and in S2ShapeIndex).
size_t size = sizeof(*this);
size += shapes_.capacity() * sizeof(std::atomic<S2Shape*>);
size += cells_.capacity() * sizeof(std::atomic<S2ShapeIndexCell*>);
return size;
}

View File

@ -0,0 +1,230 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_ENCODED_S2SHAPE_INDEX_H_
#define S2_ENCODED_S2SHAPE_INDEX_H_
#include "s2/encoded_s2cell_id_vector.h"
#include "s2/encoded_string_vector.h"
#include "s2/mutable_s2shape_index.h"
class EncodedS2ShapeIndex final : public S2ShapeIndex {
public:
using Options = MutableS2ShapeIndex::Options;
using ShapeFactory = S2ShapeIndex::ShapeFactory;
// Creates an index that must be initialized by calling Init().
EncodedS2ShapeIndex();
~EncodedS2ShapeIndex() override;
// Initializes the EncodedS2ShapeIndex, returning true on success.
//
// This method does not decode the S2Shape objects in the index; this is
// the responsibility of the client-provided function "shape_factory"
// (see s2shapeutil_coding.h). Example usage:
//
// index.Init(decoder, s2shapeutil::LazyDecodeShapeFactory(decoder));
//
// Note that the encoded shape vector must *precede* the encoded S2ShapeIndex
// in the Decoder's data buffer in this example.
bool Init(Decoder* decoder, const ShapeFactory& shape_factory);
const Options& options() const { return options_; }
// The number of distinct shape ids in the index. This equals the number of
// shapes in the index provided that no shapes have ever been removed.
// (Shape ids are not reused.)
int num_shape_ids() const override { return shapes_.size(); }
// Return a pointer to the shape with the given id, or nullptr if the shape
// has been removed from the index.
S2Shape* shape(int id) const override;
// Minimizes memory usage by requesting that any data structures that can be
// rebuilt should be discarded. This method invalidates all iterators.
//
// Like all non-const methods, this method is not thread-safe.
void Minimize() override;
class Iterator final : public IteratorBase {
public:
// Default constructor; must be followed by a call to Init().
Iterator();
// Constructs an iterator positioned as specified. By default iterators
// are unpositioned, since this avoids an extra seek in this situation
// where one of the seek methods (such as Locate) is immediately called.
//
// If you want to position the iterator at the beginning, e.g. in order to
// loop through the entire index, do this instead:
//
// for (EncodedS2ShapeIndex::Iterator it(&index, S2ShapeIndex::BEGIN);
// !it.done(); it.Next()) { ... }
explicit Iterator(const EncodedS2ShapeIndex* index,
InitialPosition pos = UNPOSITIONED);
// Initializes an iterator for the given EncodedS2ShapeIndex.
void Init(const EncodedS2ShapeIndex* index,
InitialPosition pos = UNPOSITIONED);
// Inherited non-virtual methods:
// S2CellId id() const;
// const S2ShapeIndexCell& cell() const;
// bool done() const;
// S2Point center() const;
// IteratorBase API:
void Begin() override;
void Finish() override;
void Next() override;
bool Prev() override;
void Seek(S2CellId target) override;
bool Locate(const S2Point& target) override;
CellRelation Locate(S2CellId target) override;
protected:
const S2ShapeIndexCell* GetCell() const override;
std::unique_ptr<IteratorBase> Clone() const override;
void Copy(const IteratorBase& other) override;
private:
void Refresh(); // Updates the IteratorBase fields.
const EncodedS2ShapeIndex* index_;
int32 cell_pos_; // Current position in the vector of index cells.
int32 num_cells_;
};
// Returns the number of bytes currently occupied by the index (including any
// unused space at the end of vectors, etc). It has the same thread safety
// as the other "const" methods (see introduction).
size_t SpaceUsed() const override;
protected:
std::unique_ptr<IteratorBase> NewIterator(InitialPosition pos) const override;
private:
friend class Iterator;
// Returns a value indicating that a shape has not been decoded yet.
inline static S2Shape* kUndecodedShape() {
return reinterpret_cast<S2Shape*>(1);
}
// Like std::atomic<S2Shape*>, but defaults to kUndecodedShape().
class AtomicShape : public std::atomic<S2Shape*> {
public:
AtomicShape() : std::atomic<S2Shape*>(kUndecodedShape()) {}
};
S2Shape* GetShape(int id) const;
const S2ShapeIndexCell* GetCell(int i) const;
std::unique_ptr<ShapeFactory> shape_factory_;
// The options specified for this index.
Options options_;
// A vector containing all shapes in the index. Initially all shapes are
// set to kUndecodedShape(); as shapes are decoded, they are added to the
// vector using std::atomic::compare_exchange_strong.
mutable std::vector<AtomicShape> shapes_;
// A vector containing the S2CellIds of each cell in the index.
s2coding::EncodedS2CellIdVector cell_ids_;
// A vector containing the encoded contents of each cell in the index.
s2coding::EncodedStringVector encoded_cells_;
// A vector containing the decoded contents of each cell in the index.
// Initially all values are nullptr; cells are decoded on demand and added
// to the vector using std::atomic::compare_exchange_strong.
mutable std::vector<std::atomic<S2ShapeIndexCell*>> cells_;
EncodedS2ShapeIndex(const EncodedS2ShapeIndex&) = delete;
void operator=(const EncodedS2ShapeIndex&) = delete;
};
////////////////// Implementation details follow ////////////////////
inline EncodedS2ShapeIndex::Iterator::Iterator() : index_(nullptr) {
}
inline EncodedS2ShapeIndex::Iterator::Iterator(
const EncodedS2ShapeIndex* index, InitialPosition pos) {
Init(index, pos);
}
inline void EncodedS2ShapeIndex::Iterator::Init(
const EncodedS2ShapeIndex* index, InitialPosition pos) {
index_ = index;
num_cells_ = index->cell_ids_.size();
cell_pos_ = (pos == BEGIN) ? 0 : num_cells_;
Refresh();
}
inline void EncodedS2ShapeIndex::Iterator::Refresh() {
if (cell_pos_ == num_cells_) {
set_finished();
} else {
set_state(index_->cell_ids_[cell_pos_],
index_->cells_[cell_pos_].load(std::memory_order_relaxed));
}
}
inline void EncodedS2ShapeIndex::Iterator::Begin() {
cell_pos_ = 0;
Refresh();
}
inline void EncodedS2ShapeIndex::Iterator::Finish() {
cell_pos_ = num_cells_;
Refresh();
}
inline void EncodedS2ShapeIndex::Iterator::Next() {
S2_DCHECK(!done());
++cell_pos_;
Refresh();
}
inline bool EncodedS2ShapeIndex::Iterator::Prev() {
if (cell_pos_ == 0) return false;
--cell_pos_;
Refresh();
return true;
}
inline void EncodedS2ShapeIndex::Iterator::Seek(S2CellId target) {
cell_pos_ = index_->cell_ids_.lower_bound(target);
Refresh();
}
inline std::unique_ptr<EncodedS2ShapeIndex::IteratorBase>
EncodedS2ShapeIndex::NewIterator(InitialPosition pos) const {
return absl::make_unique<Iterator>(this, pos);
}
inline S2Shape* EncodedS2ShapeIndex::shape(int id) const {
S2Shape* shape = shapes_[id].load(std::memory_order_relaxed);
if (shape != kUndecodedShape()) return shape;
return GetShape(id);
}
#endif // S2_ENCODED_S2SHAPE_INDEX_H_

View File

@ -0,0 +1,244 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_s2shape_index.h"
#include <map>
#include <vector>
#include <gtest/gtest.h>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/third_party/absl/strings/str_cat.h"
#include "s2/mutable_s2shape_index.h"
#include "s2/s2builder.h"
#include "s2/s2builderutil_s2polyline_layer.h"
#include "s2/s2builderutil_snap_functions.h"
#include "s2/s2cap.h"
#include "s2/s2closest_edge_query.h"
#include "s2/s2contains_point_query.h"
#include "s2/s2edge_distances.h"
#include "s2/s2latlng.h"
#include "s2/s2lax_polygon_shape.h"
#include "s2/s2lax_polyline_shape.h"
#include "s2/s2loop.h"
#include "s2/s2point_vector_shape.h"
#include "s2/s2pointutil.h"
#include "s2/s2shapeutil_coding.h"
#include "s2/s2shapeutil_testing.h"
#include "s2/s2testing.h"
#include "s2/s2text_format.h"
using absl::make_unique;
using absl::StrCat;
using s2builderutil::S2CellIdSnapFunction;
using s2builderutil::S2PolylineLayer;
using std::max;
using std::unique_ptr;
using std::vector;
template <class Shape>
bool DecodeHomegeneousShapeIndex(EncodedS2ShapeIndex* index, Decoder* decoder) {
return index->Init(decoder,
s2shapeutil::HomogeneousShapeFactory<Shape>(decoder));
}
template <class InShape, class OutShape>
void TestEncodedS2ShapeIndex(const MutableS2ShapeIndex& expected,
size_t expected_bytes) {
Encoder encoder;
s2shapeutil::EncodeHomogeneousShapes<InShape>(expected, &encoder);
size_t shapes_bytes = encoder.length();
expected.Encode(&encoder);
EXPECT_EQ(expected_bytes, encoder.length() - shapes_bytes);
Decoder decoder(encoder.base(), encoder.length());
EncodedS2ShapeIndex actual;
ASSERT_TRUE(DecodeHomegeneousShapeIndex<OutShape>(&actual, &decoder));
EXPECT_EQ(expected.options().max_edges_per_cell(),
actual.options().max_edges_per_cell());
s2testing::ExpectEqual(expected, actual);
}
TEST(EncodedS2ShapeIndex, Empty) {
MutableS2ShapeIndex index;
TestEncodedS2ShapeIndex<S2LaxPolylineShape, S2LaxPolylineShape>(index, 4);
}
TEST(EncodedS2ShapeIndex, OneEdge) {
MutableS2ShapeIndex index;
index.Add(s2textformat::MakeLaxPolylineOrDie("1:1, 2:2"));
TestEncodedS2ShapeIndex<S2LaxPolylineShape, S2LaxPolylineShape>(index, 8);
}
TEST(EncodedS2ShapeIndex, RegularLoops) {
struct TestCase {
int num_edges;
size_t expected_bytes;
};
vector<TestCase> test_cases = {
{4, 8},
{8, 8},
{16, 16},
{64, 77},
{256, 327},
{4096, 8813},
{65536, 168291},
};
for (const auto& test_case : test_cases) {
MutableS2ShapeIndex index;
S2Testing::rnd.Reset(test_case.num_edges);
SCOPED_TRACE(StrCat("num_edges = ", test_case.num_edges));
S2Polygon polygon(S2Loop::MakeRegularLoop(S2Point(3, 2, 1).Normalize(),
S1Angle::Degrees(0.1),
test_case.num_edges));
index.Add(make_unique<S2LaxPolygonShape>(polygon));
TestEncodedS2ShapeIndex<S2LaxPolygonShape, EncodedS2LaxPolygonShape>(
index, test_case.expected_bytes);
}
}
TEST(EncodedS2ShapeIndex, OverlappingPointClouds) {
struct TestCase {
int num_shapes, num_points_per_shape;
size_t expected_bytes;
};
vector<TestCase> test_cases = {
{1, 50, 83},
{2, 100, 583},
{4, 100, 1383},
};
S2Cap cap(S2Point(0.1, -0.4, 0.3).Normalize(), S1Angle::Degrees(1));
for (const auto& test_case : test_cases) {
MutableS2ShapeIndex index;
S2Testing::rnd.Reset(test_case.num_shapes);
SCOPED_TRACE(StrCat("num_shapes = ", test_case.num_shapes));
for (int i = 0; i < test_case.num_shapes; ++i) {
vector<S2Point> points;
for (int j = 0; j < test_case.num_points_per_shape; ++j) {
points.push_back(S2Testing::SamplePoint(cap));
}
index.Add(make_unique<S2PointVectorShape>(points));
}
TestEncodedS2ShapeIndex<S2PointVectorShape, EncodedS2PointVectorShape>(
index, test_case.expected_bytes);
}
}
TEST(EncodedS2ShapeIndex, OverlappingPolylines) {
struct TestCase {
int num_shapes, num_shape_edges;
size_t expected_bytes;
};
vector<TestCase> test_cases = {
{2, 50, 139},
{10, 50, 777},
{20, 50, 2219},
};
S2Cap cap(S2Point(-0.2, -0.3, 0.4).Normalize(), S1Angle::Degrees(0.1));
for (const auto& test_case : test_cases) {
S1Angle edge_len = 2 * cap.GetRadius() / test_case.num_shape_edges;
MutableS2ShapeIndex index;
S2Testing::rnd.Reset(test_case.num_shapes);
SCOPED_TRACE(StrCat("num_shapes = ", test_case.num_shapes));
for (int i = 0; i < test_case.num_shapes; ++i) {
S2Point a = S2Testing::SamplePoint(cap), b = S2Testing::RandomPoint();
vector<S2Point> vertices;
int n = test_case.num_shape_edges;
for (int j = 0; j <= n; ++j) {
vertices.push_back(S2::InterpolateAtDistance(j * edge_len, a, b));
}
index.Add(make_unique<S2LaxPolylineShape>(vertices));
}
TestEncodedS2ShapeIndex<S2LaxPolylineShape, EncodedS2LaxPolylineShape>(
index, test_case.expected_bytes);
}
}
TEST(EncodedS2ShapeIndex, OverlappingLoops) {
struct TestCase {
int num_shapes, max_edges_per_loop;
size_t expected_bytes;
};
vector<TestCase> test_cases = {
{2, 250, 138},
{5, 250, 1084},
{25, 50, 3673},
};
S2Cap cap(S2Point(-0.1, 0.25, 0.2).Normalize(), S1Angle::Degrees(3));
for (const auto& test_case : test_cases) {
MutableS2ShapeIndex index;
S2Testing::rnd.Reset(test_case.num_shapes);
SCOPED_TRACE(StrCat("num_shapes = ", test_case.num_shapes));
for (int i = 0; i < test_case.num_shapes; ++i) {
S2Point center = S2Testing::SamplePoint(cap);
double radius_fraction = S2Testing::rnd.RandDouble();
// Scale the number of edges so that they are all about the same length
// (similar to modeling all geometry at a similar resolution).
int num_edges = max(3.0, test_case.max_edges_per_loop * radius_fraction);
S2Polygon polygon(S2Loop::MakeRegularLoop(
center, cap.GetRadius() * radius_fraction, num_edges));
index.Add(make_unique<S2LaxPolygonShape>(polygon));
}
TestEncodedS2ShapeIndex<S2LaxPolygonShape, EncodedS2LaxPolygonShape>(
index, test_case.expected_bytes);
}
}
// Like S2PolylineLayer, but converts the polyline to an S2LaxPolylineShape
// and adds it to an S2ShapeIndex (if the polyline is non-empty).
class IndexedLaxPolylineLayer : public S2Builder::Layer {
public:
using Options = S2PolylineLayer::Options;
explicit IndexedLaxPolylineLayer(MutableS2ShapeIndex* index,
const Options& options = Options())
: index_(index), polyline_(make_unique<S2Polyline>()),
layer_(polyline_.get(), options) {}
GraphOptions graph_options() const override {
return layer_.graph_options();
}
void Build(const Graph& g, S2Error* error) override {
layer_.Build(g, error);
if (error->ok() && polyline_->num_vertices() > 0) {
index_->Add(absl::make_unique<S2LaxPolylineShape>(*polyline_));
}
}
private:
MutableS2ShapeIndex* index_;
std::unique_ptr<S2Polyline> polyline_;
S2PolylineLayer layer_;
};
TEST(EncodedS2ShapeIndex, SnappedFractalPolylines) {
MutableS2ShapeIndex index;
S2Builder builder{S2Builder::Options{S2CellIdSnapFunction()}};
for (int i = 0; i < 5; ++i) {
builder.StartLayer(make_unique<IndexedLaxPolylineLayer>(&index));
S2Testing::Fractal fractal;
fractal.SetLevelForApproxMaxEdges(3 * 256);
auto frame = S2::GetFrame(S2LatLng::FromDegrees(10, i).ToPoint());
auto loop = fractal.MakeLoop(frame, S1Angle::Degrees(0.1));
std::vector<S2Point> vertices;
S2Testing::AppendLoopVertices(*loop, &vertices);
S2Polyline polyline(vertices);
builder.AddPolyline(polyline);
}
S2Error error;
ASSERT_TRUE(builder.Build(&error)) << error.text();
TestEncodedS2ShapeIndex<S2LaxPolylineShape, EncodedS2LaxPolylineShape>(
index, 8698);
}

View File

@ -0,0 +1,65 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_string_vector.h"
using absl::MakeSpan;
using absl::Span;
using absl::string_view;
using std::vector;
namespace s2coding {
StringVectorEncoder::StringVectorEncoder() {
}
void StringVectorEncoder::Encode(Encoder* encoder) {
offsets_.push_back(data_.length());
// We don't encode the first element of "offsets_", which is always zero.
EncodeUintVector<uint64>(MakeSpan(offsets_.data() + 1, &*offsets_.end()),
encoder);
encoder->Ensure(data_.length());
encoder->putn(data_.base(), data_.length());
}
void StringVectorEncoder::Encode(Span<const string> v, Encoder* encoder) {
StringVectorEncoder string_vector;
for (const auto& str : v) string_vector.Add(str);
string_vector.Encode(encoder);
}
bool EncodedStringVector::Init(Decoder* decoder) {
if (!offsets_.Init(decoder)) return false;
data_ = reinterpret_cast<const char*>(decoder->ptr());
if (offsets_.size() > 0) {
uint64 length = offsets_[offsets_.size() - 1];
if (decoder->avail() < length) return false;
decoder->skip(length);
}
return true;
}
vector<string_view> EncodedStringVector::Decode() const {
size_t n = size();
vector<string_view> result(n);
for (int i = 0; i < n; ++i) {
result[i] = (*this)[i];
}
return result;
}
} // namespace s2coding

View File

@ -0,0 +1,155 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_ENCODED_STRING_VECTOR_H_
#define S2_ENCODED_STRING_VECTOR_H_
#include <memory>
#include <string>
#include "s2/third_party/absl/strings/string_view.h"
#include "s2/third_party/absl/types/span.h"
#include "s2/encoded_uint_vector.h"
namespace s2coding {
// This class allows an EncodedStringVector to be created by adding strings
// incrementally. It also supports adding strings that are the output of
// another Encoder. For example, to create a vector of encoded S2Polygons,
// you can do this:
//
// void EncodePolygons(const vector<S2Polygon*>& polygons, Encoder* encoder) {
// StringVectorEncoder encoded_polygons;
// for (auto polygon : polygons) {
// polygon->Encode(encoded_polygons.AddViaEncoder());
// }
// encoded_polygons.Encode(encoder);
// }
class StringVectorEncoder {
public:
StringVectorEncoder();
// Adds a string to the encoded vector.
void Add(const string& str);
// Adds a string to the encoded vector by means of the given Encoder. The
// string consists of all output added to the encoder before the next call
// to any method of this class (after which the encoder is no longer valid).
Encoder* AddViaEncoder();
// Appends the EncodedStringVector representation to the given Encoder.
//
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
void Encode(Encoder* encoder);
// Encodes a vector of strings in a format that can later be decoded as an
// EncodedStringVector.
//
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
static void Encode(absl::Span<const string> v, Encoder* encoder);
private:
// A vector consisting of the starting offset of each string in the
// encoder's data buffer, plus a final entry pointing just past the end of
// the last string.
std::vector<uint64> offsets_;
Encoder data_;
};
// This class represents an encoded vector of strings. Values are decoded
// only when they are accessed. This allows for very fast initialization and
// no additional memory use beyond the encoded data. The encoded data is not
// owned by this class; typically it points into a large contiguous buffer
// that contains other encoded data as well.
//
// This is one of several helper classes that allow complex data structures to
// be initialized from an encoded format in constant time and then decoded on
// demand. This can be a big performance advantage when only a small part of
// the data structure is actually used.
class EncodedStringVector {
public:
// Constructs an uninitialized object; requires Init() to be called.
EncodedStringVector() {}
// Initializes the EncodedStringVector. Returns false on errors, leaving
// the vector in an unspecified state.
//
// REQUIRES: The Decoder data buffer must outlive this object.
bool Init(Decoder* decoder);
// Resets the vector to be empty.
void Clear();
// Returns the size of the original vector.
size_t size() const;
// Returns the string at the given index.
absl::string_view operator[](int i) const;
// Returns a Decoder initialized with the string at the given index.
Decoder GetDecoder(int i) const;
// Returns the entire vector of original strings. Requires that the
// data buffer passed to the constructor persists until the result vector is
// no longer needed.
std::vector<absl::string_view> Decode() const;
private:
EncodedUintVector<uint64> offsets_;
const char* data_;
};
////////////////// Implementation details follow ////////////////////
inline void StringVectorEncoder::Add(const string& str) {
offsets_.push_back(data_.length());
data_.Ensure(str.size());
data_.putn(str.data(), str.size());
}
inline Encoder* StringVectorEncoder::AddViaEncoder() {
offsets_.push_back(data_.length());
return &data_;
}
inline void EncodedStringVector::Clear() {
offsets_.Clear();
data_ = nullptr;
}
inline size_t EncodedStringVector::size() const {
return offsets_.size();
}
inline absl::string_view EncodedStringVector::operator[](int i) const {
uint64 start = (i == 0) ? 0 : offsets_[i - 1];
uint64 limit = offsets_[i];
return absl::string_view(data_ + start, limit - start);
}
inline Decoder EncodedStringVector::GetDecoder(int i) const {
uint64 start = (i == 0) ? 0 : offsets_[i - 1];
uint64 limit = offsets_[i];
return Decoder(data_ + start, limit - start);
}
} // namespace s2coding
#endif // S2_ENCODED_STRING_VECTOR_H_

View File

@ -0,0 +1,69 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_string_vector.h"
#include <vector>
#include <gtest/gtest.h>
#include "s2/third_party/absl/strings/string_view.h"
using absl::string_view;
using std::vector;
namespace s2coding {
void TestEncodedStringVector(const vector<string>& input,
size_t expected_bytes) {
Encoder encoder;
StringVectorEncoder::Encode(input, &encoder);
EXPECT_EQ(expected_bytes, encoder.length());
Decoder decoder(encoder.base(), encoder.length());
EncodedStringVector actual;
ASSERT_TRUE(actual.Init(&decoder));
vector<string_view> expected;
for (const auto& str : input) {
expected.push_back(string_view(str));
}
EXPECT_EQ(actual.Decode(), expected);
}
TEST(EncodedStringVectorTest, Empty) {
TestEncodedStringVector({}, 1);
}
TEST(EncodedStringVectorTest, EmptyString) {
TestEncodedStringVector({""}, 2);
}
TEST(EncodedStringVectorTest, RepeatedEmptyStrings) {
TestEncodedStringVector({"", "", ""}, 4);
}
TEST(EncodedStringVectorTest, OneString) {
TestEncodedStringVector({"apples"}, 8);
}
TEST(EncodedStringVectorTest, TwoStrings) {
TestEncodedStringVector({"fuji", "mutsu"}, 12);
}
TEST(EncodedStringVectorTest, TwoBigStrings) {
TestEncodedStringVector({string(10000, 'x'), string(100000, 'y')},
110007);
}
} // namespace s2coding

View File

@ -0,0 +1,279 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#ifndef S2_ENCODED_UINT_VECTOR_H_
#define S2_ENCODED_UINT_VECTOR_H_
#include <type_traits>
#include <vector>
#include "s2/third_party/absl/base/internal/unaligned_access.h"
#include "s2/third_party/absl/types/span.h"
#include "s2/util/coding/coder.h"
namespace s2coding {
// Encodes a vector of unsigned integers in a format that can later be
// decoded as an EncodedUintVector.
//
// REQUIRES: T is an unsigned integer type.
// REQUIRES: 2 <= sizeof(T) <= 8
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
template <class T>
void EncodeUintVector(absl::Span<const T> v, Encoder* encoder);
// This class represents an encoded vector of unsigned integers of type T.
// Values are decoded only when they are accessed. This allows for very fast
// initialization and no additional memory use beyond the encoded data.
// The encoded data is not owned by this class; typically it points into a
// large contiguous buffer that contains other encoded data as well.
//
// This is one of several helper classes that allow complex data structures to
// be initialized from an encoded format in constant time and then decoded on
// demand. This can be a big performance advantage when only a small part of
// the data structure is actually used.
//
// Values are encoded using a fixed number of bytes per value, where the
// number of bytes depends on the largest value present.
//
// REQUIRES: T is an unsigned integer type.
// REQUIRES: 2 <= sizeof(T) <= 8
template <class T>
class EncodedUintVector {
public:
static_assert(std::is_unsigned<T>::value, "Unsupported signed integer");
static_assert(sizeof(T) & 0xe, "Unsupported integer length");
// Constructs an uninitialized object; requires Init() to be called.
EncodedUintVector() {}
// Initializes the EncodedUintVector. Returns false on errors, leaving the
// vector in an unspecified state.
//
// REQUIRES: The Decoder data buffer must outlive this object.
bool Init(Decoder* decoder);
// Resets the vector to be empty.
void Clear();
// Returns the size of the original vector.
size_t size() const;
// Returns the element at the given index.
T operator[](int i) const;
// Returns the index of the first element x such that (x >= target), or
// size() if no such element exists.
//
// REQUIRES: The vector elements are sorted in non-decreasing order.
size_t lower_bound(T target) const;
// Decodes and returns the entire original vector.
std::vector<T> Decode() const;
private:
const char* data_;
uint32 size_;
uint8 len_;
};
// Encodes an unsigned integer in little-endian format using "length" bytes.
// (The client must ensure that the encoder's buffer is large enough.)
//
// REQUIRES: T is an unsigned integer type.
// REQUIRES: 2 <= sizeof(T) <= 8
// REQUIRES: 0 <= length <= sizeof(T)
// REQUIRES: value < 256 ** length
// REQUIRES: encoder->avail() >= length
template <class T>
void EncodeUintWithLength(T value, int length, Encoder* encoder);
// Decodes a variable-length integer consisting of "length" bytes starting at
// "ptr" in little-endian format.
//
// REQUIRES: T is an unsigned integer type.
// REQUIRES: 2 <= sizeof(T) <= 8
// REQUIRES: 0 <= length <= sizeof(T)
template <class T>
T GetUintWithLength(const void* ptr, int length);
// Decodes and consumes a variable-length integer consisting of "length" bytes
// in little-endian format. Returns false if not enough bytes are available.
//
// REQUIRES: T is an unsigned integer type.
// REQUIRES: 2 <= sizeof(T) <= 8
// REQUIRES: 0 <= length <= sizeof(T)
template <class T>
bool DecodeUintWithLength(int length, Decoder* decoder, T* result);
////////////////// Implementation details follow ////////////////////
template <class T>
inline void EncodeUintWithLength(T value, int length, Encoder* encoder) {
static_assert(std::is_unsigned<T>::value, "Unsupported signed integer");
static_assert(sizeof(T) & 0xe, "Unsupported integer length");
S2_DCHECK(length >= 0 && length <= sizeof(T));
S2_DCHECK_GE(encoder->avail(), length);
while (--length >= 0) {
encoder->put8(value);
value >>= 8;
}
S2_DCHECK_EQ(value, 0);
}
template <class T>
inline T GetUintWithLength(const char* ptr, int length) {
static_assert(std::is_unsigned<T>::value, "Unsupported signed integer");
static_assert(sizeof(T) & 0xe, "Unsupported integer length");
S2_DCHECK(length >= 0 && length <= sizeof(T));
// Note that the following code is faster than any of the following:
//
// - A loop that repeatedly loads and shifts one byte.
// - memcpying "length" bytes to a local variable of type T.
// - A switch statement that handles each length optimally.
//
// The following code is slightly faster:
//
// T mask = (length == 0) ? 0 : ~T{0} >> 8 * (sizeof(T) - length);
// return *reinterpret_cast<const T*>(ptr) & mask;
//
// However this technique is unsafe because in extremely rare cases it might
// access out-of-bounds heap memory. (This can only happen if "ptr" is
// within (sizeof(T) - length) bytes of the end of a memory page and the
// following page in the address space is unmapped.)
if (length & sizeof(T)) {
if (sizeof(T) == 8) return ABSL_INTERNAL_UNALIGNED_LOAD64(ptr);
if (sizeof(T) == 4) return ABSL_INTERNAL_UNALIGNED_LOAD32(ptr);
if (sizeof(T) == 2) return ABSL_INTERNAL_UNALIGNED_LOAD16(ptr);
S2_DCHECK_EQ(sizeof(T), 1);
return *ptr;
}
T x = 0;
ptr += length;
if (sizeof(T) > 4 && (length & 4)) {
x = ABSL_INTERNAL_UNALIGNED_LOAD32(ptr -= sizeof(uint32));
}
if (sizeof(T) > 2 && (length & 2)) {
x = (x << 16) + ABSL_INTERNAL_UNALIGNED_LOAD16(ptr -= sizeof(uint16));
}
if (sizeof(T) > 1 && (length & 1)) {
x = (x << 8) + static_cast<uint8>(*--ptr);
}
return x;
}
template <class T>
bool DecodeUintWithLength(int length, Decoder* decoder, T* result) {
if (decoder->avail() < length) return false;
const char* ptr = reinterpret_cast<const char*>(decoder->ptr());
*result = GetUintWithLength<T>(ptr, length);
decoder->skip(length);
return true;
}
template <class T>
void EncodeUintVector(absl::Span<const T> v, Encoder* encoder) {
// The encoding is as follows:
//
// varint64: (v.size() * sizeof(T)) | (len - 1)
// array of v.size() elements ["len" bytes each]
//
// Note that we don't allow (len == 0) since this would require an extra bit
// to encode the length.
T one_bits = 1; // Ensures len >= 1.
for (auto x : v) one_bits |= x;
int len = (Bits::Log2FloorNonZero64(one_bits) >> 3) + 1;
S2_DCHECK(len >= 1 && len <= 8);
// Note that the multiplication is optimized into a bit shift.
encoder->Ensure(Varint::kMax64 + v.size() * len);
uint64 size_len = (uint64{v.size()} * sizeof(T)) | (len - 1);
encoder->put_varint64(size_len);
for (auto x : v) {
EncodeUintWithLength(x, len, encoder);
}
}
template <class T>
bool EncodedUintVector<T>::Init(Decoder* decoder) {
uint64 size_len;
if (!decoder->get_varint64(&size_len)) return false;
size_ = size_len / sizeof(T); // Optimized into bit shift.
len_ = (size_len & (sizeof(T) - 1)) + 1;
if (size_ > std::numeric_limits<size_t>::max() / sizeof(T)) return false;
size_t bytes = size_ * len_;
if (decoder->avail() < bytes) return false;
data_ = reinterpret_cast<const char*>(decoder->ptr());
decoder->skip(bytes);
return true;
}
template <class T>
void EncodedUintVector<T>::Clear() {
size_ = 0;
data_ = nullptr;
}
template <class T>
inline size_t EncodedUintVector<T>::size() const {
return size_;
}
template <class T>
inline T EncodedUintVector<T>::operator[](int i) const {
S2_DCHECK(i >= 0 && i < size_);
return GetUintWithLength<T>(data_ + i * len_, len_);
}
template <class T>
inline size_t EncodedUintVector<T>::lower_bound(T target) const {
// TODO(ericv): Consider using the unused 28 bits of "len_" to store the
// last result of lower_bound() to be used as a hint. This should help in
// common situation where the same element is looked up repeatedly. This
// would require declaring the new field (length_lower_bound_hint_) as
// mutable std::atomic<uint32> (accessed using std::memory_order_relaxed)
// with a custom copy constructor that resets the hint component to zero.
size_t lo = 0, hi = size_;
while (lo < hi) {
size_t mid = (lo + hi) >> 1;
if ((*this)[mid] < target) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
template <class T>
std::vector<T> EncodedUintVector<T>::Decode() const {
std::vector<T> result(size_);
for (int i = 0; i < size_; ++i) {
result[i] = (*this)[i];
}
return result;
}
} // namespace s2coding
#endif // S2_ENCODED_UINT_VECTOR_H_

View File

@ -0,0 +1,72 @@
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: ericv@google.com (Eric Veach)
#include "s2/encoded_uint_vector.h"
#include <vector>
#include <gtest/gtest.h>
using std::vector;
namespace s2coding {
static_assert(sizeof(EncodedUintVector<uint64>) == 16, "too big");
template <class T>
void TestEncodedUintVector(const vector<T>& expected, size_t expected_bytes) {
Encoder encoder;
EncodeUintVector<T>(expected, &encoder);
EXPECT_EQ(expected_bytes, encoder.length());
Decoder decoder(encoder.base(), encoder.length());
EncodedUintVector<T> actual;
ASSERT_TRUE(actual.Init(&decoder));
EXPECT_EQ(actual.Decode(), expected);
}
TEST(EncodedUintVectorTest, Empty) {
TestEncodedUintVector(vector<uint32>{}, 1);
}
TEST(EncodedUintVectorTest, Zero) {
TestEncodedUintVector(vector<uint64>{0}, 2);
}
TEST(EncodedUintVectorTest, RepeatedZeros) {
TestEncodedUintVector(vector<uint16>{0, 0, 0}, 4);
}
TEST(EncodedUintVectorTest, MaxInt) {
TestEncodedUintVector(vector<uint64>{~0ULL}, 9);
}
TEST(EncodedUintVectorTest, OneByte) {
TestEncodedUintVector(vector<uint64>{0, 255, 1, 254}, 5);
}
TEST(EncodedUintVectorTest, TwoBytes) {
TestEncodedUintVector(vector<uint64>{0, 255, 256, 254}, 9);
}
TEST(EncodedUintVectorTest, ThreeBytes) {
TestEncodedUintVector(vector<uint64>{0xffffff, 0x0102, 0, 0x050403}, 13);
}
TEST(EncodedUintVectorTest, EightBytes) {
TestEncodedUintVector(vector<uint64>{~0ULL, 0, 0x0102030405060708}, 25);
}
} // namespace s2coding

View File

@ -21,7 +21,7 @@
#include <limits>
#include <vector>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include "s2/base/logging.h"
#include "s2/sequence_lexicon.h"

View File

@ -24,6 +24,8 @@
#include "s2/base/casts.h"
#include "s2/base/commandlineflags.h"
#include "s2/base/spinlock.h"
#include "s2/encoded_s2cell_id_vector.h"
#include "s2/encoded_string_vector.h"
#include "s2/r1interval.h"
#include "s2/r2.h"
#include "s2/r2rect.h"
@ -198,7 +200,7 @@ unique_ptr<S2Shape> MutableS2ShapeIndex::Release(int shape_id) {
pending_removals_->push_back(RemovedShape());
RemovedShape* removed = &pending_removals_->back();
removed->shape_id = shape->id();
removed->has_interior = shape->has_interior();
removed->has_interior = (shape->dimension() == 2);
removed->contains_tracker_origin =
s2shapeutil::ContainsBruteForce(*shape, kInteriorTrackerOrigin());
int num_edges = shape->num_edges();
@ -252,7 +254,7 @@ struct MutableS2ShapeIndex::FaceEdge {
int32 shape_id; // The shape that this edge belongs to
int32 edge_id; // Edge id within that shape
int32 max_level; // Not desirable to subdivide this edge beyond this level
bool has_interior; // Belongs to a shape that has an interior
bool has_interior; // Belongs to a shape of dimension 2.
R2Point a, b; // The edge endpoints, clipped to a given face
S2Shape::Edge edge; // The edge endpoints
};
@ -304,7 +306,7 @@ class MutableS2ShapeIndex::InteriorTracker {
// for every edge of the shape that might cross the current DrawTo() line.
// This updates the state to correspond to the new focus point.
//
// REQUIRES: shape->has_interior()
// REQUIRES: shape->dimension() == 2
void AddShape(int32 shape_id, bool is_inside);
// Moves the focus to the given point. This method should only be used when
@ -319,7 +321,7 @@ class MutableS2ShapeIndex::InteriorTracker {
// Indicates that the given edge of the given shape may cross the line
// segment between the old and new focus locations (see DrawTo).
// REQUIRES: shape->has_interior()
// REQUIRES: shape->dimension() == 2
inline void TestEdge(int32 shape_id, const S2Shape::Edge& edge);
// The set of shape ids that contain the current focus.
@ -805,7 +807,7 @@ void MutableS2ShapeIndex::AddShape(int id, vector<FaceEdge> all_edges[6],
// Construct a template for the edges to be added.
FaceEdge edge;
edge.shape_id = id;
edge.has_interior = shape->has_interior();
edge.has_interior = (shape->dimension() == 2);
if (edge.has_interior) {
tracker->AddShape(id, s2shapeutil::ContainsBruteForce(*shape,
tracker->focus()));
@ -1308,7 +1310,7 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell,
// line segment from the cell center to the entry vertex.
FaceEdge edge;
edge.shape_id = shape->id();
edge.has_interior = shape->has_interior();
edge.has_interior = (shape->dimension() == 2);
if (edge.has_interior) {
tracker->AddShape(shape_id, clipped.contains_center());
// There might not be any edges in this entire cell (i.e., it might be
@ -1532,3 +1534,53 @@ size_t MutableS2ShapeIndex::SpaceUsed() const {
return size;
}
void MutableS2ShapeIndex::Encode(Encoder* encoder) const {
// The version number is encoded in 2 bits, under the assumption that by the
// time we need 5 versions the first version can be permanently retired.
// This only saves 1 byte, but that's significant for very small indexes.
encoder->Ensure(Varint::kMax64);
uint64 max_edges = options_.max_edges_per_cell();
encoder->put_varint64(max_edges << 2 | kCurrentEncodingVersionNumber);
vector<S2CellId> cell_ids;
cell_ids.reserve(cell_map_.size());
s2coding::StringVectorEncoder encoded_cells;
for (Iterator it(this, S2ShapeIndex::BEGIN); !it.done(); it.Next()) {
cell_ids.push_back(it.id());
it.cell().Encode(num_shape_ids(), encoded_cells.AddViaEncoder());
}
s2coding::EncodeS2CellIdVector(cell_ids, encoder);
encoded_cells.Encode(encoder);
}
bool MutableS2ShapeIndex::Init(Decoder* decoder,
const ShapeFactory& shape_factory) {
Clear();
uint64 max_edges_version;
if (!decoder->get_varint64(&max_edges_version)) return false;
int version = max_edges_version & 3;
if (version != kCurrentEncodingVersionNumber) return false;
options_.set_max_edges_per_cell(max_edges_version >> 2);
uint32 num_shapes = shape_factory.size();
shapes_.reserve(num_shapes);
for (int shape_id = 0; shape_id < num_shapes; ++shape_id) {
auto shape = shape_factory[shape_id];
if (shape) shape->id_ = shape_id;
shapes_.push_back(std::move(shape));
}
s2coding::EncodedS2CellIdVector cell_ids;
s2coding::EncodedStringVector encoded_cells;
if (!cell_ids.Init(decoder)) return false;
if (!encoded_cells.Init(decoder)) return false;
for (int i = 0; i < cell_ids.size(); ++i) {
S2CellId id = cell_ids[i];
S2ShapeIndexCell* cell = new S2ShapeIndexCell;
Decoder decoder = encoded_cells.GetDecoder(i);
if (!cell->Decode(num_shapes, &decoder)) return false;
cell_map_.insert(cell_map_.end(), std::make_pair(id, cell));
}
return true;
}

View File

@ -26,6 +26,7 @@
#include <vector>
#include "s2/base/integral_types.h"
#include "s2/base/logging.h"
#include "s2/base/mutex.h"
#include "s2/base/spinlock.h"
@ -34,7 +35,6 @@
#include "s2/s2pointutil.h"
#include "s2/s2shape.h"
#include "s2/s2shape_index.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/third_party/absl/base/macros.h"
#include "s2/third_party/absl/base/thread_annotations.h"
#include "s2/third_party/absl/memory/memory.h"
@ -181,6 +181,30 @@ class MutableS2ShapeIndex final : public S2ShapeIndex {
// Like all non-const methods, this method is not thread-safe.
void Minimize() override;
// Appends an encoded representation of the S2ShapeIndex to "encoder".
//
// This method does not encode the S2Shapes in the index; it is the client's
// responsibility to encode them separately. For example:
//
// s2shapeutil::CompactEncodeTaggedShapes(index, encoder);
// index.Encode(encoder);
//
// REQUIRES: "encoder" uses the default constructor, so that its buffer
// can be enlarged as necessary by calling Ensure(int).
void Encode(Encoder* encoder) const;
// Decodes an S2ShapeIndex, returning true on success.
//
// This method does not decode the S2Shape objects in the index; this is
// the responsibility of the client-provided function "shape_factory"
// (see s2shapeutil_coding.h). Example usage:
//
// index.Init(decoder, s2shapeutil::LazyDecodeShapeFactory(decoder));
//
// Note that the S2Shape vector must be encoded *before* the S2ShapeIndex in
// this example.
bool Init(Decoder* decoder, const ShapeFactory& shape_factory);
class Iterator final : public IteratorBase {
public:
// Default constructor; must be followed by a call to Init().
@ -287,19 +311,24 @@ class MutableS2ShapeIndex final : public S2ShapeIndex {
std::unique_ptr<IteratorBase> NewIterator(InitialPosition pos) const override;
private:
friend class Iterator; // cell_map_
friend class EncodedS2ShapeIndex;
friend class Iterator;
friend class MutableS2ShapeIndexTest;
friend class S2Stats;
class BatchDescriptor;
class ClippedEdge;
struct BatchDescriptor;
struct ClippedEdge;
class EdgeAllocator;
class FaceEdge;
struct FaceEdge;
class InteriorTracker;
struct RemovedShape;
using ShapeIdSet = std::vector<int>;
// When adding a new encoding, be aware that old binaries will not be able
// to decode it.
static const unsigned char kCurrentEncodingVersionNumber = 0;
// Internal methods are documented with their definitions.
bool is_first_update() const;
bool is_shape_being_removed(int shape_id) const;
@ -381,7 +410,7 @@ class MutableS2ShapeIndex final : public S2ShapeIndex {
// The representation of an edge that has been queued for removal.
struct RemovedShape {
int32 shape_id;
bool has_interior;
bool has_interior; // Belongs to a shape of dimension 2.
bool contains_tracker_origin;
std::vector<S2Shape::Edge> edges;
};
@ -504,7 +533,6 @@ inline void MutableS2ShapeIndex::Iterator::Begin() {
// Make sure that the index has not been modified since Init() was called.
S2_DCHECK(index_->is_fresh());
iter_ = index_->cell_map_.begin();
end_ = index_->cell_map_.end();
Refresh();
}

View File

@ -44,7 +44,9 @@
#include "s2/s2loop.h"
#include "s2/s2pointutil.h"
#include "s2/s2polygon.h"
#include "s2/s2shapeutil_coding.h"
#include "s2/s2shapeutil_contains_brute_force.h"
#include "s2/s2shapeutil_testing.h"
#include "s2/s2shapeutil_visit_crossing_edge_pairs.h"
#include "s2/s2testing.h"
#include "s2/s2text_format.h"
@ -76,6 +78,9 @@ class MutableS2ShapeIndexTest : public ::testing::Test {
// the cell center and verify that this matches "index_contains_center".
void ValidateInterior(const S2Shape* shape, S2CellId id,
bool index_contains_center);
// Verifies that the index can be encoded and decoded without change.
void TestEncodeDecode();
};
void MutableS2ShapeIndexTest::QuadraticValidate() {
@ -166,6 +171,15 @@ void MutableS2ShapeIndexTest::ValidateInterior(
}
}
void MutableS2ShapeIndexTest::TestEncodeDecode() {
Encoder encoder;
index_.Encode(&encoder);
Decoder decoder(encoder.base(), encoder.length());
MutableS2ShapeIndex index2;
ASSERT_TRUE(index2.Init(&decoder, s2shapeutil::WrappedShapeFactory(&index_)));
s2testing::ExpectEqual(index_, index2);
}
namespace {
void TestIteratorMethods(const MutableS2ShapeIndex& index) {
@ -239,6 +253,7 @@ TEST_F(MutableS2ShapeIndexTest, NoEdges) {
MutableS2ShapeIndex::Iterator it(&index_, S2ShapeIndex::BEGIN);
EXPECT_TRUE(it.done());
TestIteratorMethods(index_);
TestEncodeDecode();
}
TEST_F(MutableS2ShapeIndexTest, OneEdge) {
@ -246,6 +261,7 @@ TEST_F(MutableS2ShapeIndexTest, OneEdge) {
S2Point(0, 1, 0))));
QuadraticValidate();
TestIteratorMethods(index_);
TestEncodeDecode();
}
TEST_F(MutableS2ShapeIndexTest, ShrinkToFitOptimization) {
@ -259,6 +275,7 @@ TEST_F(MutableS2ShapeIndexTest, ShrinkToFitOptimization) {
S2Point(1, 0.5, 0.5).Normalize(), S1Angle::Degrees(89), 100));
index_.Add(make_unique<S2Loop::Shape>(loop.get()));
QuadraticValidate();
TestEncodeDecode();
}
TEST_F(MutableS2ShapeIndexTest, LoopsSpanningThreeFaces) {
@ -274,6 +291,7 @@ TEST_F(MutableS2ShapeIndexTest, LoopsSpanningThreeFaces) {
}
QuadraticValidate();
TestIteratorMethods(index_);
TestEncodeDecode();
}
TEST_F(MutableS2ShapeIndexTest, ManyIdenticalEdges) {
@ -285,6 +303,7 @@ TEST_F(MutableS2ShapeIndexTest, ManyIdenticalEdges) {
}
QuadraticValidate();
TestIteratorMethods(index_);
TestEncodeDecode();
// Since all edges span the diagonal of a face, no subdivision should
// have occurred (with the default index options).
for (MutableS2ShapeIndex::Iterator it(&index_, S2ShapeIndex::BEGIN);
@ -301,6 +320,7 @@ TEST_F(MutableS2ShapeIndexTest, DegenerateEdge) {
shape->Add(a, a);
index_.Add(std::move(shape));
QuadraticValidate();
TestEncodeDecode();
// Check that exactly 3 index cells contain the degenerate edge.
int count = 0;
for (MutableS2ShapeIndex::Iterator it(&index_, S2ShapeIndex::BEGIN);
@ -325,6 +345,7 @@ TEST_F(MutableS2ShapeIndexTest, ManyTinyEdges) {
}
index_.Add(std::move(shape));
QuadraticValidate();
TestEncodeDecode();
// Check that there is exactly one index cell and that it is a leaf cell.
MutableS2ShapeIndex::Iterator it(&index_, S2ShapeIndex::BEGIN);
ASSERT_TRUE(!it.done());
@ -345,6 +366,7 @@ TEST_F(MutableS2ShapeIndexTest, SimpleUpdates) {
for (int id = 0; id < polygon.num_loops(); ++id) {
index_.Release(id);
QuadraticValidate();
TestEncodeDecode();
}
}
@ -390,6 +412,7 @@ TEST_F(MutableS2ShapeIndexTest, RandomUpdates) {
vector<int> added(index_.num_shape_ids());
std::iota(added.begin(), added.end(), 0);
QuadraticValidate();
TestEncodeDecode();
for (int iter = 0; iter < 100; ++iter) {
S2_VLOG(1) << "Iteration: " << iter;
// Choose some shapes to add and release.
@ -412,6 +435,7 @@ TEST_F(MutableS2ShapeIndexTest, RandomUpdates) {
}
}
QuadraticValidate();
TestEncodeDecode();
}
}

View File

@ -23,7 +23,7 @@
#include <ostream>
#include <type_traits>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include "s2/_fp_contract_off.h"
#include "s2/s2point.h"
#include "s2/util/math/mathutil.h"
@ -168,7 +168,7 @@ class S1Angle {
typedef std::true_type goog_btree_prefer_linear_node_search;
private:
explicit constexpr S1Angle(double radians) : radians_(radians) {}
explicit IFNDEF_SWIG(constexpr) S1Angle(double radians) : radians_(radians) {}
double radians_;
};

View File

@ -19,15 +19,10 @@
#include <gtest/gtest.h>
#include "s2/base/commandlineflags.h"
#include "s2/base/integral_types.h"
#include "s2/base/logging.h"
#include "s2/s2latlng.h"
#include "s2/s2testing.h"
#include "s2/third_party/absl/base/integral_types.h"
DEFINE_int32(iters,
(google::DEBUG_MODE ? 100 : 1000) * (1000 * 1000),
"Run timing tests with this many iterations");
TEST(S1Angle, DefaultConstructor) {
// Check that the default constructor returns an angle of 0.
@ -90,6 +85,7 @@ TEST(S1Angle, E6E7RepresentationsUnsigned) {
TEST(S1Angle, NormalizeCorrectlyCanonicalizesAngles) {
EXPECT_DOUBLE_EQ(0.0, S1Angle::Degrees(360.0).Normalized().degrees());
EXPECT_DOUBLE_EQ(-90.0, S1Angle::Degrees(-90.0).Normalized().degrees());
EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(-180.0).Normalized().degrees());
EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(180.0).Normalized().degrees());
EXPECT_DOUBLE_EQ(180.0, S1Angle::Degrees(540.0).Normalized().degrees());
@ -142,66 +138,6 @@ TEST(S1Angle, TestFormatting) {
EXPECT_EQ("180.0000000", ss.str());
}
TEST(S1Angle, TestPerformance) {
// Verify that the conversion to E5/E6/E7 is not much slower than the
// conversion from E5/E6/E7. (Float-to-integer conversions can be quite
// slow on some platforms.) We only check the times for E6; the times for
// E5/E7 should be similar.
// To reduce the impact of loop overhead, we do kOpsPerLoop ops per loop.
static const int kOpsPerLoop = 8;
// Time conversion from E6 to radians.
double rad_sum = 0;
const double from_e6_start = S2Testing::GetCpuTime();
for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
// We structure both loops so that all the conversions can be done in
// parallel. Otherwise on some platforms the optimizer happens to do a
// much better job of parallelizing one loop than the other.
double r0 = S1Angle::E6(i-0).radians();
double r1 = S1Angle::E6(i-1).radians();
double r2 = S1Angle::E6(i-2).radians();
double r3 = S1Angle::E6(i-3).radians();
double r4 = S1Angle::E6(i-4).radians();
double r5 = S1Angle::E6(i-5).radians();
double r6 = S1Angle::E6(i-6).radians();
double r7 = S1Angle::E6(i-7).radians();
rad_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
}
const double from_e6_time = S2Testing::GetCpuTime() - from_e6_start;
EXPECT_NE(rad_sum, 0); // Don't let the sum get optimized away.
S2_LOG(INFO) << "From E6: "
<< (FLAGS_iters / from_e6_time)
<< " values per second";
// Time conversion from radians to E6.
const double delta = (2 * M_PI) / (FLAGS_iters - 1);
double angle = -M_PI;
int64 e6_sum = 0;
const double to_e6_start = S2Testing::GetCpuTime();
for (int i = FLAGS_iters; i > 0; i -= kOpsPerLoop) {
int64 r0 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r1 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r2 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r3 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r4 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r5 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r6 = S1Angle::Radians(angle).e6(); angle += delta;
int64 r7 = S1Angle::Radians(angle).e6(); angle += delta;
e6_sum += ((r0 + r1) + (r2 + r3)) + ((r4 + r5) + (r6 + r7));
}
const double to_e6_time = S2Testing::GetCpuTime() - to_e6_start;
EXPECT_NE(e6_sum + angle, 0); // Don't let them get optimized away.
S2_LOG(INFO) << " To E6: "
<< (FLAGS_iters / to_e6_time)
<< " values per second";
// Make sure that the To/From E6 times are not much different.
// The difference factor slightly less than 2 on an x86_64.
EXPECT_LE(from_e6_time / to_e6_time, 3);
EXPECT_LE(to_e6_time / from_e6_time, 3);
}
// The current implementation guarantees exact conversions between
// Degrees() and E6() when the Degrees() argument is an integer.
TEST(S1Angle, DegreesVsE6) {

View File

@ -38,7 +38,7 @@
// Specifically, the representation of (Pi - x) radians has an error of about
// (1e-15 / x), with a maximum error of about 2e-8 radians (about 13cm on the
// Earth's surface). For comparison, for angles up to 90 degrees (10000km)
// the worst-case representation error is about 2e-16 radians (1 nanonmeter),
// the worst-case representation error is about 2e-16 radians (1 nanometer),
// which is about the same as S1Angle.
//
// This class is intended to be copied by value as desired. It uses

View File

@ -1838,7 +1838,7 @@ bool S2BooleanOperation::Impl::ProcessIncidentEdges(
bool S2BooleanOperation::Impl::HasInterior(const S2ShapeIndex& index) {
for (int s = index.num_shape_ids(); --s >= 0; ) {
S2Shape* shape = index.shape(s);
if (shape && shape->has_interior()) return true;
if (shape && shape->dimension() == 2) return true;
}
return false;
}

View File

@ -86,8 +86,7 @@ void IndexMatchingLayer::Build(const Graph& g, S2Error* error) {
actual.push_back(S2Shape::Edge(g.vertex(edge.first),
g.vertex(edge.second)));
}
for (int s = 0; s < index_.num_shape_ids(); ++s) {
S2Shape* shape = index_.shape(s);
for (S2Shape* shape : index_) {
if (shape == nullptr || shape->dimension() != dimension_) {
continue;
}
@ -293,8 +292,8 @@ TEST(S2BooleanOperation, PointSemiOpenPolyline) {
// The result does not depend on Options::polyline_loops_have_boundaries().
S2BooleanOperation::Options options;
options.set_polyline_model(PolylineModel::SEMI_OPEN);
for (int bool_value = 0; bool_value < 2; ++bool_value) {
options.set_polyline_loops_have_boundaries(static_cast<bool>(bool_value));
for (bool bool_value : {false, true}) {
options.set_polyline_loops_have_boundaries(bool_value);
auto a = "0:0 | 1:0 | 2:0 | 3:0 | 4:0 | 5:0 # #";
auto b = "# 0:0, 1:0, 2:0 | 3:0, 3:0 | 4:0, 5:0, 4:0 #";
ExpectResult(OpType::UNION, options, a, b,
@ -318,8 +317,8 @@ TEST(S2BooleanOperation, PointClosedPolyline) {
// The result does not depend on Options::polyline_loops_have_boundaries().
S2BooleanOperation::Options options;
options.set_polyline_model(PolylineModel::CLOSED);
for (int bool_value = 0; bool_value < 2; ++bool_value) {
options.set_polyline_loops_have_boundaries(static_cast<bool>(bool_value));
for (bool bool_value : {false, true}) {
options.set_polyline_loops_have_boundaries(bool_value);
auto a = "0:0 | 1:0 | 2:0 | 3:0 | 4:0 | 5:0 # #";
auto b = "# 0:0, 1:0, 2:0 | 3:0, 3:0 | 4:0, 5:0, 4:0 #";
ExpectResult(OpType::UNION, options, a, b,
@ -463,8 +462,8 @@ TEST(S2BooleanOperation, PolylineVertexSemiOpenPolylineVertex) {
// The result does not depend on Options::polyline_loops_have_boundaries().
S2BooleanOperation::Options options;
options.set_polyline_model(PolylineModel::SEMI_OPEN);
for (int bool_value = 0; bool_value < 2; ++bool_value) {
options.set_polyline_loops_have_boundaries(static_cast<bool>(bool_value));
for (bool bool_value : {false, true}) {
options.set_polyline_loops_have_boundaries(bool_value);
auto a = "# 0:0, 0:1, 0:2 | 0:3, 0:4, 0:3 #";
auto b = "# 0:0, 1:0 | -1:1, 0:1, 1:1 | -1:2, 0:2 "
"| 1:3, 0:3, 1:3 | 0:4, 1:4, 0:4 #";

View File

@ -308,8 +308,8 @@ void S2Builder::set_label(Label label) {
label_set_modified_ = true;
}
static bool IsFullPolygonUnspecified(const S2Builder::Graph& g,
S2Error* error) {
bool S2Builder::IsFullPolygonUnspecified(const S2Builder::Graph& g,
S2Error* error) {
error->Init(S2Error::BUILDER_IS_FULL_PREDICATE_NOT_SPECIFIED,
"A degenerate polygon was found, but no predicate was specified "
"to determine whether the polygon is empty or full. Call "
@ -317,10 +317,16 @@ static bool IsFullPolygonUnspecified(const S2Builder::Graph& g,
return false; // Assumes the polygon is empty.
}
S2Builder::IsFullPolygonPredicate S2Builder::IsFullPolygon(bool is_full) {
return [is_full](const S2Builder::Graph& g, S2Error* error) {
return is_full;
};
}
void S2Builder::StartLayer(unique_ptr<Layer> layer) {
layer_options_.push_back(layer->graph_options());
layer_begins_.push_back(input_edges_.size());
layer_is_full_polygon_predicates_.push_back(IsFullPolygonUnspecified);
layer_is_full_polygon_predicates_.push_back(IsFullPolygon(false));
layers_.push_back(std::move(layer));
}
@ -412,7 +418,8 @@ void S2Builder::ForceVertex(const S2Point& vertex) {
// An S2Shape used to represent the entire collection of S2Builder input edges.
// Vertices are specified as indices into a vertex vector to save space.
class VertexIdEdgeVectorShape : public S2Shape {
namespace {
class VertexIdEdgeVectorShape final : public S2Shape {
public:
// Requires that "edges" is constant for the lifetime of this object.
VertexIdEdgeVectorShape(const vector<pair<int32, int32>>& edges,
@ -424,18 +431,18 @@ class VertexIdEdgeVectorShape : public S2Shape {
const S2Point& vertex1(int e) const { return vertex(edges_[e].second); }
// S2Shape interface:
int num_edges() const final { return edges_.size(); }
Edge edge(int e) const final {
int num_edges() const override { return edges_.size(); }
Edge edge(int e) const override {
return Edge(vertices_[edges_[e].first], vertices_[edges_[e].second]);
}
int dimension() const final { return 1; }
ReferencePoint GetReferencePoint() const final {
int dimension() const override { return 1; }
ReferencePoint GetReferencePoint() const override {
return ReferencePoint::Contained(false);
}
int num_chains() const final { return edges_.size(); }
Chain chain(int i) const final { return Chain(i, 1); }
Edge chain_edge(int i, int j) const final { return edge(i); }
ChainPosition chain_position(int e) const final {
int num_chains() const override { return edges_.size(); }
Chain chain(int i) const override { return Chain(i, 1); }
Edge chain_edge(int i, int j) const override { return edge(i); }
ChainPosition chain_position(int e) const override {
return ChainPosition(e, 0);
}
@ -445,6 +452,7 @@ class VertexIdEdgeVectorShape : public S2Shape {
const vector<std::pair<int32, int32>>& edges_;
const vector<S2Point>& vertices_;
};
} // namespace
bool S2Builder::Build(S2Error* error) {
// S2_CHECK rather than S2_DCHECK because this is friendlier than crashing on the
@ -474,6 +482,7 @@ void S2Builder::Reset() {
layers_.clear();
layer_options_.clear();
layer_begins_.clear();
layer_is_full_polygon_predicates_.clear();
label_set_ids_.clear();
label_set_lexicon_.Clear();
label_set_.clear();
@ -522,8 +531,7 @@ void S2Builder::CopyInputEdges() {
sites_.push_back(site);
}
input_vertices_ = sites_;
for (int i = 0; i < input_edges_.size(); ++i) {
InputEdge& e = input_edges_[i];
for (InputEdge& e : input_edges_) {
e.first = vmap[e.first];
e.second = vmap[e.second];
}
@ -649,9 +657,8 @@ void S2Builder::ChooseInitialSites(S2PointIndex<SiteId>* site_index) {
// "0:0, 0:0" rather than the expected "0:0, 0:1", because the snap radius
// is approximately sqrt(2) degrees and therefore it is legal to snap both
// input points to "0:0". "Snap first" produces "0:0, 0:1" as expected.
vector<InputVertexKey> sorted = SortInputVertices();
for (int i = 0; i < sorted.size(); ++i) {
const S2Point& vertex = input_vertices_[sorted[i].second];
for (const InputVertexKey& key : SortInputVertices()) {
const S2Point& vertex = input_vertices_[key.second];
S2Point site = SnapSite(vertex);
// If any vertex moves when snapped, the output cannot be idempotent.
snapping_needed_ = snapping_needed_ || site != vertex;
@ -863,7 +870,7 @@ void S2Builder::AddExtraSite(const S2Point& new_site,
S2ClosestEdgeQuery query(&input_edge_index, options);
S2ClosestEdgeQuery::PointTarget target(new_site);
for (const auto& result : query.FindClosestEdges(&target)) {
InputEdgeId e = result.edge_id;
InputEdgeId e = result.edge_id();
auto* site_ids = &edge_sites_[e];
site_ids->push_back(new_site_id);
SortSitesByDistance(input_vertices_[input_edges_[e].first], site_ids);
@ -963,8 +970,8 @@ void S2Builder::SnapEdge(InputEdgeId e, vector<SiteId>* chain) const {
// Now iterate through the sites. We keep track of the sequence of sites
// that are visited.
const auto& candidates = edge_sites_[e];
for (int i = 0; i < candidates.size(); ++i) {
const S2Point& c = sites_[candidates[i]];
for (SiteId site_id : candidates) {
const S2Point& c = sites_[site_id];
// Skip any sites that are too far away. (There will be some of these,
// because we also keep track of "sites to avoid".) Note that some sites
// may be close enough to the line containing the edge, but not to the
@ -1020,14 +1027,14 @@ void S2Builder::SnapEdge(InputEdgeId e, vector<SiteId>* chain) const {
if (s2pred::EdgeCircumcenterSign(x, y, a, b, c) != xyb) break;
}
if (add_site_c) {
chain->push_back(candidates[i]);
chain->push_back(site_id);
}
}
S2_DCHECK(!chain->empty());
if (google::DEBUG_MODE) {
for (int i = 0; i < candidates.size(); ++i) {
for (SiteId site_id : candidates) {
if (s2pred::CompareDistances(y, sites_[chain->back()],
sites_[candidates[i]]) > 0) {
sites_[site_id]) > 0) {
S2_LOG(ERROR) << "Snapping invariant broken!";
}
}
@ -1329,8 +1336,7 @@ void S2Builder::MergeLayerEdges(
edges->reserve(order.size());
input_edge_ids->reserve(order.size());
edge_layers->reserve(order.size());
for (int i = 0; i < order.size(); ++i) {
const LayerEdgeId& id = order[i];
for (const LayerEdgeId& id : order) {
edges->push_back(layer_edges[id.first][id.second]);
input_edge_ids->push_back(layer_input_edge_ids[id.first][id.second]);
edge_layers->push_back(id.first);
@ -1801,8 +1807,7 @@ void S2Builder::EdgeChainSimplifier::AssignDegenerateEdges(
});
// Now determine where each degenerate edge should be assigned.
for (int i = 0; i < degenerate_ids.size(); ++i) {
InputEdgeId degenerate_id = degenerate_ids[i];
for (InputEdgeId degenerate_id : degenerate_ids) {
int layer = input_edge_layer(degenerate_id);
// Find the first output edge whose range of input edge ids starts after

View File

@ -24,7 +24,7 @@
#include <memory>
#include <utility>
#include <vector>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include "s2/third_party/absl/base/macros.h"
#include "s2/_fp_contract_off.h"
#include "s2/id_set_lexicon.h"
@ -393,14 +393,17 @@ class S2Builder {
// the empty polygon or a degenerate hole in the full polygon.
//
// To resolve this ambiguity, an IsFullPolygonPredicate may be specified for
// each input layer (see AddIsFullPolygonPredicate below). If the layer
// consists only of polygon degeneracies, the layer implementation may call
// this method to determine whether the polygon is empty or full except for
// the given degeneracies. (Note that under the semi-open boundary model,
// degeneracies do not affect point containment.)
// each output layer (see AddIsFullPolygonPredicate below). If the output
// after snapping consists only of degenerate edges and/or sibling pairs
// (including the case where there are no edges at all), then the layer
// implementation calls the given predicate to determine whether the polygon
// is empty or full except for those degeneracies. The predicate is given
// an S2Builder::Graph containing the output edges, but note that in general
// the predicate must also have knowledge of the input geometry in order to
// determine the correct result.
//
// This predicate is only required for layers that will be assembled into
// polygons. It is not used by other layer types.
// This predicate is only needed by layers that are assembled into polygons.
// It is not used by other layer types.
using IsFullPolygonPredicate =
std::function<bool (const Graph& g, S2Error* error)>;
@ -469,18 +472,42 @@ class S2Builder {
// Adds the edges of the given shape to the current layer.
void AddShape(const S2Shape& shape);
// For layers that will be assembled into polygons, this method specifies a
// predicate that will be called to determine whether the polygon is empty
// or full except for the given degeneracies. (See IsFullPolygonPredicate
// above.)
// For layers that are assembled into polygons, this method specifies a
// predicate that is called when the output consists entirely of degenerate
// edges and/or sibling pairs. The predicate is given an S2Builder::Graph
// containing the output edges (if any) and is responsible for deciding
// whether this graph represents the empty polygon (possibly with degenerate
// shells) or the full polygon (possibly with degenerate holes). Note that
// this cannot be determined from the output edges alone; it also requires
// knowledge of the input geometry. (Also see IsFullPolygonPredicate above.)
//
// This method should be called at most once per layer; additional calls
// simply overwrite the previous value for the current layer.
//
// The default implementation sets an appropriate error and returns false
// (i.e., degenerate polygons are assumed to be empty).
// The default predicate simply returns false (i.e., degenerate polygons are
// assumed to be empty). Arguably it would better to return an error in
// this case, but the fact is that relatively few clients need to be able to
// construct full polygons, and it is unreasonable to expect all such
// clients to supply an appropriate predicate.
//
// The reason for having a predicate rather than a boolean value is that the
// predicate is responsible for determining whether the output polygon is
// empty or full. In general the input geometry is not degenerate, but
// rather collapses into a degenerate configuration due to snapping and/or
// simplification.
//
// TODO(ericv): Provide standard predicates to handle common cases,
// e.g. valid input geometry that becomes degenerate due to snapping.
void AddIsFullPolygonPredicate(IsFullPolygonPredicate predicate);
// A predicate that returns an error indicating that no polygon predicate
// has been specified.
static bool IsFullPolygonUnspecified(const S2Builder::Graph& g,
S2Error* error);
// Returns a predicate that returns a constant value (true or false);
static IsFullPolygonPredicate IsFullPolygon(bool is_full);
// Forces a vertex to be located at the given position. This can be used to
// prevent certain input vertices from moving. However if you are trying to
// preserve part of the input boundary, be aware that this option does not
@ -779,6 +806,8 @@ class S2Builder::GraphOptions {
// be used. (Note that some values of the sibling_pairs() option
// automatically take care of this issue by removing half of the edges and
// changing edge_type() to DIRECTED.)
//
// DEFAULT: EdgeType::DIRECTED
EdgeType edge_type() const;
void set_edge_type(EdgeType edge_type);
@ -800,6 +829,8 @@ class S2Builder::GraphOptions {
// redundant edges when simplifying geometry (e.g., a polyline of the
// form AABBBBBCCCCCCDDDD). DegenerateEdges::KEEP is mainly useful
// for algorithms that require an output edge for every input edge.
//
// DEFAULT: DegenerateEdges::KEEP
enum class DegenerateEdges { DISCARD, DISCARD_EXCESS, KEEP };
DegenerateEdges degenerate_edges() const;
void set_degenerate_edges(DegenerateEdges degenerate_edges);
@ -809,6 +840,8 @@ class S2Builder::GraphOptions {
// be created when vertices are snapped together. When several edges are
// merged, the result is a single edge labelled with all of the original
// input edge ids.
//
// DEFAULT: DuplicateEdges::KEEP
enum class DuplicateEdges { MERGE, KEEP };
DuplicateEdges duplicate_edges() const;
void set_duplicate_edges(DuplicateEdges duplicate_edges);
@ -870,6 +903,8 @@ class S2Builder::GraphOptions {
// arbitrarily, instead we merge the labels of all duplicate edges (even
// ones where no sibling pairs were discarded), yielding {AB12, CD45, CD45}
// (assuming that duplicate edges are being kept).
//
// DEFAULT: SiblingPairs::KEEP
enum class SiblingPairs { DISCARD, DISCARD_EXCESS, KEEP, REQUIRE, CREATE };
SiblingPairs sibling_pairs() const;
void set_sibling_pairs(SiblingPairs sibling_pairs);

View File

@ -663,8 +663,7 @@ vector<Graph::EdgePolyline> Graph::PolylineBuilder::BuildPaths() {
// is labeled with an input edge id.)
vector<EdgePolyline> polylines;
vector<EdgeId> edges = g_.GetInputEdgeOrder(min_input_ids_);
for (int i = 0; i < edges.size(); ++i) {
EdgeId e = edges[i];
for (EdgeId e : edges) {
if (!used_[e] && !is_interior(g_.edge(e).first)) {
polylines.push_back(BuildPath(e));
}
@ -675,8 +674,8 @@ vector<Graph::EdgePolyline> Graph::PolylineBuilder::BuildPaths() {
// direction of undirected loops. Even so, we still need to canonicalize
// the edge order to ensure that when an input edge is split into an edge
// chain, the loop does not start in the middle of such a chain.
for (int i = 0; i < edges.size() && edges_left_ > 0; ++i) {
EdgeId e = edges[i];
for (EdgeId e : edges) {
if (edges_left_ == 0) break;
if (used_[e]) continue;
EdgePolyline polyline = BuildPath(e);
CanonicalizeLoopOrder(min_input_ids_, &polyline);
@ -725,8 +724,7 @@ vector<Graph::EdgePolyline> Graph::PolylineBuilder::BuildWalks() {
// case where multiple input polylines share vertices or edges.
vector<EdgePolyline> polylines;
vector<EdgeId> edges = g_.GetInputEdgeOrder(min_input_ids_);
for (int i = 0; i < edges.size(); ++i) {
EdgeId e = edges[i];
for (EdgeId e : edges) {
if (used_[e]) continue;
VertexId v = g_.edge(e).first;
int excess = excess_degree(v);

View File

@ -20,9 +20,10 @@
#include <array>
#include <cstddef>
#include <iterator>
#include <utility>
#include <vector>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include "s2/id_set_lexicon.h"
#include "s2/s2builder.h"
#include "s2/s2error.h"
@ -108,9 +109,7 @@ class S2Builder::Graph {
const IdSetLexicon* input_edge_id_set_lexicon,
const std::vector<LabelSetId>* label_set_ids,
const IdSetLexicon* label_set_lexicon,
// TODO(ericv/hagzonal): Fix st_lib and remove default parameter.
IsFullPolygonPredicate is_full_polygon_predicate =
IsFullPolygonPredicate());
IsFullPolygonPredicate is_full_polygon_predicate);
const GraphOptions& options() const;
@ -179,7 +178,8 @@ class S2Builder::Graph {
// A helper class for VertexOutMap that represents the outgoing edge *ids*
// from a given vertex.
class VertexOutEdgeIds {
class VertexOutEdgeIds
: public std::iterator<std::forward_iterator_tag, EdgeId> {
public:
// An iterator over a range of edge ids (like boost::counting_iterator).
class Iterator {

View File

@ -32,7 +32,6 @@ using Graph = S2Builder::Graph;
using GraphOptions = S2Builder::GraphOptions;
using DegenerateEdges = GraphOptions::DegenerateEdges;
using DuplicateEdges = GraphOptions::DuplicateEdges;
using SiblingPairs = GraphOptions::SiblingPairs;
using Edge = Graph::Edge;
@ -59,8 +58,10 @@ ClosedSetNormalizer::ClosedSetNormalizer(
S2_DCHECK(graph_options_out_[1].sibling_pairs() != SiblingPairs::REQUIRE);
// Set the GraphOptions for the input graphs to ensure that (1) they share a
// common set of vertices, and (2) to ensure that redundant degeneracies are
// reduced to a single copy each.
// common set of vertices, (2) degenerate edges are kept only if they are
// isolated, and (3) multiple copies of siblings pairs are discarded. (Note
// that there may be multiple copies of isolated degenerate edges; clients
// can eliminate them if desired using DuplicateEdges::MERGE.)
for (int dim = 0; dim < 3; ++dim) {
graph_options_in_[dim].set_allow_vertex_filtering(false);
}

View File

@ -113,8 +113,7 @@ void NormalizeTest::AddLayers(
for (int dim = 0; dim < 3; ++dim) {
builder->StartLayer(make_unique<GraphAppendingLayer>(
graph_options[dim], graphs_out, &graph_clones_));
for (int s = 0; s < index->num_shape_ids(); ++s) {
S2Shape* shape = index->shape(s);
for (S2Shape* shape : *index) {
if (shape->dimension() != dim) continue;
int n = shape->num_edges();
for (int e = 0; e < n; ++e) {
@ -209,12 +208,12 @@ TEST_F(NormalizeTest, DuplicateEdgeMerging) {
// Verify that when DuplicateEdges::KEEP is specified, demoted edges are
// added as new edges rather than being merged with existing ones.
// (Note that NormalizeTest specifies DuplicateEdges::KEEP by default.)
Run("0:0 # 0:0, 0:0 | 0:1, 0:2 # 0:0; 0:1, 0:2",
"0:0 | 0:0 | 0:0 # 0:1, 0:2 | 0:1, 0:2 #");
Run("0:0 | 0:0 # 0:0, 0:0 | 0:1, 0:2 # 0:0; 0:1, 0:2",
"0:0 | 0:0 | 0:0 | 0:0 # 0:1, 0:2 | 0:1, 0:2 #");
// Now verify that the duplicate edges are merged if requested.
graph_options_out_[0].set_duplicate_edges(DuplicateEdges::MERGE);
graph_options_out_[1].set_duplicate_edges(DuplicateEdges::MERGE);
Run("0:0 # 0:0, 0:0 | 0:1, 0:2 # 0:0; 0:1, 0:2",
Run("0:0 | 0:0 # 0:0, 0:0 | 0:1, 0:2 # 0:0; 0:1, 0:2",
"0:0 # 0:1, 0:2 #");
}

View File

@ -311,22 +311,22 @@ void DegeneracyFinder::ComputeUnknownSignsBruteForce(
}
// An S2Shape representing the edges in an S2Builder::Graph.
class GraphShape : public S2Shape {
class GraphShape final : public S2Shape {
public:
explicit GraphShape(const Graph* g) : g_(*g) {}
int num_edges() const final { return g_.num_edges(); }
Edge edge(int e) const final {
int num_edges() const override { return g_.num_edges(); }
Edge edge(int e) const override {
Graph::Edge g_edge = g_.edge(e);
return Edge(g_.vertex(g_edge.first), g_.vertex(g_edge.second));
}
int dimension() const final { return 1; }
ReferencePoint GetReferencePoint() const final {
int dimension() const override { return 1; }
ReferencePoint GetReferencePoint() const override {
return ReferencePoint::Contained(false);
}
int num_chains() const final { return g_.num_edges(); }
Chain chain(int i) const final { return Chain(i, 1); }
Edge chain_edge(int i, int j) const final { return edge(i); }
ChainPosition chain_position(int e) const final {
int num_chains() const override { return g_.num_edges(); }
Chain chain(int i) const override { return Chain(i, 1); }
Edge chain_edge(int i, int j) const override { return edge(i); }
ChainPosition chain_position(int e) const override {
return ChainPosition(e, 0);
}

View File

@ -20,7 +20,7 @@
#include <vector>
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include "s2/s2builder_graph.h"
#include "s2/s2error.h"
@ -61,13 +61,13 @@ struct PolygonDegeneracy {
// REQUIRES: graph.options().sibling_pairs() == DISCARD_EXCESS (or DISCARD)
// REQUIRES: graph.options().degenerate_edges() == DISCARD_EXCESS (or DISCARD)
//
// Usually callers will want to specify SiblingPairs::DISCARD_EXCESS,
// DegenerateEdges::DISCARD_EXCESS, and DuplicateEdges::MERGE in order to
// remove all redundant degeneracies. DISCARD is also allowed for the first
// two options in case you want to keep only one type of degeneracy (i.e.,
// degenerate edges or sibling pairs).
// Usually callers will want to specify SiblingPairs::DISCARD_EXCESS and
// DegenerateEdges::DISCARD_EXCESS in order to remove all redundant
// degeneracies. DISCARD is also allowed in case you want to keep only one
// type of degeneracy (i.e., degenerate edges or sibling pairs).
//
// If the graph edges cannot be assembled into loops, the result is undefined.
// (An error may or may not be returned.)
std::vector<PolygonDegeneracy> FindPolygonDegeneracies(
const S2Builder::Graph& graph, S2Error* error);

View File

@ -20,7 +20,7 @@
#include <memory>
#include <string>
#include "s2/base/casts.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include <gtest/gtest.h>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/mutable_s2shape_index.h"

View File

@ -141,7 +141,14 @@ void S2PolygonLayer::Build(const Graph& g, S2Error* error) {
// not the loop contained S2::Origin(). By comparing this with the final
// S2Polygon loops we can fix up the edge labels appropriately.
LoopMap loop_map;
if (g.options().edge_type() == EdgeType::DIRECTED) {
if (g.num_edges() == 0) {
// The polygon is either full or empty.
if (g.IsFullPolygon(error)) {
polygon_->Init(make_unique<S2Loop>(S2Loop::kFull()));
} else {
polygon_->InitNested(vector<unique_ptr<S2Loop>>{});
}
} else if (g.options().edge_type() == EdgeType::DIRECTED) {
vector<Graph::EdgeLoop> edge_loops;
if (!g.GetDirectedLoops(LoopType::SIMPLE, &edge_loops, error)) {
return;

View File

@ -66,11 +66,10 @@ namespace s2builderutil {
// polygon and is not modified, then the output has the same cyclic ordering
// of loop vertices and the same loop ordering as the input polygon.
//
// CAVEAT: Because polygons are constructed from their boundaries, this method
// cannot distinguish between the empty and full polygons. An empty boundary
// always yields an empty polygon. If the result should sometimes be the full
// polygon, such logic must be implemented outside of this class (and will
// need to consider factors other than the polygon's boundary).
// If the polygon has no edges, then the graph's IsFullPolygonPredicate is
// called to determine whether the output polygon should be empty (containing
// no points) or full (containing all points). This predicate can be
// specified as part of the S2Builder input geometry.
class S2PolygonLayer : public S2Builder::Layer {
public:
class Options {

View File

@ -23,7 +23,7 @@
#include <set>
#include <string>
#include "s2/base/casts.h"
#include "s2/third_party/absl/base/integral_types.h"
#include "s2/base/integral_types.h"
#include <gtest/gtest.h>
#include "s2/third_party/absl/memory/memory.h"
#include "s2/mutable_s2shape_index.h"
@ -50,9 +50,12 @@ void TestS2Polygon(const vector<const char*>& input_strs,
S2Polygon output;
builder.StartLayer(make_unique<S2PolygonLayer>(
&output, S2PolygonLayer::Options(edge_type)));
bool is_full = false;
for (auto input_str : input_strs) {
builder.AddPolygon(*s2textformat::MakeVerbatimPolygon(input_str));
if (string(input_str) == "full") is_full = true;
builder.AddPolygon(*s2textformat::MakeVerbatimPolygonOrDie(input_str));
}
builder.AddIsFullPolygonPredicate(S2Builder::IsFullPolygon(is_full));
S2Error error;
ASSERT_TRUE(builder.Build(&error));
// The input strings in tests may not be in normalized form, so we build an
@ -62,6 +65,16 @@ void TestS2Polygon(const vector<const char*>& input_strs,
s2textformat::ToString(output));
}
void TestS2Polygon(const vector<const char*>& input_strs,
const char* expected_str) {
TestS2Polygon(input_strs, expected_str, EdgeType::DIRECTED);
TestS2Polygon(input_strs, expected_str, EdgeType::UNDIRECTED);
}
void TestS2PolygonUnchanged(const char* input_str) {
TestS2Polygon(vector<const char*>{input_str}, input_str);
}
// Unlike the methods above, the input consists of a set of *polylines*.
void TestS2PolygonError(const vector<const char*>& input_strs,
S2Error::Code expected_error, EdgeType edge_type) {
@ -85,20 +98,14 @@ void TestS2PolygonError(const vector<const char*>& input_strs,
TestS2PolygonError(input_strs, expected_error, EdgeType::UNDIRECTED);
}
void TestS2Polygon(const vector<const char*>& input_strs,
const char* expected_str) {
TestS2Polygon(input_strs, expected_str, EdgeType::DIRECTED);
TestS2Polygon(input_strs, expected_str, EdgeType::UNDIRECTED);
}
void TestS2PolygonUnchanged(const char* input_str) {
TestS2Polygon(vector<const char*>{input_str}, input_str);
}
TEST(S2PolygonLayer, NoLoops) {
TEST(S2PolygonLayer, Empty) {
TestS2PolygonUnchanged("");
}
TEST(S2PolygonLayer, Full) {
TestS2PolygonUnchanged("full");
}
TEST(S2PolygonLayer, SmallLoop) {
TestS2PolygonUnchanged("0:0, 0:1, 1:1");
}
@ -122,7 +129,8 @@ TEST(S2PolygonLayer, InvalidPolygon) {
TEST(S2PolygonLayer, DuplicateInputEdges) {
// Check that S2PolygonLayer can assemble polygons even when there are
// duplicate edges (after sibling pairs are removed).
// duplicate edges (after sibling pairs are removed), and then report the
// duplicate edges as an error.
S2Builder builder{S2Builder::Options()};
S2Polygon output;
S2PolygonLayer::Options options;
@ -292,12 +300,10 @@ TEST(IndexedS2PolygonLayer, AddsShape) {
EXPECT_EQ(polygon_str, s2textformat::ToString(*polygon));
}
TEST(IndexedS2PolygonLayer, AddsEmptyShape) {
TEST(IndexedS2PolygonLayer, IgnoresEmptyShape) {
S2Builder builder{S2Builder::Options()};
MutableS2ShapeIndex index;
builder.StartLayer(make_unique<IndexedS2PolygonLayer>(&index));
S2Polygon polygon;
builder.AddPolygon(polygon);
S2Error error;
ASSERT_TRUE(builder.Build(&error));
EXPECT_EQ(0, index.num_shape_ids());

Some files were not shown because too many files have changed in this diff Show More