From a50a2e720aa03101d4ae0abe15c67ddbc47fdb47 Mon Sep 17 00:00:00 2001 From: Dewey Dunnington Date: Sat, 1 Jun 2024 23:09:37 -0300 Subject: [PATCH] start to update --- data-raw/update-s2.R | 11 +- src/Makevars.in | 34 +- src/Makevars.win | 16 +- src/RcppExports.cpp | 12 +- src/s2/_fp_contract_off.h | 11 +- src/s2/base/casts.h | 111 +- src/s2/base/commandlineflags.h | 36 +- .../commandlineflags_declare.h} | 23 +- src/s2/base/integral_types.h | 2 + src/s2/base/log_severity.h | 8 - src/s2/base/logging.h | 134 +- src/s2/base/port.h | 939 +------ src/s2/base/stringprintf.cc | 107 - src/s2/base/stringprintf.h | 53 - src/s2/base/strtoint.cc | 65 - src/s2/base/strtoint.h | 106 - src/s2/encoded_s2cell_id_vector.cc | 29 +- src/s2/encoded_s2cell_id_vector.h | 10 +- src/s2/encoded_s2point_vector.cc | 132 +- src/s2/encoded_s2point_vector.h | 54 +- src/s2/encoded_s2shape_index.cc | 95 +- src/s2/encoded_s2shape_index.h | 143 +- src/s2/encoded_string_vector.cc | 33 +- src/s2/encoded_string_vector.h | 10 +- src/s2/encoded_uint_vector.h | 32 +- src/s2/id_set_lexicon.cc | 18 +- src/s2/id_set_lexicon.h | 11 +- src/s2/mutable_s2shape_index.cc | 1207 +++++--- src/s2/mutable_s2shape_index.h | 297 +- src/s2/r1interval.h | 17 +- src/s2/r2rect.cc | 8 +- src/s2/r2rect.h | 15 +- src/s2/s1angle.cc | 6 +- src/s2/s1angle.h | 63 +- src/s2/s1chord_angle.cc | 23 +- src/s2/s1chord_angle.h | 96 +- src/s2/s1interval.cc | 2 - src/s2/s1interval.h | 8 + src/s2/s2boolean_operation.cc | 910 ++++-- src/s2/s2boolean_operation.h | 135 +- src/s2/s2buffer_operation.cc | 770 +++++ src/s2/s2buffer_operation.h | 359 +++ src/s2/s2builder.cc | 876 ++++-- src/s2/s2builder.h | 422 ++- src/s2/s2builder_graph.cc | 149 +- src/s2/s2builder_graph.h | 120 +- src/s2/s2builder_layer.h | 4 +- src/s2/s2builderutil_closed_set_normalizer.cc | 19 +- src/s2/s2builderutil_closed_set_normalizer.h | 10 +- ...s2builderutil_find_polygon_degeneracies.cc | 19 +- .../s2builderutil_find_polygon_degeneracies.h | 1 + ...s2builderutil_get_snapped_winding_delta.cc | 438 +++ .../s2builderutil_get_snapped_winding_delta.h | 165 ++ src/s2/s2builderutil_graph_shape.h | 2 + src/s2/s2builderutil_lax_polygon_layer.cc | 20 +- src/s2/s2builderutil_lax_polygon_layer.h | 13 +- src/s2/s2builderutil_lax_polyline_layer.cc | 104 + src/s2/s2builderutil_lax_polyline_layer.h | 154 + src/s2/s2builderutil_s2point_vector_layer.cc | 11 +- src/s2/s2builderutil_s2point_vector_layer.h | 7 +- src/s2/s2builderutil_s2polygon_layer.cc | 16 +- src/s2/s2builderutil_s2polygon_layer.h | 5 +- src/s2/s2builderutil_s2polyline_layer.cc | 9 + src/s2/s2builderutil_s2polyline_layer.h | 16 +- .../s2builderutil_s2polyline_vector_layer.cc | 10 + .../s2builderutil_s2polyline_vector_layer.h | 29 +- src/s2/s2builderutil_snap_functions.cc | 17 +- src/s2/s2builderutil_snap_functions.h | 10 +- src/s2/s2builderutil_testing.cc | 77 +- src/s2/s2builderutil_testing.h | 47 +- src/s2/s2cap.cc | 23 +- src/s2/s2cap.h | 21 +- src/s2/s2cell.cc | 26 +- src/s2/s2cell.h | 89 +- src/s2/s2cell_id.cc | 114 +- src/s2/s2cell_id.h | 108 +- src/s2/s2cell_index.cc | 29 +- src/s2/s2cell_index.h | 41 +- src/s2/s2cell_iterator.h | 203 ++ src/s2/s2cell_iterator_join.h | 441 +++ src/s2/s2cell_iterator_testing.h | 119 + src/s2/s2cell_range_iterator.h | 262 ++ src/s2/s2cell_union.cc | 140 +- src/s2/s2cell_union.h | 120 +- src/s2/s2centroids.cc | 5 +- src/s2/s2closest_cell_query.cc | 10 +- src/s2/s2closest_cell_query.h | 5 + src/s2/s2closest_cell_query_base.h | 35 +- src/s2/s2closest_edge_query.cc | 10 +- src/s2/s2closest_edge_query.h | 70 +- src/s2/s2closest_edge_query_base.h | 39 +- src/s2/s2closest_edge_query_testing.h | 13 +- src/s2/s2closest_point_query.cc | 4 + src/s2/s2closest_point_query.h | 3 + src/s2/s2closest_point_query_base.h | 25 +- src/s2/s2coder.h | 159 ++ src/s2/s2coder_testing.h | 53 + src/s2/s2contains_point_query.h | 38 +- src/s2/s2contains_vertex_query.cc | 16 +- src/s2/s2convex_hull_query.cc | 52 +- src/s2/s2convex_hull_query.h | 1 + src/s2/s2coords.cc | 2 + src/s2/s2coords.h | 10 +- src/s2/s2coords_internal.h | 3 +- src/s2/s2crossing_edge_query.cc | 21 +- src/s2/s2crossing_edge_query.h | 6 +- src/s2/s2debug.cc | 7 +- src/s2/s2debug.h | 3 +- src/s2/s2distance_target.h | 8 +- src/s2/s2earth.cc | 7 +- src/s2/s2earth.h | 248 +- src/s2/s2edge_clipping.cc | 21 +- src/s2/s2edge_clipping.h | 90 +- src/s2/s2edge_crosser.cc | 44 +- src/s2/s2edge_crosser.h | 355 ++- src/s2/s2edge_crossings.cc | 481 +++- src/s2/s2edge_crossings.h | 114 +- src/s2/s2edge_crossings_internal.h | 70 +- src/s2/s2edge_distances.cc | 223 +- src/s2/s2edge_distances.h | 146 +- src/s2/s2edge_tessellator.cc | 16 +- src/s2/s2edge_tessellator.h | 2 + src/s2/s2edge_vector_shape.h | 8 +- src/s2/s2error.cc | 142 +- src/s2/s2error.h | 59 +- src/s2/s2furthest_edge_query.cc | 7 +- src/s2/s2furthest_edge_query.h | 72 +- src/s2/s2hausdorff_distance_query.cc | 124 + src/s2/s2hausdorff_distance_query.h | 210 ++ src/s2/s2latlng.cc | 38 +- src/s2/s2latlng.h | 112 +- src/s2/s2latlng_rect.cc | 25 +- src/s2/s2latlng_rect.h | 8 +- src/s2/s2latlng_rect_bounder.cc | 22 +- src/s2/s2lax_loop_shape.cc | 55 +- src/s2/s2lax_loop_shape.h | 20 +- src/s2/s2lax_polygon_shape.cc | 290 +- src/s2/s2lax_polygon_shape.h | 187 +- src/s2/s2lax_polyline_shape.cc | 51 +- src/s2/s2lax_polyline_shape.h | 49 +- src/s2/s2loop.cc | 337 +-- src/s2/s2loop.h | 87 +- src/s2/s2loop_measures.cc | 78 +- src/s2/s2loop_measures.h | 173 +- src/s2/s2max_distance_targets.cc | 28 +- src/s2/s2max_distance_targets.h | 2 + src/s2/s2measures.cc | 42 +- src/s2/s2memory_tracker.cc | 44 + src/s2/s2memory_tracker.h | 405 +++ src/s2/s2metrics.cc | 38 +- src/s2/s2metrics.h | 32 +- src/s2/s2min_distance_targets.cc | 28 +- src/s2/s2min_distance_targets.h | 3 + src/s2/s2padded_cell.cc | 4 + src/s2/s2padded_cell.h | 2 + src/s2/s2point.h | 144 +- src/s2/s2point_compression.cc | 26 +- src/s2/s2point_compression.h | 2 + src/s2/s2point_index.h | 72 +- src/s2/s2point_region.cc | 5 +- src/s2/s2point_region.h | 5 +- src/s2/s2point_span.h | 2 + src/s2/s2point_vector_shape.h | 45 +- src/s2/s2pointutil.cc | 40 +- src/s2/s2pointutil.h | 66 +- src/s2/s2polygon.cc | 488 ++-- src/s2/s2polygon.h | 207 +- src/s2/s2polyline.cc | 286 +- src/s2/s2polyline.h | 127 +- src/s2/s2polyline_alignment.cc | 58 +- src/s2/s2polyline_alignment.h | 33 +- src/s2/s2polyline_alignment_internal.h | 7 +- src/s2/s2polyline_measures.cc | 10 +- src/s2/s2polyline_simplifier.cc | 84 +- src/s2/s2polyline_simplifier.h | 63 +- src/s2/s2predicates.cc | 745 ++++- src/s2/s2predicates.h | 167 +- src/s2/s2predicates_internal.h | 42 + src/s2/s2projections.cc | 4 + src/s2/s2projections.h | 4 +- src/s2/s2r2rect.cc | 6 +- src/s2/s2r2rect.h | 8 + src/s2/s2region.cc | 4 +- src/s2/s2region.h | 19 +- src/s2/s2region_coverer.cc | 38 +- src/s2/s2region_coverer.h | 15 +- src/s2/s2region_intersection.cc | 15 +- src/s2/s2region_intersection.h | 4 +- src/s2/s2region_term_indexer.cc | 54 +- src/s2/s2region_term_indexer.h | 25 +- src/s2/s2region_union.cc | 17 +- src/s2/s2region_union.h | 24 +- src/s2/s2shape.h | 434 ++- src/s2/s2shape_index.cc | 11 + src/s2/s2shape_index.h | 234 +- src/s2/s2shape_index_buffered_region.cc | 16 +- src/s2/s2shape_index_buffered_region.h | 5 + src/s2/s2shape_index_measures.cc | 6 + src/s2/s2shape_index_region.h | 121 +- src/s2/s2shape_measures.cc | 22 +- src/s2/s2shape_nesting_query.cc | 264 ++ src/s2/s2shape_nesting_query.h | 192 ++ .../s2shapeutil_build_polygon_boundaries.cc | 16 +- src/s2/s2shapeutil_build_polygon_boundaries.h | 1 + src/s2/s2shapeutil_coding.cc | 136 +- src/s2/s2shapeutil_coding.h | 46 +- src/s2/s2shapeutil_contains_brute_force.cc | 3 +- src/s2/s2shapeutil_contains_brute_force.h | 1 + src/s2/s2shapeutil_conversion.cc | 72 + src/s2/s2shapeutil_conversion.h | 51 + src/s2/s2shapeutil_count_edges.h | 12 +- src/s2/s2shapeutil_edge_iterator.cc | 8 +- src/s2/s2shapeutil_edge_iterator.h | 4 + src/s2/s2shapeutil_get_reference_point.cc | 6 +- src/s2/s2shapeutil_get_reference_point.h | 1 + src/s2/s2shapeutil_range_iterator.cc | 58 - src/s2/s2shapeutil_range_iterator.h | 65 - src/s2/s2shapeutil_shape_edge.h | 10 +- src/s2/s2shapeutil_shape_edge_id.h | 21 +- src/s2/s2shapeutil_testing.cc | 109 + .../s2shapeutil_visit_crossing_edge_pairs.cc | 60 +- .../s2shapeutil_visit_crossing_edge_pairs.h | 4 +- src/s2/s2testing.cc | 101 +- src/s2/s2testing.h | 34 +- src/s2/s2text_format.cc | 205 +- src/s2/s2text_format.h | 114 +- src/s2/s2wedge_relations.cc | 1 + src/s2/s2winding_operation.cc | 561 ++++ src/s2/s2winding_operation.h | 229 ++ src/s2/s2wrapped_shape.h | 53 + src/s2/sequence_lexicon.h | 6 + src/s2/strings/ostringstream.cc | 35 - src/s2/strings/ostringstream.h | 105 - src/s2/strings/serialize.cc | 46 - src/s2/strings/serialize.h | 40 - src/s2/{base/mutex.h => testing/gtest_prod.h} | 14 +- src/s2/thread_testing.cc | 93 + src/s2/thread_testing.h | 65 + src/s2/util/bitmap/bitmap.h | 1137 ++++++++ src/s2/util/bits/bit-interleave.cc | 225 +- src/s2/util/bits/bit-interleave.h | 3 +- src/s2/util/bits/bits.cc | 155 -- src/s2/util/bits/bits.h | 745 +---- src/s2/util/coding/coder.cc | 67 +- src/s2/util/coding/coder.h | 244 +- src/s2/util/coding/varint.cc | 241 +- src/s2/util/coding/varint.h | 131 +- src/s2/util/endian/endian.h | 176 +- src/s2/util/gtl/btree.h | 2471 ----------------- src/s2/util/gtl/btree_container.h | 411 --- src/s2/util/gtl/btree_map.h | 79 - src/s2/util/gtl/btree_set.h | 73 - src/s2/util/gtl/compact_array.h | 48 +- src/s2/util/gtl/container_logging.h | 26 +- src/s2/util/gtl/dense_hash_set.h | 1 + src/s2/util/gtl/densehashtable.h | 75 +- src/s2/util/gtl/hashtable_common.h | 1 + src/s2/util/hash/mix.h | 3 + src/s2/util/math/exactfloat/exactfloat.cc | 22 +- src/s2/util/math/exactfloat/exactfloat.h | 48 +- src/s2/util/math/mathutil.cc | 11 - src/s2/util/math/mathutil.h | 62 + src/s2/util/math/matrix3x3.h | 215 +- src/s2/util/math/vector.h | 299 +- src/s2/util/math/vector3_hash.h | 54 - src/s2/util/units/physical-units.h | 78 +- src/s2/value_lexicon.h | 3 + 267 files changed, 18689 insertions(+), 10921 deletions(-) rename src/s2/{util/gtl/layout.h => base/commandlineflags_declare.h} (53%) delete mode 100644 src/s2/base/stringprintf.cc delete mode 100644 src/s2/base/stringprintf.h delete mode 100644 src/s2/base/strtoint.cc delete mode 100644 src/s2/base/strtoint.h create mode 100644 src/s2/s2buffer_operation.cc create mode 100644 src/s2/s2buffer_operation.h create mode 100644 src/s2/s2builderutil_get_snapped_winding_delta.cc create mode 100644 src/s2/s2builderutil_get_snapped_winding_delta.h create mode 100644 src/s2/s2builderutil_lax_polyline_layer.cc create mode 100644 src/s2/s2builderutil_lax_polyline_layer.h create mode 100644 src/s2/s2cell_iterator.h create mode 100644 src/s2/s2cell_iterator_join.h create mode 100644 src/s2/s2cell_iterator_testing.h create mode 100644 src/s2/s2cell_range_iterator.h create mode 100644 src/s2/s2coder.h create mode 100644 src/s2/s2coder_testing.h create mode 100644 src/s2/s2hausdorff_distance_query.cc create mode 100644 src/s2/s2hausdorff_distance_query.h create mode 100644 src/s2/s2memory_tracker.cc create mode 100644 src/s2/s2memory_tracker.h create mode 100644 src/s2/s2shape_nesting_query.cc create mode 100644 src/s2/s2shape_nesting_query.h create mode 100644 src/s2/s2shapeutil_conversion.cc create mode 100644 src/s2/s2shapeutil_conversion.h delete mode 100644 src/s2/s2shapeutil_range_iterator.cc delete mode 100644 src/s2/s2shapeutil_range_iterator.h create mode 100644 src/s2/s2shapeutil_testing.cc create mode 100644 src/s2/s2winding_operation.cc create mode 100644 src/s2/s2winding_operation.h create mode 100644 src/s2/s2wrapped_shape.h delete mode 100644 src/s2/strings/ostringstream.cc delete mode 100644 src/s2/strings/ostringstream.h delete mode 100644 src/s2/strings/serialize.cc delete mode 100644 src/s2/strings/serialize.h rename src/s2/{base/mutex.h => testing/gtest_prod.h} (66%) create mode 100644 src/s2/thread_testing.cc create mode 100644 src/s2/thread_testing.h create mode 100644 src/s2/util/bitmap/bitmap.h delete mode 100644 src/s2/util/bits/bits.cc delete mode 100644 src/s2/util/gtl/btree.h delete mode 100644 src/s2/util/gtl/btree_container.h delete mode 100644 src/s2/util/gtl/btree_map.h delete mode 100644 src/s2/util/gtl/btree_set.h delete mode 100644 src/s2/util/math/vector3_hash.h diff --git a/data-raw/update-s2.R b/data-raw/update-s2.R index 14f66d42..6e302254 100644 --- a/data-raw/update-s2.R +++ b/data-raw/update-s2.R @@ -2,7 +2,7 @@ library(tidyverse) # download S2 -source_url <- "https://github.com/google/s2geometry/archive/v0.9.0.zip" +source_url <- "https://github.com/google/s2geometry/archive/v0.11.1.zip" curl::curl_download(source_url, "data-raw/s2-source.tar.gz") unzip("data-raw/s2-source.tar.gz", exdir = "data-raw") @@ -11,15 +11,13 @@ s2_dir <- list.files("data-raw", "^s2geometry-[0-9.]+", include.dirs = TRUE, ful stopifnot(dir.exists(s2_dir), length(s2_dir) == 1) src_dir <- file.path(s2_dir, "src/s2") -# headers live in inst/include -# keeping the directory structure means that -# we don't have to update any source files (beause of header locations) +# Process headers headers <- tibble( path = list.files(file.path(s2_dir, "src", "s2"), "\\.(h|inc)$", full.names = TRUE, recursive = TRUE), - final_path = str_replace(path, ".*?s2/", "inst/include/s2/") + final_path = str_replace(path, ".*?s2/", "src/s2/") ) -# Put S2 compilation units in src/s2/... +# Process compilation units source_files <- tibble( path = list.files(file.path(s2_dir, "src", "s2"), "\\.cc$", full.names = TRUE, recursive = TRUE), final_path = str_replace(path, ".*?src/", "src/") %>% @@ -29,7 +27,6 @@ source_files <- tibble( # clean current headers and source files unlink("src/s2", recursive = TRUE) -unlink("inst/include/s2", recursive = TRUE) # create destination dirs dest_dirs <- c( diff --git a/src/Makevars.in b/src/Makevars.in index 2bc496ee..2be2c92c 100644 --- a/src/Makevars.in +++ b/src/Makevars.in @@ -111,16 +111,6 @@ OBJECTS = $(ABSL_LIBS) \ s2-lnglat.o \ s2-matrix.o \ wk-impl.o \ - s2geography/accessors.o \ - s2geography/accessors-geog.o \ - s2geography/linear-referencing.o \ - s2geography/distance.o \ - s2geography/build.o \ - s2geography/coverings.o \ - s2geography/geography.o \ - s2geography/predicates.o \ - s2/base/stringprintf.o \ - s2/base/strtoint.o \ s2/encoded_s2cell_id_vector.o \ s2/encoded_s2point_vector.o \ s2/encoded_s2shape_index.o \ @@ -132,11 +122,14 @@ OBJECTS = $(ABSL_LIBS) \ s2/s1chord_angle.o \ s2/s1interval.o \ s2/s2boolean_operation.o \ + s2/s2buffer_operation.o \ s2/s2builder_graph.o \ s2/s2builder.o \ s2/s2builderutil_closed_set_normalizer.o \ s2/s2builderutil_find_polygon_degeneracies.o \ + s2/s2builderutil_get_snapped_winding_delta.o \ s2/s2builderutil_lax_polygon_layer.o \ + s2/s2builderutil_lax_polyline_layer.o \ s2/s2builderutil_s2point_vector_layer.o \ s2/s2builderutil_s2polygon_layer.o \ s2/s2builderutil_s2polyline_layer.o \ @@ -165,6 +158,7 @@ OBJECTS = $(ABSL_LIBS) \ s2/s2edge_tessellator.o \ s2/s2error.o \ s2/s2furthest_edge_query.o \ + s2/s2hausdorff_distance_query.o \ s2/s2latlng_rect_bounder.o \ s2/s2latlng_rect.o \ s2/s2latlng.o \ @@ -175,6 +169,7 @@ OBJECTS = $(ABSL_LIBS) \ s2/s2loop.o \ s2/s2max_distance_targets.o \ s2/s2measures.o \ + s2/s2memory_tracker.o \ s2/s2metrics.o \ s2/s2min_distance_targets.o \ s2/s2padded_cell.o \ @@ -198,22 +193,31 @@ OBJECTS = $(ABSL_LIBS) \ s2/s2shape_index_measures.o \ s2/s2shape_index.o \ s2/s2shape_measures.o \ + s2/s2shape_nesting_query.o \ s2/s2shapeutil_build_polygon_boundaries.o \ s2/s2shapeutil_coding.o \ s2/s2shapeutil_contains_brute_force.o \ + s2/s2shapeutil_conversion.o \ s2/s2shapeutil_edge_iterator.o \ s2/s2shapeutil_get_reference_point.o \ - s2/s2shapeutil_range_iterator.o \ + s2/s2shapeutil_testing.o \ s2/s2shapeutil_visit_crossing_edge_pairs.o \ s2/s2testing.o \ s2/s2text_format.o \ s2/s2wedge_relations.o \ - s2/strings/ostringstream.o \ - s2/strings/serialize.o \ + s2/s2winding_operation.o \ + s2/thread_testing.o \ s2/util/bits/bit-interleave.o \ - s2/util/bits/bits.o \ s2/util/coding/coder.o \ s2/util/coding/varint.o \ s2/util/math/exactfloat/exactfloat.o \ s2/util/math/mathutil.o \ - s2/util/units/length-units.o + s2/util/units/length-units.o \ + s2geography/accessors-geog.o \ + s2geography/accessors.o \ + s2geography/build.o \ + s2geography/coverings.o \ + s2geography/distance.o \ + s2geography/geography.o \ + s2geography/linear-referencing.o \ + s2geography/predicates.o diff --git a/src/Makevars.win b/src/Makevars.win index 31ed40b7..1edfafd1 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -115,8 +115,6 @@ S2LIBS = $(ABSL_LIBS) \ s2geography/coverings.o \ s2geography/geography.o \ s2geography/predicates.o \ - s2/base/stringprintf.o \ - s2/base/strtoint.o \ s2/encoded_s2cell_id_vector.o \ s2/encoded_s2point_vector.o \ s2/encoded_s2shape_index.o \ @@ -128,11 +126,14 @@ S2LIBS = $(ABSL_LIBS) \ s2/s1chord_angle.o \ s2/s1interval.o \ s2/s2boolean_operation.o \ + s2/s2buffer_operation.o \ s2/s2builder_graph.o \ s2/s2builder.o \ s2/s2builderutil_closed_set_normalizer.o \ s2/s2builderutil_find_polygon_degeneracies.o \ + s2/s2builderutil_get_snapped_winding_delta.o \ s2/s2builderutil_lax_polygon_layer.o \ + s2/s2builderutil_lax_polyline_layer.o \ s2/s2builderutil_s2point_vector_layer.o \ s2/s2builderutil_s2polygon_layer.o \ s2/s2builderutil_s2polyline_layer.o \ @@ -161,6 +162,7 @@ S2LIBS = $(ABSL_LIBS) \ s2/s2edge_tessellator.o \ s2/s2error.o \ s2/s2furthest_edge_query.o \ + s2/s2hausdorff_distance_query.o \ s2/s2latlng_rect_bounder.o \ s2/s2latlng_rect.o \ s2/s2latlng.o \ @@ -171,6 +173,7 @@ S2LIBS = $(ABSL_LIBS) \ s2/s2loop.o \ s2/s2max_distance_targets.o \ s2/s2measures.o \ + s2/s2memory_tracker.o \ s2/s2metrics.o \ s2/s2min_distance_targets.o \ s2/s2padded_cell.o \ @@ -194,20 +197,21 @@ S2LIBS = $(ABSL_LIBS) \ s2/s2shape_index_measures.o \ s2/s2shape_index.o \ s2/s2shape_measures.o \ + s2/s2shape_nesting_query.o \ s2/s2shapeutil_build_polygon_boundaries.o \ s2/s2shapeutil_coding.o \ s2/s2shapeutil_contains_brute_force.o \ + s2/s2shapeutil_conversion.o \ s2/s2shapeutil_edge_iterator.o \ s2/s2shapeutil_get_reference_point.o \ - s2/s2shapeutil_range_iterator.o \ + s2/s2shapeutil_testing.o \ s2/s2shapeutil_visit_crossing_edge_pairs.o \ s2/s2testing.o \ s2/s2text_format.o \ s2/s2wedge_relations.o \ - s2/strings/ostringstream.o \ - s2/strings/serialize.o \ + s2/s2winding_operation.o \ + s2/thread_testing.o \ s2/util/bits/bit-interleave.o \ - s2/util/bits/bits.o \ s2/util/coding/coder.o \ s2/util/coding/varint.o \ s2/util/math/exactfloat/exactfloat.o \ diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 24a5ac1b..6350151d 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -1344,12 +1344,12 @@ BEGIN_RCPP END_RCPP } -RcppExport SEXP c_s2_geography_writer_new(SEXP, SEXP, SEXP, SEXP); -RcppExport SEXP c_s2_handle_geography(SEXP, SEXP); -RcppExport SEXP c_s2_handle_geography_tessellated(SEXP, SEXP); -RcppExport SEXP c_s2_projection_mercator(SEXP); -RcppExport SEXP c_s2_projection_orthographic(SEXP); -RcppExport SEXP c_s2_projection_plate_carree(SEXP); +RcppExport SEXP c_s2_geography_writer_new(void *, void *, void *, void *); +RcppExport SEXP c_s2_handle_geography(void *, void *); +RcppExport SEXP c_s2_handle_geography_tessellated(void *, void *); +RcppExport SEXP c_s2_projection_mercator(void *); +RcppExport SEXP c_s2_projection_orthographic(void *); +RcppExport SEXP c_s2_projection_plate_carree(void *); RcppExport SEXP c_s2_trans_s2_lnglat_new(void); RcppExport SEXP c_s2_trans_s2_point_new(void); diff --git a/src/s2/_fp_contract_off.h b/src/s2/_fp_contract_off.h index a053c7a7..4f7e915d 100644 --- a/src/s2/_fp_contract_off.h +++ b/src/s2/_fp_contract_off.h @@ -38,16 +38,7 @@ // appears before the first non-inline function definition. It is // named with an underscore so that it is included first among the S2 headers. -// TODO(compiler-team): Figure out how to do this in a portable way. -#if defined(HAVE_ARMEABI_V7A) -// Some android builds use a buggy compiler that runs out of memory while -// parsing the pragma (--cpu=armeabi-v7a). - -#elif defined(__ANDROID__) -// Other android builds use a buggy compiler that crashes with an internal -// error (Android NDK R9). - -#elif defined(__clang__) +#if defined(__clang__) // Clang supports the standard C++ pragma for turning off this optimization. #pragma STDC FP_CONTRACT OFF diff --git a/src/s2/base/casts.h b/src/s2/base/casts.h index 84880c7b..a227d234 100644 --- a/src/s2/base/casts.h +++ b/src/s2/base/casts.h @@ -13,7 +13,6 @@ // limitations under the License. // - // // Various Google-specific casting templates. // @@ -25,12 +24,16 @@ #ifndef S2_BASE_CASTS_H_ #define S2_BASE_CASTS_H_ -#include // for use with down_cast<> -#include // for enumeration casts and tests +#include // for use with down_cast<> +#include // for enumeration casts and tests + #include -#include "absl/base/casts.h" -#include "absl/base/macros.h" +#include "absl/base/casts.h" // IWYU pragma: keep +#include "absl/base/config.h" +#include "absl/log/log.h" + +#include "s2/base/logging.h" // An "upcast", i.e. a conversion from a pointer to an object to a pointer to a // base subobject, always succeeds if the base is unambiguous and accessible, @@ -44,10 +47,11 @@ // downcast in a polymorphic type hierarchy, you should use the following // function template. // -// In debug mode, we use dynamic_cast to double-check whether the downcast is -// legal (we die if it's not). In normal mode, we do the efficient static_cast -// instead. Thus, it's important to test in debug mode to make sure the cast is -// legal! +// This function never returns null. In debug mode, we use dynamic_cast to +// double-check whether the downcast is legal (we die if it's not). In normal +// mode, we do the efficient static_cast instead. Because the process will die +// in debug mode, it's important to test to make sure the cast is legal before +// calling this function! // // This is the only place in the codebase we should use dynamic_cast. // In particular, you should NOT use dynamic_cast for RTTI, e.g. for @@ -56,14 +60,13 @@ // if (auto* p = dynamic_cast(foo)) HandleASubclass2Object(p); // You should design the code some other way not to need this. -template // use like this: down_cast(foo); -inline To down_cast(From* f) { // so we only accept pointers - static_assert( - (std::is_base_of::type>::value), - "target type not derived from source type"); +template // use like this: down_cast(foo); +inline To down_cast(From* f) { // so we only accept pointers + static_assert((std::is_base_of>::value), + "target type not derived from source type"); - // We skip the assert and hence the dynamic_cast if RTTI is disabled. -#if !defined(__GNUC__) || defined(__GXX_RTTI) +// We skip the assert and hence the dynamic_cast if RTTI is disabled. +#if ABSL_INTERNAL_HAS_RTTI // Uses RTTI in dbg and fastbuild. asserts are disabled in opt builds. assert(f == nullptr || dynamic_cast(f) != nullptr); #endif // !defined(__GNUC__) || defined(__GXX_RTTI) @@ -79,19 +82,17 @@ inline To down_cast(From* f) { // so we only accept pointers // There's no need for a special const overload either for the pointer // or the reference form. If you call down_cast with a const T&, the // compiler will just bind From to const T. -template +template inline To down_cast(From& f) { - static_assert( - std::is_lvalue_reference::value, "target type not a reference"); - static_assert( - (std::is_base_of::type>::value), - "target type not derived from source type"); + static_assert(std::is_lvalue_reference::value, + "target type not a reference"); + static_assert((std::is_base_of>::value), + "target type not derived from source type"); // We skip the assert and hence the dynamic_cast if RTTI is disabled. -#if !defined(__GNUC__) || defined(__GXX_RTTI) +#if ABSL_INTERNAL_HAS_RTTI // RTTI: debug mode only - assert(dynamic_cast::type*>(&f) != - nullptr); + assert(dynamic_cast*>(&f) != nullptr); #endif // !defined(__GNUC__) || defined(__GXX_RTTI) return static_cast(f); @@ -111,7 +112,7 @@ inline To down_cast(From& f) { // enum A { A_min = -18, A_max = 33 }; // MAKE_ENUM_LIMITS(A, A_min, A_max) // -// Convert an int to an enum in one of two ways. The prefered way is a +// Convert an int to an enum in one of two ways. The preferred way is a // tight conversion, which ensures that A_min <= value <= A_max. // // A var = tight_enum_cast(3); @@ -159,25 +160,25 @@ inline To down_cast(From& f) { template class enum_limits { public: - static const Enum min_enumerator = 0; - static const Enum max_enumerator = 0; - static const bool is_specialized = false; + static constexpr Enum min_enumerator = 0; + static constexpr Enum max_enumerator = 0; + static constexpr bool is_specialized = false; }; // Now we define the macro to define the specialization for enum_limits. // The specialization checks that the enumerators fit within an int. // This checking relies on integral promotion. -#define MAKE_ENUM_LIMITS(ENUM_TYPE, ENUM_MIN, ENUM_MAX) \ -template <> \ -class enum_limits { \ -public: \ - static const ENUM_TYPE min_enumerator = ENUM_MIN; \ - static const ENUM_TYPE max_enumerator = ENUM_MAX; \ - static const bool is_specialized = true; \ - static_assert(ENUM_MIN >= INT_MIN, "enumerator too negative for int"); \ - static_assert(ENUM_MAX <= INT_MAX, "enumerator too positive for int"); \ -}; +#define MAKE_ENUM_LIMITS(ENUM_TYPE, ENUM_MIN, ENUM_MAX) \ + template <> \ + class enum_limits { \ + public: \ + static const ENUM_TYPE min_enumerator = ENUM_MIN; \ + static const ENUM_TYPE max_enumerator = ENUM_MAX; \ + static const bool is_specialized = true; \ + static_assert(ENUM_MIN >= INT_MIN, "enumerator too negative for int"); \ + static_assert(ENUM_MAX <= INT_MAX, "enumerator too positive for int"); \ + }; // The loose enum test/cast is actually the more complicated one, // because of the problem of finding the bounds. @@ -217,7 +218,7 @@ inline bool loose_enum_test(int e_val) { // Find the unary bounding negative number of e_max. // This would be b_min = e_max < 0 ? e_max : ~e_max, // but we want to avoid branches to help the compiler. - int e_max_sign = e_max >> (sizeof(e_val)*8 - 1); + int e_max_sign = e_max >> (sizeof(e_val) * 8 - 1); int b_min = ~e_max_sign ^ e_max; // Find the binary bounding negative of both e_min and e_max. @@ -241,7 +242,7 @@ inline bool loose_enum_test(int e_val) { // Find the binary bounding positive number of that // and the unary bounding positive number of e_min. - int e_min_sign = e_min >> (sizeof(e_val)*8 - 1); + int e_min_sign = e_min >> (sizeof(e_val) * 8 - 1); b_max |= e_min_sign ^ e_min; // Now set all bits right of the most significant set bit, @@ -270,39 +271,27 @@ inline bool tight_enum_test(int e_val) { template inline bool loose_enum_test_cast(int e_val, Enum* e_var) { if (loose_enum_test(e_val)) { - *e_var = static_cast(e_val); - return true; + *e_var = static_cast(e_val); + return true; } else { - return false; + return false; } } template inline bool tight_enum_test_cast(int e_val, Enum* e_var) { if (tight_enum_test(e_val)) { - *e_var = static_cast(e_val); - return true; + *e_var = static_cast(e_val); + return true; } else { - return false; + return false; } } -// The plain casts require logging, and we get header recursion if -// it is done directly. So, we do it indirectly. -// The following function is defined in logging.cc. - -namespace base { -namespace internal { - -void WarnEnumCastError(int value_of_int); - -} // namespace internal -} // namespace base - template inline Enum loose_enum_cast(int e_val) { if (!loose_enum_test(e_val)) { - base::internal::WarnEnumCastError(e_val); + S2_LOG(ERROR) << "enum_cast error for value " << e_val; } return static_cast(e_val); } @@ -310,7 +299,7 @@ inline Enum loose_enum_cast(int e_val) { template inline Enum tight_enum_cast(int e_val) { if (!tight_enum_test(e_val)) { - base::internal::WarnEnumCastError(e_val); + S2_LOG(ERROR) << "enum_cast error for value " << e_val; } return static_cast(e_val); } diff --git a/src/s2/base/commandlineflags.h b/src/s2/base/commandlineflags.h index 1763be0e..8fa81e63 100644 --- a/src/s2/base/commandlineflags.h +++ b/src/s2/base/commandlineflags.h @@ -16,36 +16,26 @@ #ifndef S2_BASE_COMMANDLINEFLAGS_H_ #define S2_BASE_COMMANDLINEFLAGS_H_ -#ifdef S2_USE_GFLAGS - -#include - -#else // !defined(S2_USE_GFLAGS) - #include +#include "absl/flags/flag.h" + +#include "s2/base/commandlineflags_declare.h" #include "s2/base/integral_types.h" -#define DEFINE_bool(name, default_value, description) \ - bool FLAGS_##name = default_value -#define DECLARE_bool(name) \ - extern bool FLAGS_##name +#define S2_DEFINE_bool(name, default_value, description) \ + ABSL_FLAG(bool, name, default_value, description) -#define DEFINE_double(name, default_value, description) \ - double FLAGS_##name = default_value -#define DECLARE_double(name) \ - extern double FLAGS_##name +#define S2_DEFINE_double(name, default_value, description) \ + ABSL_FLAG(double, name, default_value, description) -#define DEFINE_int32(name, default_value, description) \ - int32 FLAGS_##name = default_value -#define DECLARE_int32(name) \ - extern int32 FLAGS_##name +#define S2_DEFINE_int32(name, default_value, description) \ + ABSL_FLAG(int32, name, default_value, description) -#define DEFINE_string(name, default_value, description) \ - std::string FLAGS_##name = default_value -#define DECLARE_string(name) \ - extern std::string FLAGS_##name +#define S2_DEFINE_int64(name, default_value, description) \ + ABSL_FLAG(int64, name, default_value, description) -#endif // !defined(S2_USE_GFLAGS) +#define S2_DEFINE_string(name, default_value, description) \ + ABSL_FLAG(std::string, name, default_value, description) #endif // S2_BASE_COMMANDLINEFLAGS_H_ diff --git a/src/s2/util/gtl/layout.h b/src/s2/base/commandlineflags_declare.h similarity index 53% rename from src/s2/util/gtl/layout.h rename to src/s2/base/commandlineflags_declare.h index fafb6ab2..42d80e0e 100644 --- a/src/s2/util/gtl/layout.h +++ b/src/s2/base/commandlineflags_declare.h @@ -13,16 +13,23 @@ // limitations under the License. // -#ifndef S2_UTIL_GTL_LAYOUT_H_ -#define S2_UTIL_GTL_LAYOUT_H_ +#ifndef S2_BASE_COMMANDLINEFLAGS_DECLARE_H_ +#define S2_BASE_COMMANDLINEFLAGS_DECLARE_H_ -#include "absl/container/internal/layout.h" +#include -namespace gtl { +#include "absl/flags/declare.h" -using absl::container_internal::Aligned; -using absl::container_internal::Layout; +#include "s2/base/integral_types.h" -} // namespace gtl +#define S2_DECLARE_bool(name) ABSL_DECLARE_FLAG(bool, name) -#endif // S2_UTIL_GTL_LAYOUT_H_ +#define S2_DECLARE_double(name) ABSL_DECLARE_FLAG(double, name) + +#define S2_DECLARE_int32(name) ABSL_DECLARE_FLAG(int32, name) + +#define S2_DECLARE_int64(name) ABSL_DECLARE_FLAG(int64, name) + +#define S2_DECLARE_string(name) ABSL_DECLARE_FLAG(std::string, name) + +#endif // S2_BASE_COMMANDLINEFLAGS_DECLARE_H_ diff --git a/src/s2/base/integral_types.h b/src/s2/base/integral_types.h index d20f35f5..48449631 100644 --- a/src/s2/base/integral_types.h +++ b/src/s2/base/integral_types.h @@ -16,6 +16,7 @@ #ifndef S2_BASE_INTEGRAL_TYPES_H_ #define S2_BASE_INTEGRAL_TYPES_H_ +// NOLINTBEGIN(runtime/int) using int8 = signed char; using int16 = short; using int32 = int; @@ -27,5 +28,6 @@ using uint32 = unsigned int; using uint64 = unsigned long long; using uword_t = unsigned long; +// NOLINTEND(runtime/int) #endif // S2_BASE_INTEGRAL_TYPES_H_ diff --git a/src/s2/base/log_severity.h b/src/s2/base/log_severity.h index b0e9de31..d4644080 100644 --- a/src/s2/base/log_severity.h +++ b/src/s2/base/log_severity.h @@ -16,12 +16,6 @@ #ifndef S2_BASE_LOG_SEVERITY_H_ #define S2_BASE_LOG_SEVERITY_H_ -#ifdef S2_USE_GLOG - -#include - -#else // !defined(S2_USE_GLOG) - #include "absl/base/log_severity.h" // Stay compatible with glog. @@ -35,6 +29,4 @@ constexpr bool DEBUG_MODE = true; } // namespace google -#endif // !defined(S2_USE_GLOG) - #endif // S2_BASE_LOG_SEVERITY_H_ diff --git a/src/s2/base/logging.h b/src/s2/base/logging.h index d32b6118..aac48a0a 100644 --- a/src/s2/base/logging.h +++ b/src/s2/base/logging.h @@ -15,18 +15,21 @@ #ifndef S2_BASE_LOGGING_H_ #define S2_BASE_LOGGING_H_ -#include "cpp-compat.h" -#ifdef S2_USE_GLOG - -#include +// TODO(user): Get rid of `base/logging.h` includes and +// include the relevant absl file directly instead. +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "s2/base/log_severity.h" // The names CHECK, etc. are too common and may conflict with other // packages. We use S2_CHECK to make it easier to switch to -// something other than GLOG for logging. +// something other than abseil-cpp for logging. +// TODO(user): Remove these or make absl::log optional. #define S2_LOG LOG #define S2_LOG_IF LOG_IF +#define S2_DLOG DLOG #define S2_DLOG_IF DLOG_IF #define S2_CHECK CHECK @@ -45,133 +48,14 @@ #define S2_DCHECK_GT DCHECK_GT #define S2_DCHECK_GE DCHECK_GE -#define S2_VLOG VLOG -#define S2_VLOG_IS_ON VLOG_IS_ON - -#else // !defined(S2_USE_GLOG) - -#include - -#include "s2/base/log_severity.h" -#include "absl/base/attributes.h" -#include "absl/base/log_severity.h" - -class S2LogMessage { - public: - S2LogMessage(const char* file, int line, - absl::LogSeverity severity, std::ostream& stream) - : severity_(severity), stream_(stream) { - if (enabled()) { - stream_ << file << ":" << line << " " - << absl::LogSeverityName(severity) << " "; - } - } - ~S2LogMessage() { if (enabled()) stream_ << std::endl; } - - std::ostream& stream() { return stream_; } - - // silences an 'unused member' compiler warning - absl::LogSeverity severity() { return severity_; } - - private: - bool enabled() const { -#ifdef ABSL_MIN_LOG_LEVEL - return (static_cast(severity_) >= ABSL_MIN_LOG_LEVEL || - severity_ >= absl::LogSeverity::kFatal); -#else - return true; -#endif - } - - absl::LogSeverity severity_; - std::ostream& stream_; -}; - -// Same as S2LogMessage, but destructor is marked no-return to avoid -// "no return value warnings" in functions that return non-void. -class S2FatalLogMessage : public S2LogMessage { - public: - S2FatalLogMessage(const char* file, int line, - absl::LogSeverity severity, std::ostream& stream) - ABSL_ATTRIBUTE_COLD - : S2LogMessage(file, line, severity, stream) {} - ABSL_ATTRIBUTE_NORETURN ~S2FatalLogMessage() { cpp_compat_abort(); } -}; - // Logging stream that does nothing. struct S2NullStream { template S2NullStream& operator<<(const T& v) { return *this; } }; -// Used to suppress "unused value" warnings. -struct S2LogMessageVoidify { - // Must have precedence lower than << but higher than ?:. - void operator&(std::ostream&) {} -}; - -#define S2_LOG_MESSAGE_(LogMessageClass, log_severity) \ - LogMessageClass(__FILE__, __LINE__, log_severity, cpp_compat_cerr) -#define S2_LOG_INFO \ - S2_LOG_MESSAGE_(S2LogMessage, absl::LogSeverity::kInfo) -#define S2_LOG_WARNING \ - S2_LOG_MESSAGE_(S2LogMessage, absl::LogSeverity::kWarning) -#define S2_LOG_ERROR \ - S2_LOG_MESSAGE_(S2LogMessage, absl::LogSeverity::kError) -#define S2_LOG_FATAL \ - S2_LOG_MESSAGE_(S2FatalLogMessage, absl::LogSeverity::kFatal) -#ifndef NDEBUG -#define S2_LOG_DFATAL S2_LOG_FATAL -#else -#define S2_LOG_DFATAL S2_LOG_ERROR -#endif - -#define S2_LOG(severity) S2_LOG_##severity.stream() - -// Implementing this as if (...) {} else S2_LOG(...) will cause dangling else -// warnings when someone does if (...) S2_LOG_IF(...), so do this tricky -// thing instead. -#define S2_LOG_IF(severity, condition) \ - !(condition) ? (void)0 : S2LogMessageVoidify() & S2_LOG(severity) - -#define S2_CHECK(condition) \ - S2_LOG_IF(FATAL, ABSL_PREDICT_FALSE(!(condition))) \ - << ("Check failed: " #condition " ") - -#ifndef NDEBUG - -#define S2_DLOG_IF S2_LOG_IF -#define S2_DCHECK S2_CHECK - -#else // defined(NDEBUG) - -#define S2_DLOG_IF(severity, condition) \ - while (false && (condition)) S2NullStream() -#define S2_DCHECK(condition) \ - while (false && (condition)) S2NullStream() - -#endif // defined(NDEBUG) - -#define S2_CHECK_OP(op, val1, val2) S2_CHECK((val1) op (val2)) -#define S2_CHECK_EQ(val1, val2) S2_CHECK_OP(==, val1, val2) -#define S2_CHECK_NE(val1, val2) S2_CHECK_OP(!=, val1, val2) -#define S2_CHECK_LT(val1, val2) S2_CHECK_OP(<, val1, val2) -#define S2_CHECK_LE(val1, val2) S2_CHECK_OP(<=, val1, val2) -#define S2_CHECK_GT(val1, val2) S2_CHECK_OP(>, val1, val2) -#define S2_CHECK_GE(val1, val2) S2_CHECK_OP(>=, val1, val2) - -#define S2_DCHECK_OP(op, val1, val2) S2_DCHECK((val1) op (val2)) -#define S2_DCHECK_EQ(val1, val2) S2_DCHECK_OP(==, val1, val2) -#define S2_DCHECK_NE(val1, val2) S2_DCHECK_OP(!=, val1, val2) -#define S2_DCHECK_LT(val1, val2) S2_DCHECK_OP(<, val1, val2) -#define S2_DCHECK_LE(val1, val2) S2_DCHECK_OP(<=, val1, val2) -#define S2_DCHECK_GT(val1, val2) S2_DCHECK_OP(>, val1, val2) -#define S2_DCHECK_GE(val1, val2) S2_DCHECK_OP(>=, val1, val2) - -// We don't support VLOG. +// Abseil-cpp doesn't support VLOG yet. Make VLOG a no-op. #define S2_VLOG(verbose_level) S2NullStream() #define S2_VLOG_IS_ON(verbose_level) (false) -#endif // !defined(S2_USE_GLOG) - #endif // S2_BASE_LOGGING_H_ diff --git a/src/s2/base/port.h b/src/s2/base/port.h index 10724712..8d7448f3 100644 --- a/src/s2/base/port.h +++ b/src/s2/base/port.h @@ -17,293 +17,39 @@ #define S2_BASE_PORT_H_ // This file contains things that are not used in third_party/absl but needed by -// - Platform specific requirement -// - MSVC -// - Utility macros +// s2geometry. It is structed into the following high-level categories: // - Endianness -// - Hash -// - Global variables -// - Type alias -// - Predefined system/language macros -// - Predefined system/language functions // - Performance optimization (alignment) -// - Obsolete -#include -#include -#include #include #include "s2/base/integral_types.h" -#include "absl/base/config.h" -#include "absl/base/port.h" - -#ifdef SWIG -%include "third_party/absl/base/port.h" -#endif - -// ----------------------------------------------------------------------------- -// MSVC Specific Requirements -// ----------------------------------------------------------------------------- - -#ifdef _MSC_VER /* if Visual C++ */ - -#include // Must come before -#include -#include // _getpid() -#include -#undef ERROR -#undef DELETE -#undef DIFFERENCE -#define STDIN_FILENO 0 -#define STDOUT_FILENO 1 -#define STDERR_FILENO 2 -#define S_IRUSR 00400 -#define S_IWUSR 00200 -#define S_IXUSR 00100 -#define S_IRGRP 00040 -#define S_IWGRP 00020 -#define S_IXGRP 00010 -#define S_IROTH 00004 -#define S_IWOTH 00002 -#define S_IXOTH 00001 - -// This compiler flag can be easily overlooked on MSVC. -// _CHAR_UNSIGNED gets set with the /J flag. -#ifndef _CHAR_UNSIGNED -#error chars must be unsigned! Use the /J flag on the compiler command line. // NOLINT -#endif - -// Allow comparisons between signed and unsigned values. -// -// Lots of Google code uses this pattern: -// for (int i = 0; i < container.size(); ++i) -// Since size() returns an unsigned value, this warning would trigger -// frequently. Very few of these instances are actually bugs since containers -// rarely exceed MAX_INT items. Unfortunately, there are bugs related to -// signed-unsigned comparisons that have been missed because we disable this -// warning. For example: -// const long stop_time = os::GetMilliseconds() + kWaitTimeoutMillis; -// while (os::GetMilliseconds() <= stop_time) { ... } -#pragma warning(disable : 4018) // level 3 -#pragma warning(disable : 4267) // level 3 - -// Don't warn about unused local variables. -// -// extension to silence particular instances of this warning. There's no way -// to define ABSL_ATTRIBUTE_UNUSED to quiet particular instances of this warning -// in VC++, so we disable it globally. Currently, there aren't many false -// positives, so perhaps we can address those in the future and re-enable these -// warnings, which sometimes catch real bugs. -#pragma warning(disable : 4101) // level 3 - -// Allow initialization and assignment to a smaller type without warnings about -// possible loss of data. -// -// There is a distinct warning, 4267, that warns about size_t conversions to -// smaller types, but we don't currently disable that warning. -// -// Correct code can be written in such a way as to avoid false positives -// by making the conversion explicit, but Google code isn't usually that -// verbose. There are too many false positives to address at this time. Note -// that this warning triggers at levels 2, 3, and 4 depending on the specific -// type of conversion. By disabling it, we not only silence minor narrowing -// conversions but also serious ones. -#pragma warning(disable : 4244) // level 2, 3, and 4 - -// Allow silent truncation of double to float. -// -// Silencing this warning has caused us to miss some subtle bugs. -#pragma warning(disable : 4305) // level 1 - -// Allow a constant to be assigned to a type that is too small. -// -// I don't know why we allow this at all. I can't think of a case where this -// wouldn't be a bug, but enabling the warning breaks many builds today. -#pragma warning(disable : 4307) // level 2 - -// Allow passing the this pointer to an initializer even though it refers -// to an uninitialized object. -// -// Some observer implementations rely on saving the this pointer. Those are -// safe because the pointer is not dereferenced until after the object is fully -// constructed. This could however, obscure other instances. In the future, we -// should look into disabling this warning locally rather globally. -#pragma warning(disable : 4355) // level 1 and 4 - -// Allow implicit coercion from an integral type to a bool. -// -// These could be avoided by making the code more explicit, but that's never -// been the style here, so there would be many false positives. It's not -// obvious if a true positive would ever help to find an actual bug. -#pragma warning(disable : 4800) // level 3 - -#endif // _MSC_VER - -// ----------------------------------------------------------------------------- -// Utility Macros -// ----------------------------------------------------------------------------- - -// 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 -#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE -#ifndef OS_IOS -#define OS_IOS 1 -#endif -#endif // defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE -#endif // defined(__APPLE__) - -// __GLIBC_PREREQ -#if defined __linux__ -// GLIBC-related macros. -#include - -#ifndef __GLIBC_PREREQ -#define __GLIBC_PREREQ(a, b) 0 // not a GLIBC system -#endif -#endif // __linux__ - -// STATIC_ANALYSIS -// Klocwork static analysis tool's C/C++ complier kwcc -#if defined(__KLOCWORK__) -#define STATIC_ANALYSIS -#endif // __KLOCWORK__ - -// SIZEOF_MEMBER, OFFSETOF_MEMBER -#define SIZEOF_MEMBER(t, f) sizeof(reinterpret_cast(4096)->f) - -#define OFFSETOF_MEMBER(t, f) \ - (reinterpret_cast(&(reinterpret_cast(16)->f)) - \ - reinterpret_cast(16)) - -// LANG_CXX11 -// GXX_EXPERIMENTAL_CXX0X is defined by gcc and clang up to at least -// gcc-4.7 and clang-3.1 (2011-12-13). __cplusplus was defined to 1 -// in gcc before 4.7 (Crosstool 16) and clang before 3.1, but is -// defined according to the language version in effect thereafter. -// Microsoft Visual Studio 14 (2015) sets __cplusplus==199711 despite -// reasonably good C++11 support, so we set LANG_CXX for it and -// newer versions (_MSC_VER >= 1900). -#if (defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L || \ - (defined(_MSC_VER) && _MSC_VER >= 1900)) -// DEPRECATED: Do not key off LANG_CXX11. Instead, write more accurate condition -// that checks whether the C++ feature you need is available or missing, and -// define a more specific feature macro (GOOGLE_HAVE_FEATURE_FOO). You can check -// http://en.cppreference.com/w/cpp/compiler_support for compiler support on C++ -// features. -// Define this to 1 if the code is compiled in C++11 mode; leave it -// undefined otherwise. Do NOT define it to 0 -- that causes -// '#ifdef LANG_CXX11' to behave differently from '#if LANG_CXX11'. -#define LANG_CXX11 1 -#endif - -// This sanity check can be removed when all references to -// LANG_CXX11 is removed from the code base. -#if defined(__cplusplus) && !defined(LANG_CXX11) && !defined(SWIG) -#error "LANG_CXX11 is required." -#endif - -// GOOGLE_OBSCURE_SIGNAL -#if defined(__APPLE__) -// No SIGPWR on MacOSX. SIGINFO seems suitably obscure. -#define GOOGLE_OBSCURE_SIGNAL SIGINFO -#else -/* We use SIGPWR since that seems unlikely to be used for other reasons. */ -#define GOOGLE_OBSCURE_SIGNAL SIGPWR -#endif - -// ABSL_FUNC_PTR_TO_CHAR_PTR -// On some platforms, a "function pointer" points to a function descriptor -// rather than directly to the function itself. -// Use ABSL_FUNC_PTR_TO_CHAR_PTR(func) to get a char-pointer to the first -// instruction of the function func. -// TODO(b/30407660): Move this macro into Abseil when symbolizer is released in -// Abseil. -#if defined(__cplusplus) -#if (defined(__powerpc__) && !(_CALL_ELF > 1)) || defined(__ia64) -// use opd section for function descriptors on these platforms, the function -// address is the first word of the descriptor -namespace absl { -enum { kPlatformUsesOPDSections = 1 }; -} // namespace absl -#define ABSL_FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast(func)[0]) -#else // not PPC or IA64 -namespace absl { -enum { kPlatformUsesOPDSections = 0 }; -} // namespace absl -#define ABSL_FUNC_PTR_TO_CHAR_PTR(func) (reinterpret_cast(func)) -#endif // PPC or IA64 -#endif // __cplusplus - -// ----------------------------------------------------------------------------- -// Utility Functions -// ----------------------------------------------------------------------------- - -// sized_delete -#ifdef __cplusplus -namespace base { -// We support C++14's sized deallocation for all C++ builds, -// though for other toolchains, we fall back to using delete. -inline void sized_delete(void *ptr, size_t size) { -#ifdef GOOGLE_HAVE_SIZED_DELETE - ::operator delete(ptr, size); -#else - (void)size; - ::operator delete(ptr); -#endif // GOOGLE_HAVE_SIZED_DELETE -} - -inline void sized_delete_array(void *ptr, size_t size) { -#ifdef GOOGLE_HAVE_SIZED_DELETEARRAY - ::operator delete[](ptr, size); -#else - (void) size; - ::operator delete[](ptr); -#endif -} -} // namespace base -#endif // __cplusplus // ----------------------------------------------------------------------------- // Endianness // ----------------------------------------------------------------------------- // IS_LITTLE_ENDIAN, IS_BIG_ENDIAN - -// Allow compiler -D defines to override detection here -// which occasionally fails (e.g., on CRAN Solaris) -#if defined(IS_LITTLE_ENDIAN) -#undef IS_BIG_ENDIAN -#elif defined(IS_BIG_ENDIAN) -#undef IS_LITTLE_ENDIAN -#else - -#if defined __linux__ || defined OS_ANDROID || defined(__ANDROID__) -// TODO(user): http://b/21460321; use one of OS_ANDROID or __ANDROID__. -// _BIG_ENDIAN +#if defined(__linux__) || defined(__ANDROID__) #include #elif defined(__APPLE__) // BIG_ENDIAN #include // NOLINT(build/include) + /* Let's try and follow the Linux convention */ -#define __BYTE_ORDER BYTE_ORDER +#define __BYTE_ORDER BYTE_ORDER #define __LITTLE_ENDIAN LITTLE_ENDIAN #define __BIG_ENDIAN BIG_ENDIAN #endif -// defines __BYTE_ORDER for MSVC -#ifdef _MSC_VER +// defines __BYTE_ORDER +#ifdef _WIN32 #define __BYTE_ORDER __LITTLE_ENDIAN #define IS_LITTLE_ENDIAN -#else +#else // _WIN32 // define the macros IS_LITTLE_ENDIAN or IS_BIG_ENDIAN // using the above endian definitions from endian.h if @@ -326,317 +72,7 @@ inline void sized_delete_array(void *ptr, size_t size) { #endif #endif // __BYTE_ORDER -#endif // _MSC_VER -#endif // #if defined(IS_LITTLE_ENDIAN) ... #else - -// byte swap functions (bswap_16, bswap_32, bswap_64). - -// The following guarantees declaration of the byte swap functions -#ifdef _MSC_VER -#include // NOLINT(build/include) -#define bswap_16(x) _byteswap_ushort(x) -#define bswap_32(x) _byteswap_ulong(x) -#define bswap_64(x) _byteswap_uint64(x) - -#elif defined(__APPLE__) -// Mac OS X / Darwin features -#include -#define bswap_16(x) OSSwapInt16(x) -#define bswap_32(x) OSSwapInt32(x) -#define bswap_64(x) OSSwapInt64(x) - -#elif defined(__GLIBC__) || defined(__BIONIC__) || defined(__ASYLO__) -#include // IWYU pragma: export - -#else - -static inline uint16 bswap_16(uint16 x) { -#ifdef __cplusplus - return static_cast(((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)); -#else - return (uint16)(((x & 0xFF) << 8) | ((x & 0xFF00) >> 8)); // NOLINT -#endif // __cplusplus -} -#define bswap_16(x) bswap_16(x) -static inline uint32 bswap_32(uint32 x) { - return (((x & 0xFF) << 24) | - ((x & 0xFF00) << 8) | - ((x & 0xFF0000) >> 8) | - ((x & 0xFF000000) >> 24)); -} -#define bswap_32(x) bswap_32(x) -static inline uint64 bswap_64(uint64 x) { - 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) - -#endif - -// ----------------------------------------------------------------------------- -// Hash -// ----------------------------------------------------------------------------- - -#ifdef __cplusplus -#ifdef STL_MSVC // not always the same as _MSC_VER -#include "absl/base/internal/port_hash.inc" -#else -struct PortableHashBase {}; -#endif // STL_MSVC -#endif // __cplusplus - -// ----------------------------------------------------------------------------- -// Global Variables -// ----------------------------------------------------------------------------- - -// PATH_SEPARATOR -// Define the OS's path separator -// -// NOTE: Assuming the path separator at compile time is discouraged. -// Prefer instead to be tolerant of both possible separators whenever possible. -#ifdef __cplusplus // C won't merge duplicate const variables at link time -// Some headers provide a macro for this (GCC's system.h), remove it so that we -// can use our own. -#undef PATH_SEPARATOR -#if defined(_WIN32) -const char PATH_SEPARATOR = '\\'; -#else -const char PATH_SEPARATOR = '/'; #endif // _WIN32 -#endif // __cplusplus - -// ----------------------------------------------------------------------------- -// Type Alias -// ----------------------------------------------------------------------------- - -// uint, ushort, ulong -#if defined __linux__ -// The uint mess: -// mysql.h sets _GNU_SOURCE which sets __USE_MISC in -// sys/types.h typedefs uint if __USE_MISC -// mysql typedefs uint if HAVE_UINT not set -// The following typedef is carefully considered, and should not cause -// any clashes -#if !defined(__USE_MISC) -#if !defined(HAVE_UINT) -#define HAVE_UINT 1 -typedef unsigned int uint; -#endif // !HAVE_UINT -#if !defined(HAVE_USHORT) -#define HAVE_USHORT 1 -typedef unsigned short ushort; // NOLINT -#endif // !HAVE_USHORT -#if !defined(HAVE_ULONG) -#define HAVE_ULONG 1 -typedef unsigned long ulong; // NOLINT -#endif // !HAVE_ULONG -#endif // !__USE_MISC - -#endif // __linux__ - -#ifdef _MSC_VER /* if Visual C++ */ -// VC++ doesn't understand "uint" -#ifndef HAVE_UINT -#define HAVE_UINT 1 -typedef unsigned int uint; -#endif // !HAVE_UINT -#endif // _MSC_VER - -#ifdef _MSC_VER -// uid_t -// MSVC doesn't have uid_t -typedef int uid_t; - -// pid_t -// Defined all over the place. -typedef int pid_t; -#endif // _MSC_VER - -// mode_t -#ifdef _MSC_VER -// From stat.h -typedef unsigned int mode_t; -#endif // _MSC_VER - -// sig_t -#ifdef _MSC_VER -typedef void (*sig_t)(int); -#endif // _MSC_VER - -// u_int16_t, int16_t -#ifdef _MSC_VER -// u_int16_t, int16_t don't exist in MSVC -typedef unsigned short u_int16_t; // NOLINT -typedef short int16_t; // NOLINT -#endif // _MSC_VER - -// using std::hash -#ifdef _MSC_VER -#ifdef __cplusplus -// Define a minimal set of things typically available in the global -// namespace in Google code. ::string is handled elsewhere, and uniformly -// for all targets. -#include -using std::hash; -#endif // __cplusplus -#endif // _MSC_VER - -// printf macros -// __STDC_FORMAT_MACROS must be defined before inttypes.h inclusion */ -#if defined(__APPLE__) -/* From MacOSX's inttypes.h: - * "C++ implementations should define these macros only when - * __STDC_FORMAT_MACROS is defined before is included." */ -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif /* __STDC_FORMAT_MACROS */ -#endif /* __APPLE__ */ - -// printf macros for size_t, in the style of inttypes.h -#if defined(_LP64) || defined(__APPLE__) -#define __PRIS_PREFIX "z" -#else -#define __PRIS_PREFIX -#endif - -// Use these macros after a % in a printf format string -// to get correct 32/64 bit behavior, like this: -// size_t size = records.size(); -// printf("%" PRIuS "\n", size); -#define PRIdS __PRIS_PREFIX "d" -#define PRIxS __PRIS_PREFIX "x" -#define PRIuS __PRIS_PREFIX "u" -#define PRIXS __PRIS_PREFIX "X" -#define PRIoS __PRIS_PREFIX "o" - -#define GPRIuPTHREAD "lu" -#define GPRIxPTHREAD "lx" -#if defined(__APPLE__) -#define PRINTABLE_PTHREAD(pthreadt) reinterpret_cast(pthreadt) -#else -#define PRINTABLE_PTHREAD(pthreadt) pthreadt -#endif - -#ifdef PTHREADS_REDHAT_WIN32 -#include // NOLINT(build/include) -#include // NOLINT(build/include) -// pthread_t is not a simple integer or pointer on Win32 -std::ostream &operator<<(std::ostream &out, const pthread_t &thread_id); -#endif - -// ----------------------------------------------------------------------------- -// Predefined System/Language Macros -// ----------------------------------------------------------------------------- - -// EXFULL -#if defined(__APPLE__) -// Linux has this in -#define EXFULL ENOMEM // not really that great a translation... -#endif // __APPLE__ -#ifdef _MSC_VER -// This actually belongs in errno.h but there's a name conflict in errno -// on WinNT. They (and a ton more) are also found in Winsock2.h, but -// if'd out under NT. We need this subset at minimum. -#define EXFULL ENOMEM // not really that great a translation... -#endif // _MSC_VER - -// MSG_NOSIGNAL -#if defined(__APPLE__) -// Doesn't exist on OSX. -#define MSG_NOSIGNAL 0 -#endif // __APPLE__ - -// __ptr_t -#if defined(__APPLE__) -// Linux has this in -#define __ptr_t void * -#endif // __APPLE__ -#ifdef _MSC_VER -// From glob.h -#define __ptr_t void * -#endif - -// HUGE_VALF -#ifdef _MSC_VER -#include // for HUGE_VAL - -#ifndef HUGE_VALF -#define HUGE_VALF (static_cast(HUGE_VAL)) -#endif -#endif // _MSC_VER - -// MAP_ANONYMOUS -#if defined(__APPLE__) -// For mmap, Linux defines both MAP_ANONYMOUS and MAP_ANON and says MAP_ANON is -// deprecated. In Darwin, MAP_ANON is all there is. -#if !defined MAP_ANONYMOUS -#define MAP_ANONYMOUS MAP_ANON -#endif // !MAP_ANONYMOUS -#endif // __APPLE__ - -// PATH_MAX -// You say tomato, I say atotom -#ifdef _MSC_VER -#define PATH_MAX MAX_PATH -#endif - -// ----------------------------------------------------------------------------- -// Predefined System/Language Functions -// ----------------------------------------------------------------------------- - -// strtoq, strtouq, atoll -#ifdef _MSC_VER -#define strtoq _strtoi64 -#define strtouq _strtoui64 -#define atoll _atoi64 -#endif // _MSC_VER - -#ifdef _MSC_VER -// You say tomato, I say _tomato -#define strcasecmp _stricmp -#define strncasecmp _strnicmp -#define strdup _strdup -#define tempnam _tempnam -#define chdir _chdir -#define getpid _getpid -#define getcwd _getcwd -#define putenv _putenv -#define timezone _timezone -#define tzname _tzname -#endif // _MSC_VER - -// random, srandom -#ifdef _MSC_VER -// You say tomato, I say toma -inline int random() { return rand(); } -inline void srandom(unsigned int seed) { srand(seed); } -#endif // _MSC_VER - -// bcopy, bzero -#ifdef _MSC_VER -// You say juxtapose, I say transpose -#define bcopy(s, d, n) memcpy(d, s, n) -// Really from -inline void bzero(void *s, int n) { memset(s, 0, n); } -#endif // _MSC_VER - -// gethostbyname -#if defined(_WIN32) || defined(__APPLE__) -// gethostbyname() *is* thread-safe for Windows native threads. It is also -// safe on Mac OS X and iOS, where it uses thread-local storage, even though the -// manpages claim otherwise. For details, see -// http://lists.apple.com/archives/Darwin-dev/2006/May/msg00008.html -#else -// gethostbyname() is not thread-safe. So disallow its use. People -// should either use the HostLookup::Lookup*() methods, or gethostbyname_r() -#define gethostbyname gethostbyname_is_not_thread_safe_DO_NOT_USE -#endif // ----------------------------------------------------------------------------- // Performance Optimization @@ -646,368 +82,69 @@ inline void bzero(void *s, int n) { memset(s, 0, n); } // Unaligned APIs -// Portable handling of unaligned loads, stores, and copies. -// On some platforms, like ARM, the copy functions can be more efficient -// then a load and a store. +// Portable handling of unaligned loads, stores, and copies. These are simply +// constant-length memcpy calls. // -// It is possible to implement all of these these using constant-length memcpy -// calls, which is portable and will usually be inlined into simple loads and -// stores if the architecture supports it. However, such inlining usually -// happens in a pass that's quite late in compilation, which means the resulting -// loads and stores cannot participate in many other optimizations, leading to -// overall worse code. // TODO(user): These APIs are forked in Abseil, see -// LLVM, we should reimplement these APIs with functions calling memcpy(), and -// maybe publish them in Abseil. - +// "third_party/absl/base/internal/unaligned_access.h". +// // The unaligned API is C++ only. The declarations use C++ features // (namespaces, inline) which are absent or incompatible in C. #if defined(__cplusplus) -#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER) || \ - defined(MEMORY_SANITIZER) -// Consider we have an unaligned load/store of 4 bytes from address 0x...05. -// AddressSanitizer will treat it as a 3-byte access to the range 05:07 and -// will miss a bug if 08 is the first unaddressable byte. -// ThreadSanitizer will also treat this as a 3-byte access to 05:07 and will -// miss a race between this access and some other accesses to 08. -// MemorySanitizer will correctly propagate the shadow on unaligned stores -// and correctly report bugs on unaligned loads, but it may not properly -// update and report the origin of the uninitialized memory. -// For all three tools, replacing an unaligned access with a tool-specific -// callback solves the problem. - -// Make sure uint16_t/uint32_t/uint64_t are defined. -#include - -extern "C" { -uint16_t __sanitizer_unaligned_load16(const void *p); -uint32_t __sanitizer_unaligned_load32(const void *p); -uint64_t __sanitizer_unaligned_load64(const void *p); -void __sanitizer_unaligned_store16(void *p, uint16_t v); -void __sanitizer_unaligned_store32(void *p, uint32_t v); -void __sanitizer_unaligned_store64(void *p, uint64_t v); -} // extern "C" - -inline uint16 UNALIGNED_LOAD16(const void *p) { - return __sanitizer_unaligned_load16(p); -} - -inline uint32 UNALIGNED_LOAD32(const void *p) { - return __sanitizer_unaligned_load32(p); -} - -inline uint64 UNALIGNED_LOAD64(const void *p) { - return __sanitizer_unaligned_load64(p); -} - -inline void UNALIGNED_STORE16(void *p, uint16 v) { - __sanitizer_unaligned_store16(p, v); -} - -inline void UNALIGNED_STORE32(void *p, uint32 v) { - __sanitizer_unaligned_store32(p, v); -} +namespace base { -inline void UNALIGNED_STORE64(void *p, uint64 v) { - __sanitizer_unaligned_store64(p, v); -} +// Can't use ATTRIBUTE_NO_SANITIZE_MEMORY because this file is included before +// attributes.h is. +#ifdef __has_attribute +#if __has_attribute(no_sanitize_memory) +#define NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) +#endif // __has_attribute(no_sanitize_memory) +#endif // defined __has_attribute -#elif defined(UNDEFINED_BEHAVIOR_SANITIZER) +#ifndef NO_SANITIZE_MEMORY +#define NO_SANITIZE_MEMORY /**/ +#endif -inline uint16 UNALIGNED_LOAD16(const void *p) { - uint16 t; +template +T NO_SANITIZE_MEMORY UnalignedLoad(const void *p) { + T t; memcpy(&t, p, sizeof t); return t; } -inline uint32 UNALIGNED_LOAD32(const void *p) { - uint32 t; - memcpy(&t, p, sizeof t); - return t; -} +#undef NO_SANITIZE_MEMORY -inline uint64 UNALIGNED_LOAD64(const void *p) { - uint64 t; - memcpy(&t, p, sizeof t); - return t; +template +void UnalignedStore(void *p, T t) { + memcpy(p, &t, sizeof t); } - -inline void UNALIGNED_STORE16(void *p, uint16 v) { memcpy(p, &v, sizeof v); } - -inline void UNALIGNED_STORE32(void *p, uint32 v) { memcpy(p, &v, sizeof v); } - -inline void UNALIGNED_STORE64(void *p, uint64 v) { memcpy(p, &v, sizeof v); } - -#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386) || \ - defined(_M_IX86) || defined(__ppc__) || defined(__PPC__) || \ - defined(__ppc64__) || defined(__PPC64__) - -// x86 and x86-64 can perform unaligned loads/stores directly; -// modern PowerPC hardware can also do unaligned integer loads and stores; -// but note: the FPU still sends unaligned loads and stores to a trap handler! - -#define UNALIGNED_LOAD16(_p) (*reinterpret_cast(_p)) -#define UNALIGNED_LOAD32(_p) (*reinterpret_cast(_p)) -#define UNALIGNED_LOAD64(_p) (*reinterpret_cast(_p)) - -#define UNALIGNED_STORE16(_p, _val) (*reinterpret_cast(_p) = (_val)) -#define UNALIGNED_STORE32(_p, _val) (*reinterpret_cast(_p) = (_val)) -#define UNALIGNED_STORE64(_p, _val) (*reinterpret_cast(_p) = (_val)) - -#elif defined(__arm__) && !defined(__ARM_ARCH_5__) && \ - !defined(__ARM_ARCH_5T__) && !defined(__ARM_ARCH_5TE__) && \ - !defined(__ARM_ARCH_5TEJ__) && !defined(__ARM_ARCH_6__) && \ - !defined(__ARM_ARCH_6J__) && !defined(__ARM_ARCH_6K__) && \ - !defined(__ARM_ARCH_6Z__) && !defined(__ARM_ARCH_6ZK__) && \ - !defined(__ARM_ARCH_6T2__) - -// ARMv7 and newer support native unaligned accesses, but only of 16-bit -// and 32-bit values (not 64-bit); older versions either raise a fatal signal, -// do an unaligned read and rotate the words around a bit, or do the reads very -// slowly (trip through kernel mode). There's no simple #define that says just -// “ARMv7 or higher”, so we have to filter away all ARMv5 and ARMv6 -// sub-architectures. Newer gcc (>= 4.6) set an __ARM_FEATURE_ALIGNED #define, -// so in time, maybe we can move on to that. -// -// This is a mess, but there's not much we can do about it. -// -// To further complicate matters, only LDR instructions (single reads) are -// allowed to be unaligned, not LDRD (two reads) or LDM (many reads). Unless we -// explicitly tell the compiler that these accesses can be unaligned, it can and -// will combine accesses. On armcc, the way to signal this is done by accessing -// through the type (uint32 __packed *), but GCC has no such attribute -// (it ignores __attribute__((packed)) on individual variables). However, -// we can tell it that a _struct_ is unaligned, which has the same effect, -// so we do that. - -namespace base { -namespace internal { - -struct Unaligned16Struct { - uint16 value; - uint8 dummy; // To make the size non-power-of-two. -} ABSL_ATTRIBUTE_PACKED; - -struct Unaligned32Struct { - uint32 value; - uint8 dummy; // To make the size non-power-of-two. -} ABSL_ATTRIBUTE_PACKED; - -} // namespace internal } // namespace base -#define UNALIGNED_LOAD16(_p) \ - ((reinterpret_cast(_p))->value) -#define UNALIGNED_LOAD32(_p) \ - ((reinterpret_cast(_p))->value) - -#define UNALIGNED_STORE16(_p, _val) \ - ((reinterpret_cast< ::base::internal::Unaligned16Struct *>(_p))->value = \ - (_val)) -#define UNALIGNED_STORE32(_p, _val) \ - ((reinterpret_cast< ::base::internal::Unaligned32Struct *>(_p))->value = \ - (_val)) - -// TODO(user): NEON supports unaligned 64-bit loads and stores. -// See if that would be more efficient on platforms supporting it, -// at least for copies. - -inline uint64 UNALIGNED_LOAD64(const void *p) { - uint64 t; - memcpy(&t, p, sizeof t); - return t; -} - -inline void UNALIGNED_STORE64(void *p, uint64 v) { memcpy(p, &v, sizeof v); } - -#else - -#define NEED_ALIGNED_LOADS - -// These functions are provided for architectures that don't support -// unaligned loads and stores. - inline uint16 UNALIGNED_LOAD16(const void *p) { - uint16 t; - memcpy(&t, p, sizeof t); - return t; + return base::UnalignedLoad(p); } inline uint32 UNALIGNED_LOAD32(const void *p) { - uint32 t; - memcpy(&t, p, sizeof t); - return t; + return base::UnalignedLoad(p); } inline uint64 UNALIGNED_LOAD64(const void *p) { - uint64 t; - memcpy(&t, p, sizeof t); - return t; + return base::UnalignedLoad(p); } -inline void UNALIGNED_STORE16(void *p, uint16 v) { memcpy(p, &v, sizeof v); } - -inline void UNALIGNED_STORE32(void *p, uint32 v) { memcpy(p, &v, sizeof v); } - -inline void UNALIGNED_STORE64(void *p, uint64 v) { memcpy(p, &v, sizeof v); } - -#endif - -// The UNALIGNED_LOADW and UNALIGNED_STOREW macros load and store values -// of type uword_t. -#ifdef _LP64 -#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD64(_p) -#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE64(_p, _val) -#else -#define UNALIGNED_LOADW(_p) UNALIGNED_LOAD32(_p) -#define UNALIGNED_STOREW(_p, _val) UNALIGNED_STORE32(_p, _val) -#endif - -inline void UnalignedCopy16(const void *src, void *dst) { - UNALIGNED_STORE16(dst, UNALIGNED_LOAD16(src)); +inline void UNALIGNED_STORE16(void *p, uint16 v) { + base::UnalignedStore(p, v); } -inline void UnalignedCopy32(const void *src, void *dst) { - UNALIGNED_STORE32(dst, UNALIGNED_LOAD32(src)); +inline void UNALIGNED_STORE32(void *p, uint32 v) { + base::UnalignedStore(p, v); } -inline void UnalignedCopy64(const void *src, void *dst) { - if (sizeof(void *) == 8) { - UNALIGNED_STORE64(dst, UNALIGNED_LOAD64(src)); - } else { - const char *src_char = reinterpret_cast(src); - char *dst_char = reinterpret_cast(dst); - - UNALIGNED_STORE32(dst_char, UNALIGNED_LOAD32(src_char)); - UNALIGNED_STORE32(dst_char + 4, UNALIGNED_LOAD32(src_char + 4)); - } +inline void UNALIGNED_STORE64(void *p, uint64 v) { + base::UnalignedStore(p, v); } #endif // defined(__cplusplus), end of unaligned API -// aligned_malloc, aligned_free -#if defined(__ANDROID__) || defined(__ASYLO__) || defined(_WIN32) -#include // for memalign() -#endif - -// __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(__ASYLO__)) -inline void *aligned_malloc(size_t size, size_t minimum_alignment) { -#if defined(__ANDROID__) || defined(OS_ANDROID) || defined(__ASYLO__) || defined(_WIN32) || defined(__sun) || defined(sun) -# if defined(_WIN32) - return _aligned_malloc(size, minimum_alignment); -# else - return memalign(minimum_alignment, size); -# endif -#else // !__ANDROID__ && !OS_ANDROID && !__ASYLO__ - // posix_memalign requires that the requested alignment be at least - // sizeof(void*). In this case, fall back on malloc which should return memory - // aligned to at least the size of a pointer. - const size_t required_alignment = sizeof(void*); - if (minimum_alignment < required_alignment) - return malloc(size); - void *ptr = nullptr; - if (posix_memalign(&ptr, minimum_alignment, size) == 0) - return ptr; - return nullptr; -#endif -} - -inline void aligned_free(void *aligned_memory) { - free(aligned_memory); -} - -#elif defined(_MSC_VER) // MSVC - -inline void *aligned_malloc(size_t size, size_t minimum_alignment) { - return _aligned_malloc(size, minimum_alignment); -} - -inline void aligned_free(void *aligned_memory) { - _aligned_free(aligned_memory); -} - -#endif // aligned_malloc, aligned_free - -// ALIGNED_CHAR_ARRAY -// -// Provides a char array with the exact same alignment as another type. The -// first parameter must be a complete type, the second parameter is how many -// of that type to provide space for. -// -// ALIGNED_CHAR_ARRAY(struct stat, 16) storage_; -// -#if defined(__cplusplus) -#undef ALIGNED_CHAR_ARRAY -// Because MSVC and older GCCs require that the argument to their alignment -// construct to be a literal constant integer, we use a template instantiated -// at all the possible powers of two. -#ifndef SWIG -template struct AlignType { }; -template struct AlignType<0, size> { typedef char result[size]; }; -#if defined(_MSC_VER) -#define BASE_PORT_H_ALIGN_ATTRIBUTE(X) __declspec(align(X)) -#define BASE_PORT_H_ALIGN_OF(T) __alignof(T) -#elif defined(__GNUC__) || defined(__INTEL_COMPILER) -#define BASE_PORT_H_ALIGN_ATTRIBUTE(X) __attribute__((aligned(X))) -#define BASE_PORT_H_ALIGN_OF(T) __alignof__(T) -#endif - -#if defined(BASE_PORT_H_ALIGN_ATTRIBUTE) - -#define BASE_PORT_H_ALIGNTYPE_TEMPLATE(X) \ - template struct AlignType { \ - typedef BASE_PORT_H_ALIGN_ATTRIBUTE(X) char result[size]; \ - } - -BASE_PORT_H_ALIGNTYPE_TEMPLATE(1); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(2); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(4); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(8); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(16); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(32); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(64); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(128); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(256); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(512); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(1024); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(2048); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(4096); -BASE_PORT_H_ALIGNTYPE_TEMPLATE(8192); -// Any larger and MSVC++ will complain. - -#define ALIGNED_CHAR_ARRAY(T, Size) \ - typename AlignType::result - -#undef BASE_PORT_H_ALIGNTYPE_TEMPLATE -#undef BASE_PORT_H_ALIGN_ATTRIBUTE - -#else // defined(BASE_PORT_H_ALIGN_ATTRIBUTE) -#define ALIGNED_CHAR_ARRAY \ - you_must_define_ALIGNED_CHAR_ARRAY_for_your_compiler_in_base_port_h -#endif // defined(BASE_PORT_H_ALIGN_ATTRIBUTE) - -#else // !SWIG - -// SWIG can't represent alignment and doesn't care about alignment on data -// members (it works fine without it). -template -struct AlignType { typedef char result[Size]; }; -#define ALIGNED_CHAR_ARRAY(T, Size) AlignType::result - -// Enough to parse with SWIG, will never be used by running code. -#define BASE_PORT_H_ALIGN_OF(Type) 16 - -#endif // !SWIG -#else // __cplusplus -#define ALIGNED_CHAR_ARRAY ALIGNED_CHAR_ARRAY_is_not_available_without_Cplusplus -#endif // __cplusplus - #endif // S2_BASE_PORT_H_ diff --git a/src/s2/base/stringprintf.cc b/src/s2/base/stringprintf.cc deleted file mode 100644 index 501cdb7a..00000000 --- a/src/s2/base/stringprintf.cc +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2002 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/stringprintf.h" - -#include -#include // For va_list and related operations -#include // MSVC requires this for _vsnprintf -#include - -#include "s2/base/logging.h" - -#ifdef _MSC_VER -enum { IS__MSC_VER = 1 }; -#else -enum { IS__MSC_VER = 0 }; -#endif - -void StringAppendV(std::string* dst, const char* format, va_list ap) { - // First try with a small fixed size buffer - static const int kSpaceLength = 1024; - char space[kSpaceLength]; - - // It's possible for methods that use a va_list to invalidate - // the data in it upon use. The fix is to make a copy - // of the structure before using it and use that copy instead. - va_list backup_ap; - va_copy(backup_ap, ap); - int result = vsnprintf(space, kSpaceLength, format, backup_ap); - va_end(backup_ap); - - if (result < kSpaceLength) { - if (result >= 0) { - // Normal case -- everything fit. - dst->append(space, result); - return; - } - - if (IS__MSC_VER) { - // Error or MSVC running out of space. MSVC 8.0 and higher - // can be asked about space needed with the special idiom below: - va_copy(backup_ap, ap); - result = vsnprintf(nullptr, 0, format, backup_ap); - va_end(backup_ap); - } - - if (result < 0) { - // Just an error. - return; - } - } - - // Increase the buffer size to the size requested by vsnprintf, - // plus one for the closing \0. - int length = result+1; - char* buf = new char[length]; - - // Restore the va_list before we use it again - va_copy(backup_ap, ap); - result = vsnprintf(buf, length, format, backup_ap); - va_end(backup_ap); - - if (result >= 0 && result < length) { - // It fit - dst->append(buf, result); - } - delete[] buf; -} - - -std::string StringPrintf(const char* format, ...) { - va_list ap; - va_start(ap, format); - std::string result; - StringAppendV(&result, format, ap); - va_end(ap); - return result; -} - -const std::string& SStringPrintf(std::string* dst, const char* format, ...) { - va_list ap; - va_start(ap, format); - dst->clear(); - StringAppendV(dst, format, ap); - va_end(ap); - return *dst; -} - -void StringAppendF(std::string* dst, const char* format, ...) { - va_list ap; - va_start(ap, format); - StringAppendV(dst, format, ap); - va_end(ap); -} diff --git a/src/s2/base/stringprintf.h b/src/s2/base/stringprintf.h deleted file mode 100644 index 9a97e2d6..00000000 --- a/src/s2/base/stringprintf.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 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. -// - -// NOTE: See third_party/absl/strings for more options. -// -// As of 2017q4, most use of these routines is considered legacy: use -// of absl::StrCat, absl::Substitute, or absl::StrFormat is preferred for -// performance and safety reasons. - -#ifndef S2_BASE_STRINGPRINTF_H_ -#define S2_BASE_STRINGPRINTF_H_ - -#include -#include -#include - -#include "s2/base/port.h" - -// Return a C++ string -extern std::string StringPrintf(const char* format, ...) - // Tell the compiler to do printf format string checking. - ABSL_PRINTF_ATTRIBUTE(1, 2); - -// Store result into a supplied string and return it -extern const std::string& SStringPrintf(std::string* dst, const char* format, ...) - // Tell the compiler to do printf format string checking. - ABSL_PRINTF_ATTRIBUTE(2, 3); - -// Append result to a supplied string -extern void StringAppendF(std::string* dst, const char* format, ...) - // Tell the compiler to do printf format string checking. - ABSL_PRINTF_ATTRIBUTE(2, 3); - -// Lower-level routine that takes a va_list and appends to a specified -// string. All other routines are just convenience wrappers around it. -// -// Implementation note: the va_list is never modified, this implementation -// always operates on copies. -extern void StringAppendV(std::string* dst, const char* format, va_list ap); - -#endif // S2_BASE_STRINGPRINTF_H_ diff --git a/src/s2/base/strtoint.cc b/src/s2/base/strtoint.cc deleted file mode 100644 index b626f2df..00000000 --- a/src/s2/base/strtoint.cc +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2008 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. -// - -// -// Architecture-neutral plug compatible replacements for strtol() friends. -// See strtoint.h for details on how to use this component. -// - -#include -#include -#include - -#include "s2/base/integral_types.h" -#include "s2/base/port.h" -#include "s2/base/strtoint.h" - -// Replacement strto[u]l functions that have identical overflow and underflow -// characteristics for both ILP-32 and LP-64 platforms, including errno -// preservation for error-free calls. -int32 strto32_adapter(const char *nptr, char **endptr, int base) { - const int saved_errno = errno; - errno = 0; - const long result = strtol(nptr, endptr, base); - if (errno == ERANGE && result == LONG_MIN) { - return std::numeric_limits::min(); - } else if (errno == ERANGE && result == LONG_MAX) { - return std::numeric_limits::max(); - } else if (errno == 0 && result < std::numeric_limits::min()) { - errno = ERANGE; - return std::numeric_limits::min(); - } else if (errno == 0 && result > std::numeric_limits::max()) { - errno = ERANGE; - return std::numeric_limits::max(); - } - if (errno == 0) - errno = saved_errno; - return static_cast(result); -} - -uint32 strtou32_adapter(const char *nptr, char **endptr, int base) { - const int saved_errno = errno; - errno = 0; - const unsigned long result = strtoul(nptr, endptr, base); - if (errno == ERANGE && result == ULONG_MAX) { - return std::numeric_limits::max(); - } else if (errno == 0 && result > std::numeric_limits::max()) { - errno = ERANGE; - return std::numeric_limits::max(); - } - if (errno == 0) - errno = saved_errno; - return static_cast(result); -} diff --git a/src/s2/base/strtoint.h b/src/s2/base/strtoint.h deleted file mode 100644 index 479624fd..00000000 --- a/src/s2/base/strtoint.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2008 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. -// - -// -// Architecture-neutral plug compatible replacements for strtol() friends. -// -// Long's have different lengths on ILP-32 and LP-64 platforms, and so overflow -// behavior across the two varies when strtol() and similar are used to parse -// 32-bit integers. Similar problems exist with atoi(), because although it -// has an all-integer interface, it uses strtol() internally, and so suffers -// from the same narrowing problems on assignments to int. -// -// Examples: -// errno = 0; -// i = strtol("3147483647", nullptr, 10); -// printf("%d, errno %d\n", i, errno); -// // 32-bit platform: 2147483647, errno 34 -// // 64-bit platform: -1147483649, errno 0 -// -// printf("%d\n", atoi("3147483647")); -// // 32-bit platform: 2147483647 -// // 64-bit platform: -1147483649 -// -// A way round this is to define local replacements for these, and use them -// instead of the standard libc functions. -// -// In most 32-bit cases the replacements can be inlined away to a call to the -// libc function. In a couple of 64-bit cases, however, adapters are required, -// to provide the right overflow and errno behavior. -// - -#ifndef S2_BASE_STRTOINT_H_ -#define S2_BASE_STRTOINT_H_ - -#include // For strtol* functions. -#include -#include "s2/base/integral_types.h" -#include "s2/base/port.h" -#include "absl/base/macros.h" - -// Adapter functions for handling overflow and errno. -int32 strto32_adapter(const char *nptr, char **endptr, int base); -uint32 strtou32_adapter(const char *nptr, char **endptr, int base); - -// Conversions to a 32-bit integer can pass the call to strto[u]l on 32-bit -// platforms, but need a little extra work on 64-bit platforms. -inline int32 strto32(const char *nptr, char **endptr, int base) { - if (sizeof(int32) == sizeof(long)) - return static_cast(strtol(nptr, endptr, base)); - else - return strto32_adapter(nptr, endptr, base); -} - -inline uint32 strtou32(const char *nptr, char **endptr, int base) { - if (sizeof(uint32) == sizeof(unsigned long)) - return static_cast(strtoul(nptr, endptr, base)); - else - return strtou32_adapter(nptr, endptr, base); -} - -// For now, long long is 64-bit on all the platforms we care about, so these -// functions can simply pass the call to strto[u]ll. -inline int64 strto64(const char *nptr, char **endptr, int base) { - static_assert(sizeof(int64) == sizeof(long long), - "sizeof int64 is not sizeof long long"); - return strtoll(nptr, endptr, base); -} - -inline uint64 strtou64(const char *nptr, char **endptr, int base) { - static_assert(sizeof(uint64) == sizeof(unsigned long long), - "sizeof uint64 is not sizeof long long"); - return strtoull(nptr, endptr, base); -} - -// Although it returns an int, atoi() is implemented in terms of strtol, and -// so has differing overflow and underflow behavior. atol is the same. -inline int32 atoi32(const char *nptr) { - return strto32(nptr, nullptr, 10); -} - -inline int64 atoi64(const char *nptr) { - return strto64(nptr, nullptr, 10); -} - -// Convenience versions of the above that take a string argument. -inline int32 atoi32(const std::string &s) { - return atoi32(s.c_str()); -} - -inline int64 atoi64(const std::string &s) { - return atoi64(s.c_str()); -} - -#endif // S2_BASE_STRTOINT_H_ diff --git a/src/s2/encoded_s2cell_id_vector.cc b/src/s2/encoded_s2cell_id_vector.cc index b3ebf205..b93edcee 100644 --- a/src/s2/encoded_s2cell_id_vector.cc +++ b/src/s2/encoded_s2cell_id_vector.cc @@ -17,6 +17,17 @@ #include "s2/encoded_s2cell_id_vector.h" +#include +#include + +#include "s2/base/integral_types.h" +#include "absl/numeric/bits.h" +#include "absl/types/span.h" +#include "s2/util/bits/bits.h" +#include "s2/util/coding/coder.h" +#include "s2/encoded_uint_vector.h" +#include "s2/s2cell_id.h" + using absl::Span; using std::max; using std::min; @@ -62,7 +73,7 @@ void EncodeS2CellIdVector(Span v, Encoder* encoder) { v_max = max(v_max, cellid.id()); } // These variables represent the values that will used during encoding. - uint64 e_base = 0; // Base value. + 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. @@ -80,12 +91,14 @@ void EncodeS2CellIdVector(Span v, Encoder* encoder) { 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. + // "t_max_delta_msb" is the most-significant bit position (i.e. bit-width + // minus one) 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)); + int t_max_delta_msb = max( + 0, + static_cast(absl::bit_width((v_max - t_base) >> e_shift)) - 1); uint64 t_bytes = len + v.size() * ((t_max_delta_msb >> 3) + 1); if (t_bytes < e_bytes) { e_base = t_base; @@ -137,7 +150,9 @@ bool EncodedS2CellIdVector::Init(Decoder* decoder) { int shift_code = code_plus_len >> 3; if (shift_code == 31) { shift_code = 29 + decoder->get8(); + if (shift_code > 56) return false; // Valid range 0..56 } + // Decode the "base_len" most-significant bytes of "base". int base_len = code_plus_len & 7; if (!DecodeUintWithLength(base_len, decoder, &base_)) return false; @@ -155,7 +170,7 @@ bool EncodedS2CellIdVector::Init(Decoder* decoder) { vector EncodedS2CellIdVector::Decode() const { vector result(size()); - for (int i = 0; i < size(); ++i) { + for (size_t i = 0; i < size(); ++i) { result[i] = (*this)[i]; } return result; diff --git a/src/s2/encoded_s2cell_id_vector.h b/src/s2/encoded_s2cell_id_vector.h index 3b2e7f44..9a4fc374 100644 --- a/src/s2/encoded_s2cell_id_vector.h +++ b/src/s2/encoded_s2cell_id_vector.h @@ -18,7 +18,13 @@ #ifndef S2_ENCODED_S2CELL_ID_VECTOR_H_ #define S2_ENCODED_S2CELL_ID_VECTOR_H_ +#include + +#include + +#include "s2/base/integral_types.h" #include "absl/types/span.h" +#include "s2/util/coding/coder.h" #include "s2/encoded_uint_vector.h" #include "s2/s2cell_id.h" @@ -51,7 +57,7 @@ void EncodeS2CellIdVector(absl::Span v, Encoder* encoder); class EncodedS2CellIdVector { public: // Constructs an uninitialized object; requires Init() to be called. - EncodedS2CellIdVector() {} + EncodedS2CellIdVector() = default; // Initializes the EncodedS2CellIdVector. // @@ -102,7 +108,7 @@ inline size_t EncodedS2CellIdVector::lower_bound(S2CellId target) const { 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_); + (target.id() - base_ + (uint64{1} << shift_) - 1) >> shift_); } } // namespace s2coding diff --git a/src/s2/encoded_s2point_vector.cc b/src/s2/encoded_s2point_vector.cc index b7fdb237..bd33ec20 100644 --- a/src/s2/encoded_s2point_vector.cc +++ b/src/s2/encoded_s2point_vector.cc @@ -17,10 +17,26 @@ #include "s2/encoded_s2point_vector.h" +#include + +#include +#include +#include + +#include "s2/base/integral_types.h" +#include "s2/base/port.h" #include "absl/base/internal/unaligned_access.h" +#include "absl/numeric/bits.h" +#include "absl/types/span.h" #include "s2/util/bits/bits.h" +#include "s2/util/coding/coder.h" +#include "s2/util/coding/varint.h" +#include "s2/encoded_string_vector.h" +#include "s2/encoded_uint_vector.h" #include "s2/s2cell_id.h" +#include "s2/s2coder.h" #include "s2/s2coords.h" +#include "s2/s2point.h" using absl::MakeSpan; using absl::Span; @@ -33,7 +49,8 @@ namespace s2coding { // Like util_bits::InterleaveUint32, but interleaves bit pairs rather than // individual bits. This format is faster to decode than the fully interleaved // format, and produces the same results for our use case. -inline uint64 InterleaveUint32BitPairs(const uint32 val0, const uint32 val1) { +inline uint64 InterleaveUint32BitPairs(const uint32 val0, + const uint32 val1) { uint64 v0 = val0, v1 = val1; v0 = (v0 | (v0 << 16)) & 0x0000ffff0000ffff; v1 = (v1 | (v1 << 16)) & 0x0000ffff0000ffff; @@ -50,8 +67,8 @@ inline uint64 InterleaveUint32BitPairs(const uint32 val0, const uint32 val1) { // uses a lookup table. The speed advantage is expected to be even larger in // code that mixes bit interleaving with other significant operations since it // doesn't require keeping a 256-byte lookup table in the L1 data cache. -inline void DeinterleaveUint32BitPairs(uint64 code, - uint32 *val0, uint32 *val1) { +inline void DeinterleaveUint32BitPairs(uint64 code, uint32* val0, + uint32* val1) { uint64 v0 = code, v1 = code >> 2; v0 &= 0x3333333333333333; v0 |= v0 >> 2; @@ -96,7 +113,7 @@ void EncodeS2PointVector(Span points, CodingHint hint, return EncodeS2PointVectorCompact(points, encoder); default: - S2_LOG(DFATAL) << "Unknown CodingHint: " << static_cast(hint); + S2_LOG(ERROR) << "Unknown CodingHint: " << static_cast(hint); } } @@ -105,7 +122,7 @@ bool EncodedS2PointVector::Init(Decoder* decoder) { // Peek at the format but don't advance the decoder; the format-specific // Init functions will do that. - format_ = static_cast(*decoder->ptr() & kEncodingFormatMask); + format_ = static_cast(*decoder->skip(0) & kEncodingFormatMask); switch (format_) { case UNCOMPRESSED: return InitUncompressedFormat(decoder); @@ -121,12 +138,29 @@ bool EncodedS2PointVector::Init(Decoder* decoder) { vector EncodedS2PointVector::Decode() const { vector points; points.reserve(size_); - for (int i = 0; i < size_; ++i) { + for (size_t i = 0; i < size_; ++i) { points.push_back((*this)[i]); } return points; } +// The encoding must be identical to EncodeS2PointVector(). +void EncodedS2PointVector::Encode(Encoder* encoder) const { + switch (format_) { + case UNCOMPRESSED: + EncodeS2PointVectorFast(MakeSpan(uncompressed_.points, size_), encoder); + break; + + case CELL_IDS: { + // This is a full decode/encode dance, and not at all efficient. + EncodeS2PointVectorCompact(Decode(), encoder); + break; + } + + default: + S2_LOG(FATAL) << "Unknown Format: " << static_cast(format_); + } +} ////////////////////////////////////////////////////////////////////////////// // UNCOMPRESSED Encoding Format @@ -147,7 +181,7 @@ void EncodeS2PointVectorFast(Span points, Encoder* encoder) { // This is followed by an array of S2Points in little-endian order. encoder->Ensure(Varint::kMax64 + points.size() * sizeof(S2Point)); uint64 size_format = (points.size() << kEncodingFormatBits | - EncodedS2PointVector::UNCOMPRESSED); + EncodedS2PointVector::UNCOMPRESSED); encoder->put_varint64(size_format); encoder->putn(points.data(), points.size() * sizeof(S2Point)); } @@ -155,15 +189,15 @@ void EncodeS2PointVectorFast(Span points, Encoder* encoder) { bool EncodedS2PointVector::InitUncompressedFormat(Decoder* decoder) { #if !defined(IS_LITTLE_ENDIAN) || defined(__arm__) || \ defined(ABSL_INTERNAL_NEED_ALIGNED_LOADS) - // TODO(ericv): Make this work on platforms that don't support unaligned - // 64-bit little-endian reads, e.g. by falling back to + // TODO(b/231674214): Make this work on platforms that don't support + // unaligned 64-bit little-endian reads, e.g. by falling back to // // bit_cast(little_endian::Load64()). // // Maybe the compiler is smart enough that we can do this all the time, // but more likely we will need two cases using the #ifdef above. // (Note that even ARMv7 does not support unaligned 64-bit loads.) - S2_LOG(DFATAL) << "Needs architecture with 64-bit little-endian unaligned loads"; + S2_LOG(ERROR) << "Needs architecture with 64-bit little-endian unaligned loads"; return false; #endif @@ -179,7 +213,7 @@ bool EncodedS2PointVector::InitUncompressedFormat(Decoder* decoder) { size_t bytes = size_t{size_} * sizeof(S2Point); if (decoder->avail() < bytes) return false; - uncompressed_.points = reinterpret_cast(decoder->ptr()); + uncompressed_.points = reinterpret_cast(decoder->skip(0)); decoder->skip(bytes); return true; } @@ -194,7 +228,7 @@ bool EncodedS2PointVector::InitUncompressedFormat(Decoder* decoder) { struct CellPoint { // Constructor necessary in order to narrow "int" arguments to "int8". CellPoint(int level, int face, uint32 si, uint32 ti) - : level(level), face(face), si(si), ti(ti) {} + : level(level), face(face), si(si), ti(ti) {} int8 level, face; uint32 si, ti; @@ -205,12 +239,12 @@ struct CellPoint { // Block sizes of 4, 8, 16, and 32 were tested and kBlockSize == 16 seems to // offer the best compression. (Note that kBlockSize == 32 requires some code // modifications which have since been removed.) -constexpr int kBlockShift = 4; -constexpr size_t kBlockSize = 1 << kBlockShift; +static constexpr int kBlockShift = 4; +static constexpr size_t kBlockSize = 1 << kBlockShift; // Used to indicate that a point must be encoded as an exception (a 24-byte // S2Point) rather than as an S2CellId. -constexpr uint64 kException = ~0ULL; +static constexpr uint64 kException = ~0ULL; // Represents the encoding parameters to be used for a given block (consisting // of kBlockSize encodable 64-bit values). See below. @@ -221,9 +255,7 @@ struct BlockCode { }; // Returns a bit mask with "n" low-order 1 bits, for 0 <= n <= 64. -inline uint64 BitMask(int n) { - return (n == 0) ? 0 : (~0ULL >> (64 - n)); -} +inline uint64 BitMask(int n) { return (n == 0) ? 0 : (~0ULL >> (64 - n)); } // Returns the maximum number of bits per value at the given S2CellId level. inline int MaxBitsForLevel(int level) { @@ -240,9 +272,9 @@ inline int BaseShift(int level, int base_bits) { // Forward declarations. int ChooseBestLevel(Span points, vector* cell_points); vector ConvertCellsToValues(const vector& cell_points, - int level, bool* have_exceptions); -uint64 ChooseBase(const vector& values, int level, bool have_exceptions, - int* base_bits); + int level, bool* have_exceptions); +uint64 ChooseBase(const vector& values, int level, + bool have_exceptions, int* base_bits); BlockCode GetBlockCode(Span values, uint64 base, bool have_exceptions); @@ -292,9 +324,9 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { // except that it is faster to decode and the spatial locality is not quite // as good. // - // The 64-bit values are divided into blocks of size 8, and then each value is - // encoded as the sum of a base value, a per-block offset, and a per-value - // delta within that block: + // The 64-bit values are divided into blocks of size kBlockSize, and then + // each value is encoded as the sum of a base value, a per-block offset, and + // a per-value delta within that block: // // v[i,j] = base + offset[i] + delta[i, j] // @@ -380,10 +412,11 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { // // If there are any points that could not be represented as S2CellIds, then // "have_exceptions" in the header is true. In that case the delta values - // within each block are encoded as (delta + 8), and values 0-7 are used to - // represent exceptions. If a block has exceptions, they are encoded - // immediately following the array of deltas, and are referenced by encoding - // the corresponding exception index (0-7) as the delta. + // within each block are encoded as (delta + kBlockSize), and values + // 0...kBlockSize-1 are used to represent exceptions. If a block has + // exceptions, they are encoded immediately following the array of deltas, + // and are referenced by encoding the corresponding exception index + // 0...kBlockSize-1 as the delta. // // TODO(ericv): A vector containing a single leaf cell is currently encoded as // 13 bytes (2 byte header, 7 byte base, 1 byte block count, 1 byte block @@ -394,7 +427,7 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { // (3 bits), followed by the S2CellId bytes. The extra 2 header bits could be // used to store single points using other encodings, e.g. E7. // - // If we wind up using 8-value blocks, we could also use the extra bit in the + // If we had used 8-value blocks, we could have used the extra bit in the // first byte of the header to indicate that there is only one value, and // then skip the 2nd byte of header and the EncodedStringVector. But this // would be messy because it also requires special cases while decoding. @@ -414,8 +447,8 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { // // TODO(ericv): Benchmark using shifted S2CellIds instead. bool have_exceptions; - vector values = ConvertCellsToValues(cell_points, level, - &have_exceptions); + vector values = + ConvertCellsToValues(cell_points, level, &have_exceptions); // 3. Choose the global encoding parameter "base" (consisting of the bit // prefix shared by all values to be encoded). @@ -443,10 +476,7 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { // Now we encode the contents of each block. StringVectorEncoder blocks; vector exceptions; - uint64 offset_bytes_sum = 0; - uint64 delta_nibbles_sum = 0; - uint64 exceptions_sum = 0; - for (int i = 0; i < values.size(); i += kBlockSize) { + for (size_t i = 0; i < values.size(); i += kBlockSize) { int block_size = min(kBlockSize, values.size() - i); BlockCode code = GetBlockCode(MakeSpan(&values[i], block_size), base, have_exceptions); @@ -515,9 +545,6 @@ void EncodeS2PointVectorCompact(Span points, Encoder* encoder) { block->Ensure(exceptions_bytes); block->putn(exceptions.data(), exceptions_bytes); } - offset_bytes_sum += offset_bytes; - delta_nibbles_sum += delta_nibbles; - exceptions_sum += num_exceptions; } blocks.Encode(encoder); } @@ -569,7 +596,7 @@ int ChooseBestLevel(Span points, // indicated by the value "kException". "have_exceptions" is set to indicate // whether any exceptions were present. vector ConvertCellsToValues(const vector& cell_points, - int level, bool* have_exceptions) { + int level, bool* have_exceptions) { vector values; values.reserve(cell_points.size()); *have_exceptions = false; @@ -595,8 +622,8 @@ vector ConvertCellsToValues(const vector& cell_points, return values; } -uint64 ChooseBase(const vector& values, int level, bool have_exceptions, - int* base_bits) { +uint64 ChooseBase(const vector& values, int level, + bool have_exceptions, int* base_bits) { // Find the minimum and maximum non-exception values to be represented. uint64 v_min = kException, v_max = 0; for (auto v : values) { @@ -619,8 +646,8 @@ uint64 ChooseBase(const vector& values, int level, bool have_exceptions, // 2. The format only allows us to represent up to 7 bytes (56 bits) of // "base", so we need to ensure that "base" conforms to this requirement. int min_delta_bits = (have_exceptions || values.size() == 1) ? 8 : 4; - int excluded_bits = max(Bits::Log2Floor64(v_min ^ v_max) + 1, - max(min_delta_bits, BaseShift(level, 56))); + int excluded_bits = max(absl::bit_width(v_min ^ v_max), + max(min_delta_bits, BaseShift(level, 56))); uint64 base = v_min & ~BitMask(excluded_bits); // Determine how many bytes are needed to represent this prefix. @@ -645,8 +672,8 @@ uint64 ChooseBase(const vector& values, int level, bool have_exceptions, // Returns true if the range of values [d_min, d_max] can be encoded using the // specified parameters (delta_bits, overlap_bits, and have_exceptions). -bool CanEncode(uint64 d_min, uint64 d_max, int delta_bits, - int overlap_bits, bool have_exceptions) { +bool CanEncode(uint64 d_min, uint64 d_max, int delta_bits, int overlap_bits, + bool have_exceptions) { // "offset" can't represent the lowest (delta_bits - overlap_bits) of d_min. d_min &= ~BitMask(delta_bits - overlap_bits); @@ -721,7 +748,8 @@ BlockCode GetBlockCode(Span values, uint64 base, // // It is possible to show that this last example is the worst case, i.e. we // do not need to consider increasing delta_bits or overlap_bits further. - int delta_bits = (max(1, Bits::Log2Floor64(b_max - b_min)) + 3) & ~3; + int delta_bits = + (max(1, static_cast(absl::bit_width(b_max - b_min)) - 1) + 3) & ~3; int overlap_bits = 0; if (!CanEncode(b_min, b_max, delta_bits, 0, have_exceptions)) { if (CanEncode(b_min, b_max, delta_bits, 4, have_exceptions)) { @@ -736,9 +764,12 @@ BlockCode GetBlockCode(Span values, uint64 base, } } - // Avoid wasting 4 bits of delta when the block size is 1. This reduces the - // encoding size for single leaf cells by one byte. - if (values.size() == 1) { + // When the block size is 1 and no exceptions exist, we have delta_bits == 4 + // and overlap_bits == 0 which wastes 4 bits. We fix this below, which + // among other things reduces the encoding size for single leaf cells by one + // byte. (Note that when exceptions exist, delta_bits == 8 and overlap_bits + // may be 0 or 4. These cases are covered by the unit tests.) + if (values.size() == 1 && !have_exceptions) { S2_DCHECK(delta_bits == 4 && overlap_bits == 0); delta_bits = 8; } @@ -798,7 +829,8 @@ S2Point EncodedS2PointVector::DecodeCellIdsFormat(int i) const { // Decode the offset for this block. int offset_shift = (delta_nibbles - overlap_nibbles) << 2; - uint64 offset = GetUintWithLength(ptr, offset_bytes) << offset_shift; + uint64 offset = GetUintWithLength(ptr, offset_bytes) + << offset_shift; ptr += offset_bytes; // Decode the delta for the requested value. diff --git a/src/s2/encoded_s2point_vector.h b/src/s2/encoded_s2point_vector.h index 44ce5eb0..470322d3 100644 --- a/src/s2/encoded_s2point_vector.h +++ b/src/s2/encoded_s2point_vector.h @@ -18,19 +18,22 @@ #ifndef S2_ENCODED_S2POINT_VECTOR_H_ #define S2_ENCODED_S2POINT_VECTOR_H_ +#include + #include +#include + +#include "s2/base/integral_types.h" #include "absl/types/span.h" +#include "s2/util/coding/coder.h" #include "s2/encoded_string_vector.h" #include "s2/encoded_uint_vector.h" +#include "s2/s2coder.h" #include "s2/s2point.h" +#include "s2/s2shape.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 class CodingHint : uint8 { FAST, COMPACT }; - // Encodes a vector of S2Points in a format that can later be decoded as an // EncodedS2PointVector. // @@ -68,6 +71,10 @@ class EncodedS2PointVector { // Decodes and returns the entire original vector. std::vector Decode() const; + // Copy the encoded data to the encoder. This allows for "reserialization" of + // encoded shapes created through lazy decoding. + void Encode(Encoder* encoder) const; + // TODO(ericv): Consider adding a method that returns an adjacent pair of // points. This would save some decoding overhead. @@ -90,26 +97,6 @@ class EncodedS2PointVector { // 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. - - // dd: These structs are anonymous in the upstream S2 code; however, - // this generates CMD-check failure due to the [-Wnested-anon-types] - // (anonymous types declared in an anonymous union are an extension) - // The approach here just names the types. - struct CellIDStruct { - EncodedStringVector blocks; - uint64 base; - uint8 level; - bool have_exceptions; - - // TODO(ericv): Use std::atomic_flag to cache the last point decoded in - // a thread-safe way. This reduces benchmark times for actual polygon - // operations (e.g. S2ClosestEdgeQuery) by about 15%. - }; - - struct UncompressedStruct { - const S2Point* points; - }; - enum Format : uint8 { UNCOMPRESSED = 0, CELL_IDS = 1, @@ -117,8 +104,19 @@ class EncodedS2PointVector { Format format_; uint32 size_; union { - struct UncompressedStruct uncompressed_; - struct CellIDStruct cell_ids_; + struct { + const S2Point* points; + } uncompressed_; + struct { + EncodedStringVector blocks; + uint64 base; + uint8 level; + bool have_exceptions; + + // TODO(ericv): Use std::atomic_flag to cache the last point decoded in + // a thread-safe way. This reduces benchmark times for actual polygon + // operations (e.g. S2ClosestEdgeQuery) by about 15%. + } cell_ids_; }; }; @@ -139,7 +137,7 @@ inline S2Point EncodedS2PointVector::operator[](int i) const { return DecodeCellIdsFormat(i); default: - S2_LOG(DFATAL) << "Unrecognized format"; + S2_DLOG(FATAL) << "Unrecognized format"; return S2Point(); } } diff --git a/src/s2/encoded_s2shape_index.cc b/src/s2/encoded_s2shape_index.cc index e68f2e48..5b6adad9 100644 --- a/src/s2/encoded_s2shape_index.cc +++ b/src/s2/encoded_s2shape_index.cc @@ -17,23 +17,28 @@ #include "s2/encoded_s2shape_index.h" +#include + +#include #include -#include "absl/memory/memory.h" +#include + +#include "s2/base/casts.h" +#include "s2/base/integral_types.h" +#include "s2/util/bits/bits.h" +#include "s2/util/coding/coder.h" +#include "s2/encoded_s2cell_id_vector.h" +#include "s2/encoded_string_vector.h" #include "s2/mutable_s2shape_index.h" +#include "s2/s2cell_id.h" +#include "s2/s2point.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" -using absl::make_unique; +using std::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::Iterator::Clone() const { return make_unique(*this); @@ -49,33 +54,51 @@ S2Shape* EncodedS2ShapeIndex::GetShape(int id) const { if (shape) shape->id_ = id; S2Shape* expected = kUndecodedShape(); if (shapes_[id].compare_exchange_strong(expected, shape.get(), - std::memory_order_relaxed)) { + std::memory_order_acq_rel)) { return shape.release(); // Ownership has been transferred to shapes_. } - return shapes_[id].load(std::memory_order_relaxed); + return expected; // Another thread updated shapes_[id] first. } inline const S2ShapeIndexCell* EncodedS2ShapeIndex::GetCell(int i) const { - if (cell_decoded(i)) { - auto cell = cells_[i].load(std::memory_order_acquire); - if (cell != nullptr) return cell; - } - // We decode the cell before acquiring the spinlock in order to minimize the + // memory_order_release ensures that no reads or writes in the current + // thread can be reordered after this store, and all writes in the current + // thread are visible to other threads that acquire the same atomic + // variable. + // + // memory_order_acquire ensures that no reads or writes in the current + // thread can be reordered before this load, and all writes in other threads + // that release the same atomic variable are visible in this thread. + // + // We use this to implement lock-free synchronization on the read path as + // follows: + // + // 1. cells_decoded(i) is updated using acquire/release semantics + // 2. cells_[i] is written before cells_decoded(i) + // 3. cells_[i] is read after cells_decoded(i) + // + // Note that we do still use a lock for the write path to ensure that + // cells_[i] and cell_decoded(i) are updated together atomically. + if (cell_decoded(i)) return cells_[i]; + + // Decode the cell before acquiring the spinlock in order to minimize the // time that the lock is held. auto cell = make_unique(); Decoder decoder = encoded_cells_.GetDecoder(i); if (!cell->Decode(num_shape_ids(), &decoder)) { return nullptr; } + // Recheck cell_decoded(i) once we hold the lock in case another thread + // has decoded this cell in the meantime. SpinLockHolder l(&cells_lock_); - if (test_and_set_cell_decoded(i)) { - // This cell has already been decoded. - return cells_[i].load(std::memory_order_relaxed); - } - if (cell_cache_.size() < max_cell_cache_size()) { + if (cell_decoded(i)) return cells_[i]; + + // Update the cell, setting cells_[i] before cell_decoded(i). + cells_[i] = cell.get(); + set_cell_decoded(i); + if (cell_cache_.size() < static_cast(max_cell_cache_size())) { cell_cache_.push_back(i); } - cells_[i].store(cell.get(), std::memory_order_relaxed); return cell.release(); // Ownership has been transferred to cells_. } @@ -83,8 +106,7 @@ const S2ShapeIndexCell* EncodedS2ShapeIndex::Iterator::GetCell() const { return index_->GetCell(cell_pos_); } -EncodedS2ShapeIndex::EncodedS2ShapeIndex() { -} +EncodedS2ShapeIndex::EncodedS2ShapeIndex() = default; EncodedS2ShapeIndex::~EncodedS2ShapeIndex() { // Although Minimize() does slightly more than required for destruction @@ -107,7 +129,7 @@ bool EncodedS2ShapeIndex::Init(Decoder* decoder, // AtomicShape is a subtype of std::atomic that changes the // default constructor value to kUndecodedShape(). This saves the effort of // initializing all the elements twice. - shapes_ = std::vector(shape_factory.size()); + shapes_ = vector(shape_factory.size()); shape_factory_ = shape_factory.Clone(); if (!cell_ids_.Init(decoder)) return false; @@ -118,17 +140,18 @@ bool EncodedS2ShapeIndex::Init(Decoder* decoder, // need to initialize one bit per cell to zero. // // For very large S2ShapeIndexes the internal memset() call to initialize - // cells_decoded_ still takes about 4 microseconds per million cells, but - // this seems reasonable relative to other likely costs (I/O, etc). + // cells_decoded_ still takes about 1.3 microseconds per million cells + // (assuming an optimized implementation that writes 32 bytes per cycle), + // but this seems reasonable relative to other likely costs (I/O, etc). // // NOTE(ericv): DO NOT use make_unique<> here! make_unique<> allocates memory // using "new T[n]()", which initializes all elements of the array. This // slows down some benchmarks by over 100x. // - // cells_ = make_unique[]>(cell_ids_.size()); + // cells_ = make_unique[](cell_ids_.size()); // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // NO NO NO - cells_.reset(new std::atomic[cell_ids_.size()]); + cells_.reset(new S2ShapeIndexCell*[cell_ids_.size()]); cells_decoded_ = vector>((cell_ids_.size() + 63) >> 6); return encoded_cells_.Init(decoder); @@ -144,23 +167,23 @@ void EncodedS2ShapeIndex::Minimize() { delete shape; } } - if (cell_cache_.size() < max_cell_cache_size()) { + if (cell_cache_.size() < static_cast(max_cell_cache_size())) { // When only a tiny fraction of the cells are decoded, we keep track of // those cells in cell_cache_ to avoid the cost of scanning the // cells_decoded_ vector. (The cost is only about 1 cycle per 64 cells, // but for a huge polygon with 1 million cells that's still 16000 cycles.) for (int pos : cell_cache_) { cells_decoded_[pos >> 6].store(0, std::memory_order_relaxed); - delete cells_[pos].load(std::memory_order_relaxed); + delete cells_[pos]; } } else { // Scan the cells_decoded_ vector looking for cells that must be deleted. - for (int i = cells_decoded_.size(), base = 0; --i >= 0; base += 64) { + for (int i = cells_decoded_.size(); --i >= 0;) { uint64 bits = cells_decoded_[i].load(std::memory_order_relaxed); if (bits == 0) continue; do { int offset = Bits::FindLSBSetNonZero64(bits); - delete cells_[(i << 6) + offset].load(std::memory_order_relaxed); + delete cells_[(i << 6) + offset]; bits &= bits - 1; } while (bits != 0); cells_decoded_[i].store(0, std::memory_order_relaxed); @@ -170,7 +193,7 @@ void EncodedS2ShapeIndex::Minimize() { } size_t EncodedS2ShapeIndex::SpaceUsed() const { - // TODO(ericv): Add SpaceUsed() method to S2Shape base class,and Include + // 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); diff --git a/src/s2/encoded_s2shape_index.h b/src/s2/encoded_s2shape_index.h index 3232474b..540a3154 100644 --- a/src/s2/encoded_s2shape_index.h +++ b/src/s2/encoded_s2shape_index.h @@ -18,10 +18,112 @@ #ifndef S2_ENCODED_S2SHAPE_INDEX_H_ #define S2_ENCODED_S2SHAPE_INDEX_H_ +#include + +#include +#include +#include + +#include "s2/base/integral_types.h" +#include "absl/strings/cord.h" +#include "s2/util/coding/coder.h" #include "s2/encoded_s2cell_id_vector.h" #include "s2/encoded_string_vector.h" #include "s2/mutable_s2shape_index.h" - +#include "s2/s2cell_id.h" +#include "s2/s2point.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" + +// EncodedS2ShapeIndex is an S2ShapeIndex implementation that works directly +// with encoded data. Rather than decoding everything in advance, geometry is +// decoded incrementally (down to individual edges) as needed. It can be +// initialized from a single block of data in nearly constant time (about 1.3 +// microseconds per million edges). This saves large amounts of memory and is +// also much faster in the common situation where geometric data is loaded +// from somewhere, decoded, and then only a single operation is performed on +// it. It supports all S2ShapeIndex operations including boolean operations, +// measuring distances, etc. +// +// The speedups can be over 1000x for large geometric objects. For example +// vertices and 50,000 loops. If this geometry is represented as an +// S2Polygon, then simply decoding it takes ~250ms and building its internal +// S2ShapeIndex takes a further ~1500ms. These times are much longer than the +// time needed for many operations, e.g. e.g. measuring the distance from the +// polygon to one of its vertices takes only about 0.001ms. +// +// If the same geometry is represented using EncodedLaxPolygonShape and +// EncodedS2ShapeIndex, initializing the index takes only 0.005ms. The +// distance measuring operation itself takes slightly longer than before +// (0.0013ms vs. the original 0.001ms) but the overall time is now much lower +// (~0.007ms vs. 1750ms). This is possible because the new classes decode +// data lazily (only when it is actually needed) and with fine granularity +// (down to the level of individual edges). The overhead associated with this +// incremental decoding is small; operations are typically 25% slower compared +// to fully decoding the MutableS2ShapeIndex and its underlying shapes. +// +// EncodedS2ShapeIndex also uses less memory than MutableS2ShapeIndex. The +// encoded data is contiguous block of memory that is typically between 4-20% +// of the original index size (see MutableS2ShapeIndex::Encode for examples). +// Constructing the EncodedS2ShapeIndex uses additional memory, but even so +// the total memory usage immediately after construction is typically 25-35% +// of the corresponding MutableS2ShapeIndex size. +// +// Note that MutableS2ShapeIndex will still be faster and use less memory if +// you need to decode the entire index. Similarly MutableS2ShapeIndex will be +// faster if you plan to execute a large number of operations on it. The main +// advantage of EncodedS2ShapeIndex is that it is much faster and uses less +// memory when only a small portion of the data needs to be decoded. +// +// Example code showing how to create an encoded index: +// +// Encoder encoder; +// s2shapeutil::CompactEncodeTaggedShapes(index, encoder); +// index.Encode(encoder); +// string encoded(encoder.base(), encoder.length()); // Encoded data. +// +// Example code showing how to use an encoded index: +// +// Decoder decoder(encoded.data(), encoded.size()); +// EncodedS2ShapeIndex index; +// index.Init(&decoder, s2shapeutil::LazyDecodeShapeFactory(&decoder)); +// S2ClosestEdgeQuery query(&index); +// S2ClosestEdgeQuery::PointTarget target(test_point); +// if (query.IsDistanceLessOrEqual(&target, limit)) { +// ... +// } +// +// Note that EncodedS2ShapeIndex does not make a copy of the encoded data, and +// therefore the client must ensure that this data outlives the +// EncodedS2ShapeIndex object. +// +// There are a number of built-in classes that work with S2ShapeIndex objects. +// Generally these classes accept any collection of geometry that can be +// represented by an S2ShapeIndex, i.e. any combination of points, polylines, +// and polygons. Such classes include: +// +// - S2ContainsPointQuery: returns the shape(s) that contain a given point. +// +// - S2ClosestEdgeQuery: returns the closest edge(s) to a given point, edge, +// S2CellId, or S2ShapeIndex. +// +// - S2CrossingEdgeQuery: returns the edge(s) that cross a given edge. +// +// - S2BooleanOperation: computes boolean operations such as union, +// and boolean predicates such as containment. +// +// - S2ShapeIndexRegion: can be used together with S2RegionCoverer to +// approximate geometry as a set of S2CellIds. +// +// - S2ShapeIndexBufferedRegion: computes approximations that have been +// expanded by a given radius. +// +// EncodedS2ShapeIndex is thread-compatible, meaning that const methods are +// thread safe, and non-const methods are not thread safe. The only non-const +// method is Minimize(), so if you plan to call Minimize() while other threads +// are actively using the index that you must use an external reader-writer +// lock such as absl::Mutex to guard access to it. (There is no global state +// and therefore each index can be guarded independently.) class EncodedS2ShapeIndex final : public S2ShapeIndex { public: using Options = MutableS2ShapeIndex::Options; @@ -88,14 +190,20 @@ class EncodedS2ShapeIndex final : public S2ShapeIndex { // bool done() const; // S2Point center() const; - // IteratorBase API: + // S2CellIterator 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; + + bool Locate(const S2Point& target) override { + return LocateImpl(*this, target); + } + + S2CellRelation Locate(S2CellId target) override { + return LocateImpl(*this, target); + } protected: const S2ShapeIndexCell* GetCell() const override; @@ -134,7 +242,7 @@ class EncodedS2ShapeIndex final : public S2ShapeIndex { S2Shape* GetShape(int id) const; const S2ShapeIndexCell* GetCell(int i) const; bool cell_decoded(int i) const; - bool test_and_set_cell_decoded(int i) const; + void set_cell_decoded(int i) const; int max_cell_cache_size() const; std::unique_ptr shape_factory_; @@ -156,7 +264,7 @@ class EncodedS2ShapeIndex final : public S2ShapeIndex { // A raw array containing the decoded contents of each cell in the index. // Initially all values are *uninitialized memory*. The cells_decoded_ // field below keeps track of which elements are present. - mutable std::unique_ptr[]> cells_; + mutable std::unique_ptr cells_; // A bit vector indicating which elements of cells_ have been decoded. // All other elements of cells_ contain uninitialized (random) memory. @@ -236,28 +344,31 @@ inline void EncodedS2ShapeIndex::Iterator::Seek(S2CellId target) { inline std::unique_ptr EncodedS2ShapeIndex::NewIterator(InitialPosition pos) const { - return absl::make_unique(this, pos); + return std::make_unique(this, pos); } inline S2Shape* EncodedS2ShapeIndex::shape(int id) const { - S2Shape* shape = shapes_[id].load(std::memory_order_relaxed); + S2Shape* shape = shapes_[id].load(std::memory_order_acquire); if (shape != kUndecodedShape()) return shape; return GetShape(id); } -// Returns true if the given cell has been decoded yet. +// Returns true if the given cell has already been decoded. inline bool EncodedS2ShapeIndex::cell_decoded(int i) const { - uint64 group_bits = cells_decoded_[i >> 6].load(std::memory_order_relaxed); + // cell_decoded(i) uses acquire/release synchronization (see .cc file). + uint64 group_bits = cells_decoded_[i >> 6].load(std::memory_order_acquire); return (group_bits & (1ULL << (i & 63))) != 0; } -// Marks the given cell as decoded and returns true if it was already marked. -inline bool EncodedS2ShapeIndex::test_and_set_cell_decoded(int i) const { +// Marks the given cell as having been decoded. +// REQUIRES: cells_lock_ is held +inline void EncodedS2ShapeIndex::set_cell_decoded(int i) const { + // We use memory_order_release for the store operation below to ensure that + // cells_decoded(i) sees the most recent value, however we can use + // memory_order_relaxed for the load because cells_lock_ is held. std::atomic* group = &cells_decoded_[i >> 6]; - uint64 group_bits = group->load(std::memory_order_relaxed); - uint64 test_bit = 1ULL << (i & 63); - group->store(group_bits | test_bit, std::memory_order_relaxed); - return (group_bits & test_bit) != 0; + uint64 bits = group->load(std::memory_order_relaxed); + group->store(bits | 1ULL << (i & 63), std::memory_order_release); } inline int EncodedS2ShapeIndex::max_cell_cache_size() const { diff --git a/src/s2/encoded_string_vector.cc b/src/s2/encoded_string_vector.cc index 2a6d7a58..10c775ca 100644 --- a/src/s2/encoded_string_vector.cc +++ b/src/s2/encoded_string_vector.cc @@ -17,15 +17,27 @@ #include "s2/encoded_string_vector.h" +#include +#include + +#include +#include + +#include "s2/base/integral_types.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "s2/util/coding/coder.h" +#include "s2/encoded_uint_vector.h" + using absl::MakeSpan; using absl::Span; using absl::string_view; +using std::string; using std::vector; namespace s2coding { -StringVectorEncoder::StringVectorEncoder() { -} +StringVectorEncoder::StringVectorEncoder() = default; void StringVectorEncoder::Encode(Encoder* encoder) { offsets_.push_back(data_.length()); @@ -37,7 +49,7 @@ void StringVectorEncoder::Encode(Encoder* encoder) { encoder->putn(data_.base(), data_.length()); } -void StringVectorEncoder::Encode(Span v, Encoder* encoder) { +void StringVectorEncoder::Encode(Span v, Encoder* encoder) { StringVectorEncoder string_vector; for (const auto& str : v) string_vector.Add(str); string_vector.Encode(encoder); @@ -45,7 +57,7 @@ void StringVectorEncoder::Encode(Span v, Encoder* encoder) { bool EncodedStringVector::Init(Decoder* decoder) { if (!offsets_.Init(decoder)) return false; - data_ = reinterpret_cast(decoder->ptr()); + data_ = decoder->skip(0); if (offsets_.size() > 0) { uint64 length = offsets_[offsets_.size() - 1]; if (decoder->avail() < length) return false; @@ -57,10 +69,21 @@ bool EncodedStringVector::Init(Decoder* decoder) { vector EncodedStringVector::Decode() const { size_t n = size(); vector result(n); - for (int i = 0; i < n; ++i) { + for (size_t i = 0; i < n; ++i) { result[i] = (*this)[i]; } return result; } +// The encoding must be identical to StringVectorEncoder::Encode(). +void EncodedStringVector::Encode(Encoder* encoder) const { + offsets_.Encode(encoder); + + if (offsets_.size() > 0) { + const uint64 length = offsets_[offsets_.size() - 1]; + encoder->Ensure(length); + encoder->putn(data_, length); + } +} + } // namespace s2coding diff --git a/src/s2/encoded_string_vector.h b/src/s2/encoded_string_vector.h index bef16977..da6b6a08 100644 --- a/src/s2/encoded_string_vector.h +++ b/src/s2/encoded_string_vector.h @@ -18,10 +18,16 @@ #ifndef S2_ENCODED_STRING_VECTOR_H_ #define S2_ENCODED_STRING_VECTOR_H_ +#include + #include #include +#include + +#include "s2/base/integral_types.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" +#include "s2/util/coding/coder.h" #include "s2/encoded_uint_vector.h" namespace s2coding { @@ -84,7 +90,7 @@ class StringVectorEncoder { class EncodedStringVector { public: // Constructs an uninitialized object; requires Init() to be called. - EncodedStringVector() {} + EncodedStringVector() = default; // Initializes the EncodedStringVector. Returns false on errors, leaving // the vector in an unspecified state. @@ -113,6 +119,8 @@ class EncodedStringVector { // no longer needed. std::vector Decode() const; + void Encode(Encoder* encoder) const; + private: EncodedUintVector offsets_; const char* data_; diff --git a/src/s2/encoded_uint_vector.h b/src/s2/encoded_uint_vector.h index 9ca50ba0..c4a3732a 100644 --- a/src/s2/encoded_uint_vector.h +++ b/src/s2/encoded_uint_vector.h @@ -18,11 +18,19 @@ #ifndef S2_ENCODED_UINT_VECTOR_H_ #define S2_ENCODED_UINT_VECTOR_H_ +#include + +#include #include #include + +#include "s2/base/integral_types.h" #include "absl/base/internal/unaligned_access.h" #include "absl/types/span.h" + +#include "s2/util/bits/bits.h" #include "s2/util/coding/coder.h" +#include "s2/util/coding/varint.h" namespace s2coding { @@ -59,7 +67,7 @@ class EncodedUintVector { static_assert(sizeof(T) & 0xe, "Unsupported integer length"); // Constructs an uninitialized object; requires Init() to be called. - EncodedUintVector() {} + EncodedUintVector() = default; // Initializes the EncodedUintVector. Returns false on errors, leaving the // vector in an unspecified state. @@ -85,6 +93,8 @@ class EncodedUintVector { // Decodes and returns the entire original vector. std::vector Decode() const; + void Encode(Encoder* encoder) const; + private: template size_t lower_bound(T target) const; @@ -111,7 +121,7 @@ void EncodeUintWithLength(T value, int length, Encoder* encoder); // REQUIRES: 2 <= sizeof(T) <= 8 // REQUIRES: 0 <= length <= sizeof(T) template -T GetUintWithLength(const void* ptr, int length); +T GetUintWithLength(const char* 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. @@ -185,8 +195,8 @@ inline T GetUintWithLength(const char* ptr, int length) { template bool DecodeUintWithLength(int length, Decoder* decoder, T* result) { - if (decoder->avail() < length) return false; - const char* ptr = reinterpret_cast(decoder->ptr()); + if (decoder->avail() < static_cast(length)) return false; + const char* ptr = decoder->skip(0); *result = GetUintWithLength(ptr, length); decoder->skip(length); return true; @@ -223,9 +233,9 @@ bool EncodedUintVector::Init(Decoder* decoder) { size_ = size_len / sizeof(T); // Optimized into bit shift. len_ = (size_len & (sizeof(T) - 1)) + 1; if (size_ > std::numeric_limits::max() / sizeof(T)) return false; - size_t bytes = size_ * len_; + size_t bytes = static_cast(size_) * static_cast(len_); if (decoder->avail() < bytes) return false; - data_ = reinterpret_cast(decoder->ptr()); + data_ = decoder->skip(0); decoder->skip(bytes); return true; } @@ -294,6 +304,16 @@ std::vector EncodedUintVector::Decode() const { return result; } +template +// The encoding must be identical to StringVectorEncoder::Encode(). +void EncodedUintVector::Encode(Encoder* encoder) const { + uint64 size_len = (uint64{size_} * sizeof(T)) | (len_ - 1); + + encoder->Ensure(Varint::kMax64 + size_len); + encoder->put_varint64(size_len); + encoder->putn(data_, size_ * len_); +} + } // namespace s2coding #endif // S2_ENCODED_UINT_VECTOR_H_ diff --git a/src/s2/id_set_lexicon.cc b/src/s2/id_set_lexicon.cc index f4dd9114..9f7c6c2e 100644 --- a/src/s2/id_set_lexicon.cc +++ b/src/s2/id_set_lexicon.cc @@ -18,15 +18,17 @@ #include "s2/id_set_lexicon.h" #include +#include #include -#include "s2/base/logging.h" +#include "s2/base/integral_types.h" +#include "s2/sequence_lexicon.h" -IdSetLexicon::IdSetLexicon() { -} +using std::vector; -IdSetLexicon::~IdSetLexicon() { -} +IdSetLexicon::IdSetLexicon() = default; + +IdSetLexicon::~IdSetLexicon() = default; // We define the copy/move constructors and assignment operators explicitly // in order to avoid copying/moving the temporary storage vector "tmp_". @@ -51,7 +53,7 @@ void IdSetLexicon::Clear() { id_sets_.Clear(); } -int32 IdSetLexicon::AddInternal(std::vector* ids) { +int32 IdSetLexicon::AddInternal(vector* ids) { if (ids->empty()) { // Empty sets have a special id chosen not to conflict with other ids. return kEmptySetId; @@ -62,6 +64,10 @@ int32 IdSetLexicon::AddInternal(std::vector* ids) { // Canonicalize the set by sorting and removing duplicates. std::sort(ids->begin(), ids->end()); ids->erase(std::unique(ids->begin(), ids->end()), ids->end()); + + // After eliminating duplicates, we may now have a singleton. + if (ids->size() == 1) return (*ids)[0]; + // Non-singleton sets are represented by the bitwise complement of the id // returned by SequenceLexicon. return ~id_sets_.Add(*ids); diff --git a/src/s2/id_set_lexicon.h b/src/s2/id_set_lexicon.h index 25ace84c..db8443b9 100644 --- a/src/s2/id_set_lexicon.h +++ b/src/s2/id_set_lexicon.h @@ -18,6 +18,9 @@ #ifndef S2_ID_SET_LEXICON_H_ #define S2_ID_SET_LEXICON_H_ +#include + +#include #include #include @@ -65,8 +68,8 @@ // This class is similar to SequenceLexicon, except: // // 1. Empty and singleton sets are represented implicitly; they use no space. -// 2. Sets are represented rather than sequences; the ordering of values is -// not important and duplicates are removed. +// 2. Sets are represented rather than sequences; values are reordered to be in +// sorted order, and duplicates are removed. // 3. The values must be 32-bit non-negative integers (only). class IdSetLexicon { public: @@ -114,6 +117,8 @@ class IdSetLexicon { // This class represents a set of integers stored in the IdSetLexicon. class IdSet { public: + using value_type = const int32; + Iterator begin() const; Iterator end() const; size_t size() const; @@ -132,7 +137,7 @@ class IdSetLexicon { private: // Choose kEmptySetId to be the last id that will ever be generated. // (Non-negative ids are reserved for singleton sets.) - static const int32 kEmptySetId = std::numeric_limits::min(); + static constexpr int32 kEmptySetId = std::numeric_limits::min(); int32 AddInternal(std::vector* ids); SequenceLexicon id_sets_; diff --git a/src/s2/mutable_s2shape_index.cc b/src/s2/mutable_s2shape_index.cc index a81ee9cf..3e29fed6 100644 --- a/src/s2/mutable_s2shape_index.cc +++ b/src/s2/mutable_s2shape_index.cc @@ -17,13 +17,25 @@ #include "s2/mutable_s2shape_index.h" +#include + #include #include #include +#include +#include +#include #include "s2/base/casts.h" #include "s2/base/commandlineflags.h" -#include "s2/base/spinlock.h" +#include "s2/base/integral_types.h" +#include "absl/base/attributes.h" +#include "absl/container/btree_map.h" +#include "absl/flags/flag.h" +#include "absl/synchronization/mutex.h" +#include "absl/utility/utility.h" +#include "s2/util/coding/coder.h" +#include "s2/util/coding/varint.h" #include "s2/encoded_s2cell_id_vector.h" #include "s2/encoded_string_vector.h" #include "s2/r1interval.h" @@ -34,30 +46,38 @@ #include "s2/s2coords.h" #include "s2/s2edge_clipping.h" #include "s2/s2edge_crosser.h" +#include "s2/s2memory_tracker.h" #include "s2/s2metrics.h" #include "s2/s2padded_cell.h" -#include "s2/s2pointutil.h" +#include "s2/s2point.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" #include "s2/s2shapeutil_contains_brute_force.h" +#include "s2/s2shapeutil_shape_edge_id.h" +#include "s2/util/gtl/compact_array.h" +#include "s2/util/math/mathutil.h" using std::fabs; +using std::make_pair; +using std::make_unique; using std::max; +using std::min; using std::unique_ptr; using std::vector; // FLAGS_s2shape_index_default_max_edges_per_cell // -// The default maximum number of edges per cell (not counting "long" edges). +// The default maximum number of edges per cell (not counting 'long' edges). // If a cell has more than this many edges, and it is not a leaf cell, then it // is subdivided. This flag can be overridden via MutableS2ShapeIndex::Options. // Reasonable values range from 10 to about 50 or so. -DEFINE_int32( +S2_DEFINE_int32( s2shape_index_default_max_edges_per_cell, 10, - "Default maximum number of edges (not counting 'long' edges) per cell; " + "Default maximum number of edges per cell (not counting 'long' edges); " "reasonable values range from 10 to 50. Small values makes queries " - "faster, while large values make construction faster and use less " - "memory."); + "faster, while large values make construction faster and use less memory."); -// FLAGS_s2shape_index_tmp_memory_budget_mb +// FLAGS_s2shape_index_tmp_memory_budget // // Attempt to limit the amount of temporary memory allocated while building or // updating a MutableS2ShapeIndex to at most this value. This is achieved by @@ -70,33 +90,88 @@ DEFINE_int32( // with huge numbers of edges may exceed the budget; // (3) shapes being removed are always processed in a single batch. (This // could be fixed, but it seems better to keep the code simpler for now.) -DEFINE_int32( - s2shape_index_tmp_memory_budget_mb, 100, +S2_DEFINE_int64( + s2shape_index_tmp_memory_budget, int64{100} << 20 /*100 MB*/, "Attempts to limit the amount of temporary memory used by " - "MutableS2ShapeIndex when creating or updating very large indexes " - "to at most this value. If more memory than this is needed, updates " + "MutableS2ShapeIndex when creating or updating very large indexes to at " + "most this number of bytes. If more memory than this is needed, updates " "will automatically be split into batches internally."); // FLAGS_s2shape_index_cell_size_to_long_edge_ratio // -// The cell size relative to the length of an edge at which it is first -// considered to be "long". Long edges do not contribute toward the decision -// to subdivide a cell further. For example, a value of 2.0 means that the -// cell must be at least twice the size of the edge in order for that edge to -// be counted. There are two reasons for not counting long edges: (1) such -// edges typically need to be propagated to several children, which increases -// time and memory costs without much benefit, and (2) in pathological cases, -// many long edges close together could force subdivision to continue all the -// way to the leaf cell level. -DEFINE_double( +// The maximum cell size, relative to an edge's length, for which that edge is +// considered 'long'. Cell size is defined as the average edge length of all +// cells at a given level. For example, a value of 2.0 means that an edge E +// is long at cell level k iff the average edge length at level k is at most +// twice the length of E. Long edges edges are not counted towards the +// max_edges_per_cell() limit because such edges typically need to be +// propagated to several children, which increases time and memory costs +// without commensurate benefits. +S2_DEFINE_double( s2shape_index_cell_size_to_long_edge_ratio, 1.0, - "The cell size relative to the length of an edge at which it is first " - "considered to be 'long'. Long edges do not contribute to the decision " - "to subdivide a cell further. The size and speed of the index are " + "The maximum cell size, relative to an edge's length, for which that " + "edge is considered 'long'. Long edges are not counted towards the " + "max_edges_per_cell() limit. The size and speed of the index are " "typically not very sensitive to this parameter. Reasonable values range " "from 0.1 to 10, with smaller values causing more aggressive subdivision " "of long edges grouped closely together."); +// FLAGS_s2shape_index_min_short_edge_fraction +// +// The minimum fraction of 'short' edges that must be present in a cell in +// order for it to be subdivided. If this parameter is non-zero then the +// total index size and construction time are guaranteed to be linear in the +// number of input edges; this prevents the worst-case quadratic space and +// time usage that can otherwise occur with certain input configurations. +// Specifically, the maximum index size is +// +// O((c1 + c2 * (1 - f) / f) * n) +// +// where n is the number of input edges, f is this parameter value, and +// constant c2 is roughly 20 times larger than constant c1. (The exact values +// of c1 and c2 depend on the cell_size_to_long_edge_ratio and +// max_edges_per_cell parameters and certain properties of the input geometry +// such as whether it consists of O(1) shapes, whether it includes polygons, +// and whether the polygon interiors are disjoint.) +// +// Reasonable parameter values range from 0.1 up to perhaps 0.95. The main +// factors to consider when choosing this parameter are: +// +// - For pathological geometry, larger values result in indexes that are +// smaller and faster to construct but have worse query performance (due to +// having more edges per cell). However note that even a setting of 0.1 +// reduces the worst case by 100x compared with a setting of 0.001. +// +// - For normal geometry, values up to about 0.8 result in indexes that are +// virtually unchanged except for a slight increase in index construction +// time (proportional to the parameter value f) for very large inputs. +// With millions of edges, indexing time increases by about (15% * f), +// e.g. a parameter value of 0.5 slows down indexing for very large inputs +// by about 7.5%. (Indexing time for small inputs is not affected.) +// +// - Values larger than about 0.8 start to affect index construction even for +// normal geometry, resulting in smaller indexes and faster construction +// times but gradually worse query performance. +// +// Essentially this parameter provides control over a space-time tradeoff that +// largely affects only pathological geometry. The default value of 0.2 was +// chosen to make index construction as fast as possible while still +// protecting against possible quadratic space usage. +S2_DEFINE_double( + s2shape_index_min_short_edge_fraction, 0.2, + "The minimum fraction of 'short' edges that must be present in a cell in " + "order for it to be subdivided. If this parameter is non-zero then the " + "total index size and construction time are guaranteed to be linear in the " + "number of input edges, where the constant of proportionality has the " + "form (c1 + c2 * (1 - f) / f). Reasonable values range from 0.1 to " + "perhaps 0.95. Values up to about 0.8 have almost no effect on 'normal' " + "geometry except for a small increase in index construction time " + "(proportional to f) for very large inputs. For worst-case geometry, " + "larger parameter values result in indexes that are smaller and faster " + "to construct but have worse query performance (due to having more edges " + "per cell). Essentially this parameter provides control over a space-time " + "tradeoff that largely affects only pathological geometry."); + // The total error when clipping an edge comes from two sources: // (1) Clipping the original spherical edge to a cube face (the "face edge"). // The maximum error in this step is S2::kFaceClipErrorUVCoord. @@ -109,129 +184,28 @@ const double MutableS2ShapeIndex::kCellPadding = 2 * (S2::kFaceClipErrorUVCoord + S2::kEdgeClipErrorUVCoord); MutableS2ShapeIndex::Options::Options() - : max_edges_per_cell_(FLAGS_s2shape_index_default_max_edges_per_cell) { -} + : max_edges_per_cell_( + absl::GetFlag(FLAGS_s2shape_index_default_max_edges_per_cell)) {} void MutableS2ShapeIndex::Options::set_max_edges_per_cell( int max_edges_per_cell) { max_edges_per_cell_ = max_edges_per_cell; } -bool MutableS2ShapeIndex::Iterator::Locate(const S2Point& target) { - return LocateImpl(target, this); -} - -MutableS2ShapeIndex::CellRelation MutableS2ShapeIndex::Iterator::Locate( - S2CellId target) { - return LocateImpl(target, this); -} - const S2ShapeIndexCell* MutableS2ShapeIndex::Iterator::GetCell() const { - S2_LOG(DFATAL) << "Should never be called"; + S2_LOG(ERROR) << "Should never be called"; return nullptr; } unique_ptr MutableS2ShapeIndex::Iterator::Clone() const { - return absl::make_unique(*this); + return make_unique(*this); } void MutableS2ShapeIndex::Iterator::Copy(const IteratorBase& other) { *this = *down_cast(&other); } -// Defines the initial focus point of MutableS2ShapeIndex::InteriorTracker -// (the start of the S2CellId space-filling curve). -// -// TODO(ericv): Move InteriorTracker here to avoid the need for this method. -static S2Point kInteriorTrackerOrigin() { - return S2::FaceUVtoXYZ(0, -1, -1).Normalize(); -} - -MutableS2ShapeIndex::MutableS2ShapeIndex() - : index_status_(FRESH) { -} - -MutableS2ShapeIndex::MutableS2ShapeIndex(const Options& options) - : options_(options), - index_status_(FRESH) { -} - -void MutableS2ShapeIndex::Init(const Options& options) { - S2_DCHECK(shapes_.empty()); - options_ = options; -} - -MutableS2ShapeIndex::~MutableS2ShapeIndex() { - Clear(); -} - -void MutableS2ShapeIndex::Minimize() { - // TODO(ericv): Implement. In theory we should be able to discard the - // entire index and rebuild it the next time it is needed. -} - -int MutableS2ShapeIndex::Add(unique_ptr shape) { - // Additions are processed lazily by ApplyUpdates(). - const int id = shapes_.size(); - shape->id_ = id; - shapes_.push_back(std::move(shape)); - index_status_.store(STALE, std::memory_order_relaxed); - return id; -} - -unique_ptr MutableS2ShapeIndex::Release(int shape_id) { - // This class updates itself lazily, because it is much more efficient to - // process additions and removals in batches. However this means that when - // a shape is removed, we need to make a copy of all its edges, since the - // client is free to delete "shape" once this call is finished. - - S2_DCHECK(shapes_[shape_id] != nullptr); - auto shape = std::move(shapes_[shape_id]); - if (shape_id >= pending_additions_begin_) { - // We are removing a shape that has not yet been added to the index, - // so there is nothing else to do. - } else { - if (!pending_removals_) { - pending_removals_.reset(new vector); - } - // We build the new RemovedShape in place, since it includes a potentially - // large vector of edges that might be expensive to copy. - pending_removals_->push_back(RemovedShape()); - RemovedShape* removed = &pending_removals_->back(); - removed->shape_id = shape->id(); - removed->has_interior = (shape->dimension() == 2); - removed->contains_tracker_origin = - s2shapeutil::ContainsBruteForce(*shape, kInteriorTrackerOrigin()); - int num_edges = shape->num_edges(); - removed->edges.reserve(num_edges); - for (int e = 0; e < num_edges; ++e) { - removed->edges.push_back(shape->edge(e)); - } - } - index_status_.store(STALE, std::memory_order_relaxed); - return shape; -} - -vector> MutableS2ShapeIndex::ReleaseAll() { - Iterator it; - for (it.InitStale(this, S2ShapeIndex::BEGIN); !it.done(); it.Next()) { - delete &it.cell(); - } - cell_map_.clear(); - pending_additions_begin_ = 0; - pending_removals_.reset(); - S2_DCHECK(update_state_ == nullptr); - index_status_.store(FRESH, std::memory_order_relaxed); - vector> result; - result.swap(shapes_); - return result; -} - -void MutableS2ShapeIndex::Clear() { - ReleaseAll(); -} - // FaceEdge and ClippedEdge store temporary edge data while the index is being // updated. FaceEdge represents an edge that has been projected onto a given // face, while ClippedEdge represents the portion of that edge that has been @@ -251,9 +225,9 @@ void MutableS2ShapeIndex::Clear() { // ClippedEdge and this data is cached more successfully. 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 + 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 of dimension 2. R2Point a, b; // The edge endpoints, clipped to a given face S2Shape::Edge edge; // The edge endpoints @@ -299,15 +273,15 @@ class MutableS2ShapeIndex::InteriorTracker { // Returns true if any shapes are being tracked. bool is_active() const { return is_active_; } - // Adds a shape whose interior should be tracked. "is_inside" indicates - // whether the current focus point is inside the shape. Alternatively, if - // the focus point is in the process of being moved (via MoveTo/DrawTo), you - // can also specify "is_inside" at the old focus point and call TestEdge() + // Adds a shape whose interior should be tracked. "contains_focus" indicates + // whether the current focus point is inside the shape. Alternatively, if the + // focus point is in the process of being moved (via MoveTo/DrawTo), you can + // also specify "contains_focus" at the old focus point and call TestEdge() // 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->dimension() == 2 - void AddShape(int32 shape_id, bool is_inside); + void AddShape(int32 shape_id, bool contains_focus); // Moves the focus to the given point. This method should only be used when // it is known that there are no edge crossings between the old and new @@ -353,6 +327,11 @@ class MutableS2ShapeIndex::InteriorTracker { // only affects the state for shape_ids below "limit_shape_id". void RestoreStateBefore(int32 limit_shape_id); + // Indicates that only some edges of the given shape are being added, and + // therefore its interior should not be processed yet. + int partial_shape_id() const { return partial_shape_id_; } + void set_partial_shape_id(int shape_id) { partial_shape_id_ = shape_id; } + private: // Removes "shape_id" from shape_ids_ if it exists, otherwise insert it. void ToggleShape(int shape_id); @@ -360,7 +339,7 @@ class MutableS2ShapeIndex::InteriorTracker { // Returns a pointer to the first entry "x" where x >= shape_id. ShapeIdSet::iterator lower_bound(int32 shape_id); - bool is_active_; + bool is_active_ = false; S2Point a_, b_; S2CellId next_cellid_; S2EdgeCrosser crosser_; @@ -369,14 +348,22 @@ class MutableS2ShapeIndex::InteriorTracker { // Shape ids saved by SaveAndClearStateBefore(). The state is never saved // recursively so we don't need to worry about maintaining a stack. ShapeIdSet saved_ids_; + + // As an optimization, we also save is_active_ so that RestoreStateBefore() + // can deactivate the tracker again in the case where the shapes being added + // and removed do not have an interior, but some existing shapes do. + bool saved_is_active_; + + // If non-negative, indicates that only some edges of the given shape are + // being added and therefore its interior should not be tracked yet. + int partial_shape_id_ = -1; }; // As shapes are added, we compute which ones contain the start of the // S2CellId space-filling curve by drawing an edge from S2::Origin() to this // point and counting how many shape edges cross this edge. MutableS2ShapeIndex::InteriorTracker::InteriorTracker() - : is_active_(false), b_(Origin()), - next_cellid_(S2CellId::Begin(S2CellId::kMaxLevel)) { + : b_(Origin()), next_cellid_(S2CellId::Begin(S2CellId::kMaxLevel)) { } S2Point MutableS2ShapeIndex::InteriorTracker::Origin() { @@ -422,8 +409,10 @@ void MutableS2ShapeIndex::InteriorTracker::DrawTo(const S2Point& b) { crosser_.Init(&a_, &b_); } -inline void MutableS2ShapeIndex::InteriorTracker::TestEdge( - int32 shape_id, const S2Shape::Edge& edge) { +ABSL_ATTRIBUTE_ALWAYS_INLINE // ~1% faster + inline void + MutableS2ShapeIndex::InteriorTracker::TestEdge(int32 shape_id, + const S2Shape::Edge& edge) { if (crosser_.EdgeOrVertexCrossing(&edge.v0, &edge.v1)) { ToggleShape(shape_id); } @@ -445,6 +434,7 @@ void MutableS2ShapeIndex::InteriorTracker::SaveAndClearStateBefore( ShapeIdSet::iterator limit = lower_bound(limit_shape_id); saved_ids_.assign(shape_ids_.begin(), limit); shape_ids_.erase(shape_ids_.begin(), limit); + saved_is_active_ = is_active_; } void MutableS2ShapeIndex::InteriorTracker::RestoreStateBefore( @@ -452,6 +442,143 @@ void MutableS2ShapeIndex::InteriorTracker::RestoreStateBefore( shape_ids_.erase(shape_ids_.begin(), lower_bound(limit_shape_id)); shape_ids_.insert(shape_ids_.begin(), saved_ids_.begin(), saved_ids_.end()); saved_ids_.clear(); + is_active_ = saved_is_active_; +} + +MutableS2ShapeIndex::MutableS2ShapeIndex() = default; + +MutableS2ShapeIndex::MutableS2ShapeIndex(const Options& options) { + Init(options); +} + +MutableS2ShapeIndex::MutableS2ShapeIndex(MutableS2ShapeIndex&& b) + : S2ShapeIndex(std::move(b)), + shapes_(std::move(b.shapes_)), + cell_map_(std::move(b.cell_map_)), + options_(std::move(b.options_)), + pending_additions_begin_(absl::exchange(b.pending_additions_begin_, 0)), + pending_removals_(std::move(b.pending_removals_)), + index_status_(b.index_status_.exchange(FRESH, std::memory_order_relaxed)), + mem_tracker_(std::move(b.mem_tracker_)) {} + +MutableS2ShapeIndex& MutableS2ShapeIndex::operator=(MutableS2ShapeIndex&& b) { + // We need to delegate to our parent move-assignment operator since we can't + // move any of its private state. This is a little odd since b is in a + // half-moved state after calling but is ultimately safe. + S2ShapeIndex::operator=(static_cast(b)); + shapes_ = std::move(b.shapes_); + cell_map_ = std::move(b.cell_map_); + options_ = std::move(b.options_); + pending_additions_begin_ = absl::exchange(b.pending_additions_begin_, 0); + pending_removals_ = std::move(b.pending_removals_); + index_status_.store( + b.index_status_.exchange(FRESH, std::memory_order_relaxed), + std::memory_order_relaxed); + mem_tracker_ = std::move(b.mem_tracker_); + return *this; +} + +void MutableS2ShapeIndex::Init(const Options& options) { + S2_DCHECK(shapes_.empty()); + options_ = options; + // Memory tracking is not affected by this method. +} + +MutableS2ShapeIndex::~MutableS2ShapeIndex() { + Clear(); +} + +void MutableS2ShapeIndex::set_memory_tracker(S2MemoryTracker* tracker) { + mem_tracker_.Tally(-mem_tracker_.client_usage_bytes()); + mem_tracker_.Init(tracker); + if (mem_tracker_.is_active()) mem_tracker_.Tally(SpaceUsed()); +} + +// Called to set the index status when the index needs to be rebuilt. +void MutableS2ShapeIndex::MarkIndexStale() { + // The UPDATING status can only be changed in ApplyUpdatesThreadSafe(). + if (index_status_.load(std::memory_order_relaxed) == UPDATING) return; + + // If a memory tracking error has occurred we set the index status to FRESH + // in order to prevent us from attempting to rebuild it. + IndexStatus status = (shapes_.empty() || !mem_tracker_.ok()) ? FRESH : STALE; + index_status_.store(status, std::memory_order_relaxed); +} + +void MutableS2ShapeIndex::Minimize() { + mem_tracker_.Tally(-mem_tracker_.client_usage_bytes()); + Iterator it; + for (it.InitStale(this, S2ShapeIndex::BEGIN); !it.done(); it.Next()) { + delete &it.cell(); + } + cell_map_.clear(); + pending_removals_.reset(); + pending_additions_begin_ = 0; + MarkIndexStale(); + if (mem_tracker_.is_active()) mem_tracker_.Tally(SpaceUsed()); +} + +int MutableS2ShapeIndex::Add(unique_ptr shape) { + // Additions are processed lazily by ApplyUpdates(). Note that in order to + // avoid unexpected client behavior, this method continues to add shapes + // even once the specified S2MemoryTracker limit has been exceeded. + const int id = shapes_.size(); + shape->id_ = id; + mem_tracker_.AddSpace(&shapes_, 1); + shapes_.push_back(std::move(shape)); + MarkIndexStale(); + return id; +} + +unique_ptr MutableS2ShapeIndex::Release(int shape_id) { + // This class updates itself lazily, because it is much more efficient to + // process additions and removals in batches. However this means that when + // a shape is removed we need to make a copy of all its edges, since the + // client is free to delete "shape" once this call is finished. + + S2_DCHECK(shapes_[shape_id] != nullptr); + auto shape = std::move(shapes_[shape_id]); + if (shape_id >= pending_additions_begin_) { + // We are removing a shape that has not yet been added to the index, + // so there is nothing else to do. + } else { + if (!pending_removals_) { + if (!mem_tracker_.Tally(sizeof(*pending_removals_))) { + Minimize(); + return shape; + } + pending_removals_ = make_unique>(); + } + RemovedShape removed; + removed.shape_id = shape->id(); + removed.has_interior = (shape->dimension() == 2); + removed.contains_tracker_origin = + s2shapeutil::ContainsBruteForce(*shape, InteriorTracker::Origin()); + int num_edges = shape->num_edges(); + if (!mem_tracker_.AddSpace(&removed.edges, num_edges) || + !mem_tracker_.AddSpace(pending_removals_.get(), 1)) { + Minimize(); + return shape; + } + for (int e = 0; e < num_edges; ++e) { + removed.edges.push_back(shape->edge(e)); + } + pending_removals_->push_back(std::move(removed)); + } + MarkIndexStale(); + return shape; +} + +vector> MutableS2ShapeIndex::ReleaseAll() { + S2_DCHECK(update_state_ == nullptr); + vector> result; + result.swap(shapes_); + Minimize(); + return result; +} + +void MutableS2ShapeIndex::Clear() { + ReleaseAll(); } // Apply any pending updates in a thread-safe way. @@ -479,7 +606,7 @@ void MutableS2ShapeIndex::ApplyUpdatesThreadSafe() { // and this saves an extra lock and unlock step; (3) even in the rare case // where there is contention, the main side effect is that some other // thread will burn a few CPU cycles rather than sleeping. - update_state_.reset(new UpdateState); + update_state_ = make_unique(); // lock_.Lock wait_mutex *before* calling Unlock() to ensure that all other // threads will block on it. update_state_->wait_mutex.Lock(); @@ -516,44 +643,21 @@ inline void MutableS2ShapeIndex::UnlockAndSignal() { } } -void MutableS2ShapeIndex::ForceBuild() { - // No locks required because this is not a const method. It is the client's - // responsibility to ensure correct thread synchronization. - if (index_status_.load(std::memory_order_relaxed) != FRESH) { - ApplyUpdatesInternal(); - index_status_.store(FRESH, std::memory_order_relaxed); - } -} - -// A BatchDescriptor represents a set of pending updates that will be applied -// at the same time. The batch consists of all updates with shape ids between -// the current value of "ShapeIndex::pending_additions_begin_" (inclusive) and -// "additions_end" (exclusive). The first batch to be processed also -// implicitly includes all shapes being removed. "num_edges" is the total -// number of edges that will be added or removed in this batch. -struct MutableS2ShapeIndex::BatchDescriptor { - BatchDescriptor(int _additions_end, int _num_edges) - : additions_end(_additions_end), num_edges(_num_edges) { - } - int additions_end; - int num_edges; -}; - // This method updates the index by applying all pending additions and // removals. It does *not* update index_status_ (see ApplyUpdatesThreadSafe). void MutableS2ShapeIndex::ApplyUpdatesInternal() { // Check whether we have so many edges to process that we should process // them in multiple batches to save memory. Building the index can use up // to 20x as much memory (per edge) as the final index size. - vector batches; - GetUpdateBatches(&batches); - int i = 0; + vector batches = GetUpdateBatches(); for (const BatchDescriptor& batch : batches) { + if (mem_tracker_.is_active()) { + S2_DCHECK_EQ(mem_tracker_.client_usage_bytes(), SpaceUsed()); // Invariant. + } vector all_edges[6]; - S2_VLOG(1) << "Batch " << i++ << ": shape_limit=" << batch.additions_end - << ", edges=" << batch.num_edges; - ReserveSpace(batch, all_edges); + if (!mem_tracker_.ok()) return Minimize(); + InteriorTracker tracker; if (pending_removals_) { // The first batch implicitly includes all shapes being removed. @@ -562,24 +666,41 @@ void MutableS2ShapeIndex::ApplyUpdatesInternal() { } pending_removals_.reset(nullptr); } - for (int id = pending_additions_begin_; id < batch.additions_end; ++id) { - AddShape(id, all_edges, &tracker); + // A batch consists of zero or more full shapes followed by zero or one + // partial shapes. The loop below handles all such cases. + for (auto begin = batch.begin; begin < batch.end; + ++begin.shape_id, begin.edge_id = 0) { + const S2Shape* shape = this->shape(begin.shape_id); + if (shape == nullptr) continue; // Already removed. + int edges_end = begin.shape_id == batch.end.shape_id ? batch.end.edge_id + : shape->num_edges(); + AddShape(shape, begin.edge_id, edges_end, all_edges, &tracker); } for (int face = 0; face < 6; ++face) { UpdateFaceEdges(face, all_edges[face], &tracker); // Save memory by clearing vectors after we are done with them. vector().swap(all_edges[face]); } - pending_additions_begin_ = batch.additions_end; + pending_additions_begin_ = batch.end.shape_id; + if (batch.begin.edge_id > 0 && batch.end.edge_id == 0) { + // We have just finished adding the edges of shape that was split over + // multiple batches. Now we need to mark the interior of the shape, if + // any, by setting contains_center() on the appropriate index cells. + FinishPartialShape(tracker.partial_shape_id()); + } + if (mem_tracker_.is_active()) { + mem_tracker_.Tally(-mem_tracker_.client_usage_bytes()); + if (!mem_tracker_.Tally(SpaceUsed())) return Minimize(); + } } // It is the caller's responsibility to update index_status_. } // Count the number of edges being updated, and break them into several // batches if necessary to reduce the amount of memory needed. (See the -// documentation for FLAGS_s2shape_index_tmp_memory_budget_mb.) -void MutableS2ShapeIndex::GetUpdateBatches(vector* batches) - const { +// documentation for FLAGS_s2shape_index_tmp_memory_budget.) +vector +MutableS2ShapeIndex::GetUpdateBatches() const { // Count the edges being removed and added. int num_edges_removed = 0; if (pending_removals_) { @@ -588,125 +709,188 @@ void MutableS2ShapeIndex::GetUpdateBatches(vector* batches) } } int num_edges_added = 0; - for (int id = pending_additions_begin_; id < shapes_.size(); ++id) { + for (size_t id = pending_additions_begin_; id < shapes_.size(); ++id) { const S2Shape* shape = this->shape(id); - if (shape == nullptr) continue; - num_edges_added += shape->num_edges(); + if (shape) num_edges_added += shape->num_edges(); } - int num_edges = num_edges_removed + num_edges_added; + BatchGenerator batch_gen(num_edges_removed, num_edges_added, + pending_additions_begin_); + for (size_t id = pending_additions_begin_; id < shapes_.size(); ++id) { + const S2Shape* shape = this->shape(id); + if (shape) batch_gen.AddShape(id, shape->num_edges()); + } + return batch_gen.Finish(); +} - // The following memory estimates are based on heap profiling. - // - // The final size of a MutableS2ShapeIndex depends mainly on how finely the - // index is subdivided, as controlled by Options::max_edges_per_cell() and - // --s2shape_index_default_max_edges_per_cell. For realistic values of - // max_edges_per_cell() and shapes with moderate numbers of edges, it is - // difficult to get much below 8 bytes per edge. [The minimum possible size - // is 4 bytes per edge (to store a 32-bit edge id in an S2ClippedShape) plus - // 24 bytes per shape (for the S2ClippedShape itself plus a pointer in the - // shapes_ vector.] - // - // The temporary memory consists mainly of the FaceEdge and ClippedEdge - // structures plus a ClippedEdge pointer for every level of recursive - // subdivision. For very large indexes this can be 200 bytes per edge. - const size_t kFinalBytesPerEdge = 8; - const size_t kTmpBytesPerEdge = 200; - const size_t kTmpMemoryBudgetBytes = - static_cast(FLAGS_s2shape_index_tmp_memory_budget_mb) << 20; - - // We arbitrarily limit the number of batches just as a safety measure. - // With the current default memory budget of 100 MB, this limit is not - // reached even when building an index of 350 million edges. - const int kMaxUpdateBatches = 100; - - if (num_edges * kTmpBytesPerEdge <= kTmpMemoryBudgetBytes) { - // We can update all edges at once without exceeding kTmpMemoryBudgetBytes. - batches->push_back(BatchDescriptor(shapes_.size(), num_edges)); - return; +// The following memory estimates are based on heap profiling. + +// The batch sizes during a given update gradually decrease as the space +// occupied by the index itself grows. In order to do this, we need a +// conserative lower bound on how much the index grows per edge. +// +// The final size of a MutableS2ShapeIndex depends mainly on how finely the +// index is subdivided, as controlled by Options::max_edges_per_cell() and +// --s2shape_index_default_max_edges_per_cell. For realistic values of +// max_edges_per_cell() and shapes with moderate numbers of edges, it is +// difficult to get much below 8 bytes per edge. *The minimum possible size +// is 4 bytes per edge (to store a 32-bit edge id in an S2ClippedShape) plus +// 24 bytes per shape (for the S2ClippedShape itself plus a pointer in the +// shapes_ vector.) Note that this value is a lower bound; a typical final +// index size is closer to 24 bytes per edge. +static constexpr size_t kFinalBytesPerEdge = 8; + +// The temporary memory consists mainly of the FaceEdge and ClippedEdge +// structures plus a ClippedEdge pointer for every level of recursive +// subdivision. This can be more than 220 bytes per edge even for typical +// geometry. (The pathological worst case is higher, but we don't use this to +// determine the batch sizes.) +static constexpr size_t kTmpBytesPerEdge = 226; + +// We arbitrarily limit the number of batches as a safety measure. With the +// current default memory budget of 100 MB, this limit is not reached even +// when building an index of 350 million edges. +static constexpr int kMaxBatches = 100; + +MutableS2ShapeIndex::BatchGenerator::BatchGenerator(int num_edges_removed, + int num_edges_added, + int shape_id_begin) + : max_batch_sizes_(GetMaxBatchSizes(num_edges_removed, num_edges_added)), + batch_begin_(shape_id_begin, 0), + shape_id_end_(shape_id_begin) { + if (max_batch_sizes_.size() > 1) { + S2_VLOG(1) << "Removing " << num_edges_removed << ", adding " + << num_edges_added << " edges in " << max_batch_sizes_.size() + << " batches"; } - // Otherwise, break the updates into up to several batches, where the size - // of each batch is chosen so that all batches use approximately the same - // high-water memory. GetBatchSizes() returns the recommended number of - // edges in each batch. - vector batch_sizes; - GetBatchSizes(num_edges, kMaxUpdateBatches, kFinalBytesPerEdge, - kTmpBytesPerEdge, kTmpMemoryBudgetBytes, &batch_sizes); - - // We always process removed edges in a single batch, since (1) they already - // take up a lot of memory because we have copied all their edges, and (2) - // AbsorbIndexCell() uses (shapes_[id] == nullptr) to detect when a shape is - // being removed, so in order to split the removals into batches we would - // need a different approach (e.g., temporarily add fake entries to shapes_ - // and restore them back to nullptr as shapes are actually removed). - num_edges = 0; - if (pending_removals_) { - num_edges += num_edges_removed; - if (num_edges >= batch_sizes[0]) { - batches->push_back(BatchDescriptor(pending_additions_begin_, num_edges)); - num_edges = 0; + // Duplicate the last entry to simplify next_max_batch_size(). + max_batch_sizes_.push_back(max_batch_sizes_.back()); + + // We process edge removals before additions, and edges are always removed + // in a single batch. The reasons for this include: (1) removed edges use + // quite a bit of memory (about 50 bytes each) and this space can be freed + // immediately when we process them in one batch; (2) removed shapes are + // expected to be small fraction of the index size in typical use cases + // (e.g. incremental updates of large indexes), and (3) AbsorbIndexCell() + // uses (shape(id) == nullptr) to detect when a shape is being removed, so + // in order to split the removed shapes into multiple batches we would need + // a different approach (e.g., temporarily adding fake entries to shapes_ + // and restoring them back to nullptr as shapes are removed). Removing + // individual shapes over multiple batches would be even more work. + batch_size_ = num_edges_removed; +} + +void MutableS2ShapeIndex::BatchGenerator::AddShape(int shape_id, + int num_edges) { + int batch_remaining = max_batch_size() - batch_size_; + if (num_edges <= batch_remaining) { + ExtendBatch(num_edges); + } else if (num_edges <= next_max_batch_size()) { + // Avoid splitting shapes across batches unnecessarily. + FinishBatch(0, ShapeEdgeId(shape_id, 0)); + ExtendBatch(num_edges); + } else { + // This shape must be split across at least two batches. We simply fill + // each batch until the remaining edges will fit in two batches, and then + // divide those edges such that both batches have the same amount of + // remaining space relative to their maximum size. + int e_begin = 0; + while (batch_remaining + next_max_batch_size() < num_edges) { + e_begin += batch_remaining; + FinishBatch(batch_remaining, ShapeEdgeId(shape_id, e_begin)); + num_edges -= batch_remaining; + batch_remaining = max_batch_size(); } + // Figure out how many edges to add to the current batch so that it will + // have the same amount of remaining space as the next batch. + int n = (num_edges + batch_remaining - next_max_batch_size()) / 2; + FinishBatch(n, ShapeEdgeId(shape_id, e_begin + n)); + FinishBatch(num_edges - n, ShapeEdgeId(shape_id + 1, 0)); } - // Keep adding shapes to each batch until the recommended number of edges - // for that batch is reached, then move on to the next batch. - for (int id = pending_additions_begin_; id < shapes_.size(); ++id) { - const S2Shape* shape = this->shape(id); - if (shape == nullptr) continue; - num_edges += shape->num_edges(); - if (num_edges >= batch_sizes[batches->size()]) { - batches->push_back(BatchDescriptor(id + 1, num_edges)); - num_edges = 0; - } + shape_id_end_ = shape_id + 1; +} + +vector +MutableS2ShapeIndex::BatchGenerator::Finish() { + // We must generate at least one batch even when num_edges_removed == + // num_edges_added == 0, because some shapes have an interior but no edges. + // (Specifically, the full polygon has this property.) + if (batches_.empty() || shape_id_end_ != batch_begin_.shape_id) { + FinishBatch(0, ShapeEdgeId(shape_id_end_, 0)); } - // Some shapes have no edges. If a shape with no edges is the last shape to - // be added or removed, then the final batch may not include it, so we fix - // that problem here. - batches->back().additions_end = shapes_.size(); - S2_DCHECK_LE(batches->size(), kMaxUpdateBatches); + return std::move(batches_); } -// Given "num_items" items, each of which uses "tmp_bytes_per_item" while it -// is being updated but only "final_bytes_per_item" in the end, divide the -// items into batches that have approximately the same *total* memory usage -// consisting of the temporary memory needed for the items in the current -// batch plus the final size of all the items that have already been -// processed. Use the fewest number of batches (but never more than -// "max_batches") such that the total memory usage does not exceed the -// combined final size of all the items plus "tmp_memory_budget_bytes". +void MutableS2ShapeIndex::BatchGenerator::FinishBatch(int num_edges, + ShapeEdgeId batch_end) { + ExtendBatch(num_edges); + batches_.push_back(BatchDescriptor{batch_begin_, batch_end, batch_size_}); + batch_begin_ = batch_end; + batch_index_edges_left_ -= batch_size_; + while (batch_index_edges_left_ < 0) { + batch_index_edges_left_ += max_batch_size(); + batch_index_ += 1; + } + batch_size_ = 0; +} + +// Divides "num_edges" edges into batches where each batch needs about the +// same total amount of memory. (The total memory needed by a batch consists +// of the temporary memory needed to process the edges in that batch plus the +// final representations of the edges that have already been indexed.) It +// uses the fewest number of batches (up to kMaxBatches) such that the total +// memory usage does not exceed the combined final size of all the edges plus +// FLAGS_s2shape_index_tmp_memory_budget. Returns a vector of sizes +// indicating the desired number of edges in each batch. /* static */ -void MutableS2ShapeIndex::GetBatchSizes(int num_items, int max_batches, - double final_bytes_per_item, - double tmp_bytes_per_item, - double tmp_memory_budget_bytes, - vector* batch_sizes) { - // This code tries to fit all the data into the same memory space - // ("total_budget_bytes") at every iteration. The data consists of some - // number of processed items (at "final_bytes_per_item" each), plus some - // number being updated (at "tmp_bytes_per_item" each). The space occupied - // by the items being updated is the "free space". At each iteration, the - // free space is multiplied by (1 - final_bytes_per_item/tmp_bytes_per_item) - // as the items are converted into their final form. - double final_bytes = num_items * final_bytes_per_item; - double final_bytes_ratio = final_bytes_per_item / tmp_bytes_per_item; - double free_space_multiplier = 1 - final_bytes_ratio; +vector MutableS2ShapeIndex::BatchGenerator::GetMaxBatchSizes( + int num_edges_removed, int num_edges_added) { + // Check whether we can update all the edges at once. + int num_edges_total = num_edges_removed + num_edges_added; + const double tmp_memory_budget_bytes = + absl::GetFlag(FLAGS_s2shape_index_tmp_memory_budget); + if (num_edges_total * kTmpBytesPerEdge <= tmp_memory_budget_bytes) { + return vector{num_edges_total}; + } + + // Each batch is allowed to use up to "total_budget_bytes". The memory + // usage consists of some number of edges already added by previous batches + // (at kFinalBytesPerEdge each), plus some number being updated in the + // current batch (at kTmpBytesPerEdge each). The available free space is + // multiplied by (1 - kFinalBytesPerEdge / kTmpBytesPerEdge) after each + // batch is processed as edges are converted into their final form. + const double final_bytes = num_edges_added * kFinalBytesPerEdge; + constexpr double kFinalBytesRatio = 1.0 * kFinalBytesPerEdge / + kTmpBytesPerEdge; + constexpr double kTmpSpaceMultiplier = 1 - kFinalBytesRatio; // The total memory budget is the greater of the final size plus the allowed // temporary memory, or the minimum amount of memory required to limit the - // number of batches to "max_batches". - double total_budget_bytes = max( + // number of batches to "kMaxBatches". + const double total_budget_bytes = max( final_bytes + tmp_memory_budget_bytes, - final_bytes / (1 - pow(free_space_multiplier, max_batches))); - - // "max_batch_items" is the number of items in the current batch. - double max_batch_items = total_budget_bytes / tmp_bytes_per_item; - batch_sizes->clear(); - for (int i = 0; i + 1 < max_batches && num_items > 0; ++i) { - int batch_items = - std::min(num_items, static_cast(max_batch_items + 1)); - batch_sizes->push_back(batch_items); - num_items -= batch_items; - max_batch_items *= free_space_multiplier; + final_bytes / (1 - MathUtil::IPow(kTmpSpaceMultiplier, kMaxBatches - 1))); + + // "ideal_batch_size" is the number of edges in the current batch before + // rounding to an integer. + double ideal_batch_size = total_budget_bytes / kTmpBytesPerEdge; + + // Removed edges are always processed in the first batch, even if this might + // use more memory than requested (see the BatchGenerator constructor). + vector batch_sizes; + int num_edges_left = num_edges_added; + if (num_edges_removed > ideal_batch_size) { + batch_sizes.push_back(num_edges_removed); + } else { + num_edges_left += num_edges_removed; + } + for (int i = 0; num_edges_left > 0; ++i) { + int batch_size = static_cast(ideal_batch_size + 1); + batch_sizes.push_back(batch_size); + num_edges_left -= batch_size; + ideal_batch_size *= kTmpSpaceMultiplier; } - S2_DCHECK_LE(batch_sizes->size(), max_batches); + S2_DCHECK_LE(batch_sizes.size(), kMaxBatches); + return batch_sizes; } // Reserve an appropriate amount of space for the top-level face edges in the @@ -714,17 +898,27 @@ void MutableS2ShapeIndex::GetBatchSizes(int num_items, int max_batches, // needed during index construction. Furthermore, if the arrays are grown via // push_back() then up to 10% of the total run time consists of copying data // as these arrays grow, so it is worthwhile to preallocate space for them. -void MutableS2ShapeIndex::ReserveSpace(const BatchDescriptor& batch, - vector all_edges[6]) const { +void MutableS2ShapeIndex::ReserveSpace( + const BatchDescriptor& batch, vector all_edges[6]) { + // The following accounts for the temporary space needed for everything + // except the FaceEdge vectors (which are allocated separately below). + int64 other_usage = batch.num_edges * (kTmpBytesPerEdge - sizeof(FaceEdge)); + // If the number of edges is relatively small, then the fastest approach is // to simply reserve space on every face for the maximum possible number of - // edges. We use a different threshold for this calculation than for - // deciding when to break updates into batches, because the cost/benefit - // ratio is different. (Here the only extra expense is that we need to - // sample the edges to estimate how many edges per face there are.) - const size_t kMaxCheapBytes = 30 << 20; // 30 MB - const int kMaxCheapEdges = kMaxCheapBytes / (6 * sizeof(FaceEdge)); - if (batch.num_edges <= kMaxCheapEdges) { + // edges. (We use a different threshold for this calculation than for + // deciding when to break updates into batches because the cost/benefit + // ratio is different. Here the only extra expense is that we need to + // sample the edges to estimate how many edges per face there are, and + // therefore we generally use a lower threshold.) + const size_t kMaxCheapBytes = + min(absl::GetFlag(FLAGS_s2shape_index_tmp_memory_budget) / 2, + int64{30} << 20 /*30 MB*/); + int64 face_edge_usage = batch.num_edges * (6 * sizeof(FaceEdge)); + if (static_cast(face_edge_usage) <= kMaxCheapBytes) { + if (!mem_tracker_.TallyTemp(face_edge_usage + other_usage)) { + return; + } for (int face = 0; face < 6; ++face) { all_edges[face].reserve(batch.num_edges); } @@ -758,16 +952,19 @@ void MutableS2ShapeIndex::ReserveSpace(const BatchDescriptor& batch, } } } - for (int id = pending_additions_begin_; id < batch.additions_end; ++id) { - const S2Shape* shape = this->shape(id); - if (shape == nullptr) continue; - edge_id += shape->num_edges(); + for (auto begin = batch.begin; begin < batch.end; + ++begin.shape_id, begin.edge_id = 0) { + const S2Shape* shape = this->shape(begin.shape_id); + if (shape == nullptr) continue; // Already removed. + int edges_end = begin.shape_id == batch.end.shape_id ? batch.end.edge_id + : shape->num_edges(); + edge_id += edges_end - begin.edge_id; while (edge_id >= sample_interval) { edge_id -= sample_interval; // For speed, we only count the face containing one endpoint of the // edge. In general the edge could span all 6 faces (with padding), but // it's not worth the expense to compute this more accurately. - face_count[S2::GetFace(shape->edge(edge_id).v0)] += 1; + face_count[S2::GetFace(shape->edge(edge_id + begin.edge_id).v0)] += 1; } } // Now given the raw face counts, compute a confidence interval such that we @@ -783,11 +980,22 @@ void MutableS2ShapeIndex::ReserveSpace(const BatchDescriptor& batch, // It is quite likely that such faces are truly empty, so we save time // and memory this way. If the face does contain some edges, there will // only be a few so it is fine to let the vector grow automatically. - // On average, we reserve 2% extra space for each face that has geometry. + // On average, we reserve 2% extra space for each face that has geometry + // (which could be up to 12% extra space overall, but typically 2%). // kMaxSemiWidth is the maximum semi-width over all probabilities p of a // 4-sigma binomial confidence interval with a sample size of 10,000. const double kMaxSemiWidth = 0.02; + + // First estimate the total amount of memory we are about to allocate. + double multiplier = 1.0; + for (int face = 0; face < 6; ++face) { + if (face_count[face] != 0) multiplier += kMaxSemiWidth; + } + face_edge_usage = multiplier * batch.num_edges * sizeof(FaceEdge); + if (!mem_tracker_.TallyTemp(face_edge_usage + other_usage)) { + return; + } const double sample_ratio = 1.0 / actual_sample_size; for (int face = 0; face < 6; ++face) { if (face_count[face] == 0) continue; @@ -796,24 +1004,30 @@ void MutableS2ShapeIndex::ReserveSpace(const BatchDescriptor& batch, } } -// Clip all edges of the given shape to the six cube faces, add the clipped +// Clips the edges of the given shape to the six cube faces, add the clipped // edges to "all_edges", and start tracking its interior if necessary. -void MutableS2ShapeIndex::AddShape(int id, vector all_edges[6], - InteriorTracker* tracker) const { - const S2Shape* shape = this->shape(id); - if (shape == nullptr) { - return; // This shape has already been removed. - } +void MutableS2ShapeIndex::AddShape( + const S2Shape* shape, int edges_begin, int edges_end, + vector all_edges[6], InteriorTracker* tracker) const { // Construct a template for the edges to be added. FaceEdge edge; - edge.shape_id = id; - edge.has_interior = (shape->dimension() == 2); - if (edge.has_interior) { - tracker->AddShape(id, s2shapeutil::ContainsBruteForce(*shape, - tracker->focus())); + edge.shape_id = shape->id(); + edge.has_interior = false; + if (shape->dimension() == 2) { + // To add a single shape with an interior over multiple batches, we first + // add all the edges without tracking the interior. After all edges have + // been added, the interior is updated in a separate step by setting the + // contains_center() flags appropriately. + if (edges_begin > 0 || edges_end < shape->num_edges()) { + tracker->set_partial_shape_id(edge.shape_id); + } else { + edge.has_interior = true; + tracker->AddShape( + edge.shape_id, + s2shapeutil::ContainsBruteForce(*shape, tracker->focus())); + } } - int num_edges = shape->num_edges(); - for (int e = 0; e < num_edges; ++e) { + for (int e = edges_begin; e < edges_end; ++e) { edge.edge_id = e; edge.edge = shape->edge(e); edge.max_level = GetEdgeMaxLevel(edge.edge); @@ -838,6 +1052,108 @@ void MutableS2ShapeIndex::RemoveShape(const RemovedShape& removed, } } +void MutableS2ShapeIndex::FinishPartialShape(int shape_id) { + if (shape_id < 0) return; // The partial shape did not have an interior. + const S2Shape* shape = this->shape(shape_id); + + // Filling in the interior of a partial shape can grow the cell_map_ + // significantly, however the new cells have just one shape and no edges. + // The following is a rough estimate of how much extra memory is needed + // based on experiments. It assumes that one new cell is required for every + // 10 shape edges, and that the cell map uses 50% more space than necessary + // for the new entries because they are inserted between existing entries + // (which means that the btree nodes are not full). + if (mem_tracker_.is_active()) { + const int64 new_usage = + SpaceUsed() - mem_tracker_.client_usage_bytes() + + 0.1 * shape->num_edges() * + (1.5 * sizeof(CellMap::value_type) + sizeof(S2ShapeIndexCell) + + sizeof(S2ClippedShape)); + if (!mem_tracker_.TallyTemp(new_usage)) return; + } + + // All the edges of the partial shape have already been indexed, now we just + // need to set the contains_center() flags appropriately. We use a fresh + // InteriorTracker for this purpose since we don't want to continue tracking + // the interior state of any other shapes in this batch. + // + // We have implemented this below in the simplest way possible, namely by + // scanning through the entire index. In theory it would be more efficient + // to keep track of the set of index cells that were modified when the + // partial shape's edges were added, and then visit only those cells. + // However in practice any shape that is added over multiple batches is + // likely to occupy most or all of the index anyway, so it is faster and + // simpler to just iterate through the entire index. + // + // "tmp_edges" below speeds up large polygon index construction by 3-12%. + vector tmp_edges; // Temporary storage. + InteriorTracker tracker; + tracker.AddShape(shape_id, + s2shapeutil::ContainsBruteForce(*shape, tracker.focus())); + S2CellId begin = S2CellId::Begin(S2CellId::kMaxLevel); + for (CellMap::iterator index_it = cell_map_.begin(); ; ++index_it) { + if (!tracker.shape_ids().empty()) { + // Check whether we need to add new cells that are entirely contained by + // the partial shape. + S2CellId fill_end = + (index_it != cell_map_.end()) ? index_it->first.range_min() + : S2CellId::End(S2CellId::kMaxLevel); + if (begin != fill_end) { + for (S2CellId cellid : S2CellUnion::FromBeginEnd(begin, fill_end)) { + S2ShapeIndexCell* cell = new S2ShapeIndexCell; + S2ClippedShape* clipped = cell->add_shapes(1); + clipped->Init(shape_id, 0); + clipped->set_contains_center(true); + index_it = cell_map_.insert(index_it, make_pair(cellid, cell)); + ++index_it; + } + } + } + if (index_it == cell_map_.end()) break; + + // Now check whether the current index cell needs to be updated. + S2CellId cellid = index_it->first; + S2ShapeIndexCell* cell = index_it->second; + int n = cell->shapes_.size(); + if (n > 0 && cell->shapes_[n - 1].shape_id() == shape_id) { + // This cell contains edges of the partial shape. If the partial shape + // contains the center of this cell, we must update the index. + S2PaddedCell pcell(cellid, kCellPadding); + if (!tracker.at_cellid(cellid)) { + tracker.MoveTo(pcell.GetEntryVertex()); + } + tracker.DrawTo(pcell.GetCenter()); + S2ClippedShape* clipped = &cell->shapes_[n - 1]; + int num_edges = clipped->num_edges(); + S2_DCHECK_GT(num_edges, 0); + for (int i = 0; i < num_edges; ++i) { + tmp_edges.push_back(shape->edge(clipped->edge(i))); + } + for (const auto& edge : tmp_edges) { + tracker.TestEdge(shape_id, edge); + } + if (!tracker.shape_ids().empty()) { + // The partial shape contains the center of this index cell. + clipped->set_contains_center(true); + } + tracker.DrawTo(pcell.GetExitVertex()); + for (const auto& edge : tmp_edges) { + tracker.TestEdge(shape_id, edge); + } + tracker.set_next_cellid(cellid.next()); + tmp_edges.clear(); + + } else if (!tracker.shape_ids().empty()) { + // The partial shape contains the center of an existing index cell that + // does not intersect any of its edges. + S2ClippedShape* clipped = cell->add_shapes(1); + clipped->Init(shape_id, 0); + clipped->set_contains_center(true); + } + begin = cellid.range_max().next(); + } +} + inline void MutableS2ShapeIndex::AddFaceEdge( FaceEdge* edge, vector all_edges[6]) const { // Fast path: both endpoints are on the same face, and are far enough from @@ -862,17 +1178,19 @@ inline void MutableS2ShapeIndex::AddFaceEdge( } } -// Return the first level at which the edge will *not* contribute towards -// the decision to subdivide. +// Returns the first level for which the given edge will be considered "long", +// i.e. it will not count towards the max_edges_per_cell() limit. int MutableS2ShapeIndex::GetEdgeMaxLevel(const S2Shape::Edge& edge) const { - // Compute the maximum cell size for which this edge is considered "long". - // The calculation does not need to be perfectly accurate, so we use Norm() - // rather than Angle() for speed. - double cell_size = ((edge.v0 - edge.v1).Norm() * - FLAGS_s2shape_index_cell_size_to_long_edge_ratio); + // Compute the maximum cell edge length for which this edge is considered + // "long". The calculation does not need to be perfectly accurate, so we + // use Norm() rather than Angle() for speed. + double max_cell_edge = + ((edge.v0 - edge.v1).Norm() * + absl::GetFlag(FLAGS_s2shape_index_cell_size_to_long_edge_ratio)); + // Now return the first level encountered during subdivision where the - // average cell size is at most "cell_size". - return S2::kAvgEdge.GetLevelForMaxValue(cell_size); + // average cell edge length at that level is at most "max_cell_edge". + return S2::kAvgEdge.GetLevelForMaxValue(max_cell_edge); } // EdgeAllocator provides temporary storage for new ClippedEdges that are @@ -953,8 +1271,13 @@ void MutableS2ShapeIndex::UpdateFaceEdges(int face, S2PaddedCell pcell(face_id, kCellPadding); // "disjoint_from_index" means that the current cell being processed (and - // all its descendants) are not already present in the index. - bool disjoint_from_index = is_first_update(); + // all its descendants) are not already present in the index. It is set to + // true during the recursion whenever we detect that the current cell is + // disjoint from the index. We could save a tiny bit of work by setting + // this flag to true here on the very first update, however currently there + // is no easy way to check that. (It's not sufficient to test whether + // cell_map_.empty() or pending_additions_begin_ == 0.) + bool disjoint_from_index = false; if (num_edges > 0) { S2CellId shrunk_id = ShrinkToFit(pcell, bound); if (shrunk_id != pcell.id()) { @@ -975,17 +1298,19 @@ void MutableS2ShapeIndex::UpdateFaceEdges(int face, UpdateEdges(pcell, &clipped_edges, tracker, &alloc, disjoint_from_index); } -inline S2CellId MutableS2ShapeIndex::ShrinkToFit(const S2PaddedCell& pcell, - const R2Rect& bound) const { +S2CellId MutableS2ShapeIndex::ShrinkToFit(const S2PaddedCell& pcell, + const R2Rect& bound) const { S2CellId shrunk_id = pcell.ShrinkToFit(bound); - if (!is_first_update() && shrunk_id != pcell.id()) { + if (shrunk_id != pcell.id()) { // Don't shrink any smaller than the existing index cells, since we need - // to combine the new edges with those cells. - // Use InitStale() to avoid applying updated recursively. + // to combine the new edges with those cells. Use InitStale() to avoid + // applying updates recursively. Iterator iter; iter.InitStale(this); - CellRelation r = iter.Locate(shrunk_id); - if (r == INDEXED) { shrunk_id = iter.id(); } + S2CellRelation r = iter.Locate(shrunk_id); + if (r == S2CellRelation::INDEXED) { + shrunk_id = iter.id(); + } } return shrunk_id; } @@ -1008,6 +1333,28 @@ void MutableS2ShapeIndex::SkipCellRange(S2CellId begin, S2CellId end, } } +// Given an edge and an interval "middle" along the v-axis, clip the edge +// against the boundaries of "middle" and add the edge to the corresponding +// children. +/* static */ ABSL_ATTRIBUTE_ALWAYS_INLINE // ~8% faster +inline void MutableS2ShapeIndex::ClipVAxis( + const ClippedEdge* edge, + const R1Interval& middle, + vector child_edges[2], + EdgeAllocator* alloc) { + if (edge->bound[1].hi() <= middle.lo()) { + // Edge is entirely contained in the lower child. + child_edges[0].push_back(edge); + } else if (edge->bound[1].lo() >= middle.hi()) { + // Edge is entirely contained in the upper child. + child_edges[1].push_back(edge); + } else { + // The edge bound spans both children. + child_edges[0].push_back(ClipVBound(edge, 1, middle.hi(), alloc)); + child_edges[1].push_back(ClipVBound(edge, 0, middle.lo(), alloc)); + } +} + // Given a cell and a set of ClippedEdges whose bounding boxes intersect that // cell, add or remove all the edges from the index. Temporary space for // edges that need to be subdivided is allocated from the given EdgeAllocator. @@ -1047,21 +1394,21 @@ void MutableS2ShapeIndex::UpdateEdges(const S2PaddedCell& pcell, if (!disjoint_from_index) { // There may be existing index cells contained inside "pcell". If we // encounter such a cell, we need to combine the edges being updated with - // the existing cell contents by "absorbing" the cell. - // Use InitStale() to avoid applying updated recursively. + // the existing cell contents by "absorbing" the cell. We use InitStale() + // to avoid applying updates recursively. Iterator iter; iter.InitStale(this); - CellRelation r = iter.Locate(pcell.id()); - if (r == DISJOINT) { + S2CellRelation r = iter.Locate(pcell.id()); + if (r == S2CellRelation::DISJOINT) { disjoint_from_index = true; - } else if (r == INDEXED) { + } else if (r == S2CellRelation::INDEXED) { // Absorb the index cell by transferring its contents to "edges" and // deleting it. We also start tracking the interior of any new shapes. AbsorbIndexCell(pcell, iter, edges, tracker, alloc); index_cell_absorbed = true; disjoint_from_index = true; } else { - S2_DCHECK_EQ(SUBDIVIDED, r); + S2_DCHECK_EQ(S2CellRelation::SUBDIVIDED, r); } } @@ -1152,28 +1499,6 @@ void MutableS2ShapeIndex::UpdateEdges(const S2PaddedCell& pcell, } } -// Given an edge and an interval "middle" along the v-axis, clip the edge -// against the boundaries of "middle" and add the edge to the corresponding -// children. -/* static */ -inline void MutableS2ShapeIndex::ClipVAxis( - const ClippedEdge* edge, - const R1Interval& middle, - vector child_edges[2], - EdgeAllocator* alloc) { - if (edge->bound[1].hi() <= middle.lo()) { - // Edge is entirely contained in the lower child. - child_edges[0].push_back(edge); - } else if (edge->bound[1].lo() >= middle.hi()) { - // Edge is entirely contained in the upper child. - child_edges[1].push_back(edge); - } else { - // The edge bound spans both children. - child_edges[0].push_back(ClipVBound(edge, 1, middle.hi(), alloc)); - child_edges[1].push_back(ClipVBound(edge, 0, middle.lo(), alloc)); - } -} - // Given an edge, clip the given endpoint (lo=0, hi=1) of the u-axis so that // it does not extend past the given value. /* static */ @@ -1264,7 +1589,8 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell, // Here we first update the InteriorTracker state for removed edges to // correspond to the exit vertex of this cell, and then save the // InteriorTracker state. This state will be restored by UpdateEdges when - // it is finished processing the contents of this cell. + // it is finished processing the contents of this cell. (Note in the test + // below that removed edges are always sorted before added edges.) if (tracker->is_active() && !edges->empty() && is_shape_being_removed((*edges)[0]->face_edge->shape_id)) { // We probably need to update the InteriorTracker. ("Probably" because @@ -1284,11 +1610,12 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell, } } } - // Save the state of the edges being removed, so that it can be restored - // when we are finished processing this cell and its children. We don't - // need to save the state of the edges being added because they aren't being - // removed from "edges" and will therefore be updated normally as we visit - // this cell and its children. + // Save the state of the edges being removed so that it can be restored when + // we are finished processing this cell and its children. Below we not only + // remove those edges but also add new edges whose state only needs to be + // tracked within this subtree. We don't need to save the state of the + // edges being added because they aren't being removed from "edges" and will + // therefore be updated normally as we visit this cell and its children. tracker->SaveAndClearStateBefore(pending_additions_begin_); // Create a FaceEdge for each edge in this cell that isn't being removed. @@ -1309,8 +1636,9 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell, // cell is inside the shape, so we need to test all the edges against the // line segment from the cell center to the entry vertex. FaceEdge edge; - edge.shape_id = shape->id(); - edge.has_interior = (shape->dimension() == 2); + edge.shape_id = shape_id; + edge.has_interior = (shape->dimension() == 2 && + shape_id != tracker->partial_shape_id()); 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 @@ -1331,7 +1659,7 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell, if (edge.has_interior) tracker->TestEdge(shape_id, edge.edge); if (!S2::ClipToPaddedFace(edge.edge.v0, edge.edge.v1, pcell.id().face(), kCellPadding, &edge.a, &edge.b)) { - S2_LOG(DFATAL) << "Invariant failure in MutableS2ShapeIndex"; + S2_LOG(ERROR) << "Invariant failure in MutableS2ShapeIndex"; } face_edges->push_back(edge); } @@ -1347,7 +1675,7 @@ void MutableS2ShapeIndex::AbsorbIndexCell(const S2PaddedCell& pcell, } // Discard any edges from "edges" that are being removed, and append the // remainder to "new_edges". (This keeps the edges sorted by shape id.) - for (int i = 0; i < edges->size(); ++i) { + for (size_t i = 0; i < edges->size(); ++i) { const ClippedEdge* clipped = (*edges)[i]; if (!is_shape_being_removed(clipped->face_edge->shape_id)) { new_edges.insert(new_edges.end(), edges->begin() + i, edges->end()); @@ -1372,13 +1700,120 @@ bool MutableS2ShapeIndex::MakeIndexCell(const S2PaddedCell& pcell, return true; } - // Count the number of edges that have not reached their maximum level yet. - // Return false if there are too many such edges. - int count = 0; - for (const ClippedEdge* edge : edges) { - count += (pcell.level() < edge->face_edge->max_level); - if (count > options_.max_edges_per_cell()) - return false; + // We can show using amortized analysis that the total index size is + // + // O(c1 * n + c2 * (1 - f) / f * n) + // + // where n is the number of input edges (and where we also count an "edge" + // for each shape with an interior but no edges), f is the value of + // FLAGS_s2shape_index_min_short_edge_fraction, and c1 and c2 are constants + // where c2 is about 20 times larger than c1. + // + // First observe that the space used by a MutableS2ShapeIndex is + // proportional to the space used by all of its index cells, and the space + // used by an S2ShapeIndexCell is proportional to the number of edges that + // intersect that cell plus the number of shapes that contain the entire + // cell ("containing shapes"). Define an "index entry" as an intersecting + // edge or containing shape stored by an index cell. Our goal is then to + // bound the number of index entries. + // + // We divide the index entries into two groups. An index entry is "short" + // if it represents an edge that was considered short in that index cell's + // parent, and "long" otherwise. (Note that the long index entries also + // include the containing shapes mentioned above.) We then bound the + // maximum number of both types of index entries by associating them with + // edges that were considered short in those index cells' parents. + // + // First consider the short index entries for a given edge E. Let S be the + // set of index cells that intersect E and where E was considered short in + // those index cells' parents. Since E was short in each parent cell, the + // width of those parent cells is at least some fraction "g" of E's length + // (as controlled by FLAGS_s2shape_index_cell_size_to_long_edge_ratio). + // Therefore the minimum width of each cell in S is also at least some + // fraction of E's length (i.e., g / 2). This implies that there are at most + // a constant number c1 of such cells, since they all intersect E and do not + // overlap, which means that there are at most (c1 * n) short entries in + // total. + // + // With index_cell_size_to_long_edge_ratio = 1.0 (the default value), it can + // be shown that c1 = 10. In other words, it is not possible for a given + // edge to intersect more than 10 index cells where it was considered short + // in those cells' parents. The value of c1 can be reduced as low c1 = 4 by + // increasing index_cell_size_to_long_edge_ratio to about 3.1. (The reason + // the minimum value is 3.1 rather than 2.0 is that this ratio is defined in + // terms of the average edge length of cells at a given level, rather than + // their minimum width, and 2 * (S2::kAvgEdge / S2::kMinWidth) ~= 3.1.) + // + // Next we consider the long index entries. Let c2 be the maximum number of + // index cells where a given edge E was considered short in those cells' + // parents. (Unlike the case above, we do not require that these cells + // intersect E.) Because the minimum width of each parent cell is at least + // some fraction of E's length and the parent cells at a given level do not + // overlap, there can be at most a small constant number of index cells at + // each level where E is considered short in those cells' parents. For + // example, consider a very short edge E that intersects the midpoint of a + // cell edge at level 0. There are 16 cells at level 30 where E was + // considered short in the parent cell, 12 cells at each of levels 29..2, and + // 4 cells at levels 1 and 0 (pretending that all 6 face cells share a common + // "parent"). This yields a total of c2 = 360 index cells. This is actually + // the worst case for index_cell_size_to_long_edge_ratio >= 3.1; with the + // default value of 1.0 it is possible to have a few more index cells at + // levels 29 and 30, for a maximum of c2 = 366 index cells. + // + // The code below subdivides a given cell only if + // + // s > f * (s + l) + // + // where "f" is the min_short_edge_fraction parameter, "s" is the number of + // short edges that intersect the cell, and "l" is the number of long edges + // that intersect the cell plus an upper bound on the number of shapes that + // contain the entire cell. (It is an upper bound rather than an exact count + // because we use the number of shapes that contain an arbitrary vertex of + // the cell.) Note that the number of long index entries in each child of + // this cell is at most "l" because no child intersects more edges than its + // parent or is entirely contained by more shapes than its parent. + // + // The inequality above can be rearranged to give + // + // l < s * (1 - f) / f + // + // This says that each long index entry in a child cell can be associated + // with at most (1 - f) / f edges that were considered short when the parent + // cell was subdivided. Furthermore we know that there are at most c2 index + // cells where a given edge was considered short in the parent cell. Since + // there are only n edges in total, this means that the maximum number of + // long index entries is at most + // + // c2 * (1 - f) / f * n + // + // and putting this together with the result for short index entries gives + // the desired bound. + // + // There are a variety of ways to make this bound tighter, e.g. when "n" is + // relatively small. For example when the indexed geometry satisfies the + // requirements of S2BooleanOperation (i.e., shape interiors are disjoint) + // and the min_short_edge_fraction parameter is not too large, then the + // constant c2 above is only about half as big (i.e., c2 ~= 180). This is + // because the worst case under these circumstances requires having many + // shapes whose interiors overlap. + + // Continue subdividing if the proposed index cell would contain too many + // edges that are "short" relative to its size (as controlled by the + // FLAGS_s2shape_index_cell_size_to_long_edge_ratio parameter). Usually "too + // many" means more than options_.max_edges_per_cell(), but this value might + // be increased if the cell has a lot of long edges and/or containing shapes. + // This strategy ensures that the total index size is linear (see above). + if (edges.size() > static_cast(options_.max_edges_per_cell())) { + int max_short_edges = + max(options_.max_edges_per_cell(), + static_cast( + absl::GetFlag(FLAGS_s2shape_index_min_short_edge_fraction) * + (edges.size() + tracker->shape_ids().size()))); + int count = 0; + for (const ClippedEdge* edge : edges) { + count += (pcell.level() < edge->face_edge->max_level); + if (count > max_short_edges) return false; + } } // Possible optimization: Continue subdividing as long as exactly one child @@ -1424,7 +1859,7 @@ bool MutableS2ShapeIndex::MakeIndexCell(const S2PaddedCell& pcell, // "containing shapes" (those that contain the cell center). We keep track // of the index of the next intersecting edge and the next containing shape // as we go along. Both sets of shape ids are already sorted. - int enext = 0; + size_t enext = 0; ShapeIdSet::const_iterator cnext = cshape_ids.begin(); for (int i = 0; i < num_shapes; ++i) { S2ClippedShape* clipped = base + i; @@ -1448,7 +1883,7 @@ bool MutableS2ShapeIndex::MakeIndexCell(const S2PaddedCell& pcell, ++enext; } clipped->Init(eshape_id, enext - ebegin); - for (int e = ebegin; e < enext; ++e) { + for (size_t e = ebegin; e < enext; ++e) { clipped->set_edge(e - ebegin, edges[e]->face_edge->edge_id); } if (cshape_id == eshape_id) { @@ -1462,7 +1897,7 @@ bool MutableS2ShapeIndex::MakeIndexCell(const S2PaddedCell& pcell, // is much faster to give an insertion hint in this case. Otherwise the // hint doesn't do much harm. With more effort we could provide a hint even // during incremental updates, but this is probably not worth the effort. - cell_map_.insert(cell_map_.end(), std::make_pair(pcell.id(), cell)); + cell_map_.insert(cell_map_.end(), make_pair(pcell.id(), cell)); // Shift the InteriorTracker focus point to the exit vertex of this cell. if (tracker->is_active() && !edges.empty()) { @@ -1512,7 +1947,7 @@ int MutableS2ShapeIndex::CountShapes(const vector& edges, size_t MutableS2ShapeIndex::SpaceUsed() const { size_t size = sizeof(*this); - size += shapes_.capacity() * sizeof(std::unique_ptr); + size += shapes_.capacity() * sizeof(unique_ptr); // cell_map_ itself is already included in sizeof(*this). size += cell_map_.bytes_used() - sizeof(cell_map_); size += cell_map_.size() * sizeof(S2ShapeIndexCell); @@ -1528,9 +1963,12 @@ size_t MutableS2ShapeIndex::SpaceUsed() const { } } if (pending_removals_ != nullptr) { + size += sizeof(*pending_removals_); size += pending_removals_->capacity() * sizeof(RemovedShape); + for (const RemovedShape& removed : *pending_removals_) { + size += removed.edges.capacity() * sizeof(S2Shape::Edge); + } } - return size; } @@ -1542,6 +1980,9 @@ void MutableS2ShapeIndex::Encode(Encoder* encoder) const { uint64 max_edges = options_.max_edges_per_cell(); encoder->put_varint64(max_edges << 2 | kCurrentEncodingVersionNumber); + // The index will be built anyway when we iterate through it, but building + // it in advance lets us size the cell_ids vector correctly. + ForceBuild(); vector cell_ids; cell_ids.reserve(cell_map_.size()); s2coding::StringVectorEncoder encoded_cells; @@ -1563,7 +2004,7 @@ bool MutableS2ShapeIndex::Init(Decoder* decoder, 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) { + for (size_t 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)); @@ -1574,12 +2015,12 @@ bool MutableS2ShapeIndex::Init(Decoder* decoder, if (!cell_ids.Init(decoder)) return false; if (!encoded_cells.Init(decoder)) return false; - for (int i = 0; i < cell_ids.size(); ++i) { + for (size_t 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)); + cell_map_.insert(cell_map_.end(), make_pair(id, cell)); } return true; } diff --git a/src/s2/mutable_s2shape_index.h b/src/s2/mutable_s2shape_index.h index 2585e8f7..145cdacf 100644 --- a/src/s2/mutable_s2shape_index.h +++ b/src/s2/mutable_s2shape_index.h @@ -25,26 +25,35 @@ #include #include +#include "absl/base/macros.h" +#include "absl/base/thread_annotations.h" +#include "absl/container/btree_map.h" +#include "absl/synchronization/mutex.h" +#include "s2/base/commandlineflags.h" +#include "s2/base/commandlineflags_declare.h" #include "s2/base/integral_types.h" #include "s2/base/logging.h" -#include "s2/base/mutex.h" #include "s2/base/spinlock.h" #include "s2/_fp_contract_off.h" +#include "s2/r1interval.h" +#include "s2/r2rect.h" #include "s2/s2cell_id.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" #include "s2/s2pointutil.h" #include "s2/s2shape.h" #include "s2/s2shape_index.h" -#include "absl/base/macros.h" -#include "absl/base/thread_annotations.h" -#include "absl/memory/memory.h" -#include "absl/container/btree_map.h" +#include "s2/s2shapeutil_shape_edge_id.h" +#include "s2/util/coding/coder.h" + +class S2PaddedCell; namespace s2internal { // Hack to expose bytes_used. template class BTreeMap : public absl::btree_map { -public: + public: size_t bytes_used() const { return this->tree_.bytes_used(); } }; } // namespace s2internal @@ -53,14 +62,15 @@ class BTreeMap : public absl::btree_map { // The objects in the index are known as "shapes", and may consist of points, // polylines, and/or polygons, possibly overlapping. The index makes it very // fast to answer queries such as finding nearby shapes, measuring distances, -// testing for intersection and containment, etc. +// testing for intersection and containment, etc. It is one of several +// implementations of the S2ShapeIndex interface (see EncodedS2ShapeIndex). // // MutableS2ShapeIndex allows not only building an index, but also updating it -// incrementally by adding or removing shapes (hence its name). It is one of -// several implementations of the S2ShapeIndex interface. MutableS2ShapeIndex -// is designed to be compact; usually it is smaller than the underlying -// geometry being indexed. It is capable of indexing up to hundreds of -// millions of edges. The index is also fast to construct. +// incrementally by adding or removing shapes (hence its name). It is designed +// to be compact; usually the index is smaller than the underlying geometry. +// It is capable of indexing up to hundreds of millions of edges. The index is +// also fast to construct. The index size and construction time are guaranteed +// to be linear in the number of input edges. // // There are a number of built-in classes that work with S2ShapeIndex objects. // Generally these classes accept any collection of geometry that can be @@ -77,7 +87,8 @@ class BTreeMap : public absl::btree_map { // - S2BooleanOperation: computes boolean operations such as union, // and boolean predicates such as containment. // -// - S2ShapeIndexRegion: computes approximations for a collection of geometry. +// - S2ShapeIndexRegion: can be used together with S2RegionCoverer to +// approximate geometry as a set of S2CellIds. // // - S2ShapeIndexBufferedRegion: computes approximations that have been // expanded by a given radius. @@ -89,7 +100,7 @@ class BTreeMap : public absl::btree_map { // const vector& polygons) { // MutableS2ShapeIndex index; // for (auto polygon : polygons) { -// index.Add(absl::make_unique(polygon)); +// index.Add(std::make_unique(polygon)); // } // auto query = MakeS2ContainsPointQuery(&index); // for (const auto& point : points) { @@ -115,10 +126,10 @@ class BTreeMap : public absl::btree_map { // if one thread updates the index, you must ensure that no other thread is // reading or updating the index at the same time. // -// TODO(ericv): MutableS2ShapeIndex has an Encode() method that allows the -// index to be serialized. An encoded S2ShapeIndex can be decoded either into -// its original form (MutableS2ShapeIndex) or into an EncodedS2ShapeIndex. -// The key property of EncodedS2ShapeIndex is that it can be constructed +// MutableS2ShapeIndex has an Encode() method that allows the index to be +// serialized. An encoded S2ShapeIndex can be decoded either into its +// original form (MutableS2ShapeIndex) or into an EncodedS2ShapeIndex. The +// key property of EncodedS2ShapeIndex is that it can be constructed // instantaneously, since the index is kept in its original encoded form. // Data is decoded only when an operation needs it. For example, to determine // which shapes(s) contain a given query point only requires decoding the data @@ -128,6 +139,10 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { using CellMap = s2internal::BTreeMap; public: + // The amount by which cells are "padded" to compensate for numerical errors + // when clipping line segments to cell boundaries. + static const double kCellPadding; + // Options that affect construction of the MutableS2ShapeIndex. class Options { public: @@ -166,13 +181,65 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { ~MutableS2ShapeIndex() override; + MutableS2ShapeIndex(MutableS2ShapeIndex&&); + MutableS2ShapeIndex& operator=(MutableS2ShapeIndex&&); + // Initialize a MutableS2ShapeIndex with the given options. This method may - // only be called when the index is empty (i.e. newly created or Reset() has - // just been called). + // only be called when the index is empty (i.e. newly created or Clear() has + // just been called). May be called before or after set_memory_tracker(). void Init(const Options& options); const Options& options() const { return options_; } + // Specifies that memory usage should be tracked and/or limited by the given + // S2MemoryTracker. For example: + // + // S2MemoryTracker tracker; + // tracker.set_limit(500 << 20); // 500 MB memory limit + // MutableS2ShapeIndex index; + // index.set_memory_tracker(&tracker); + // + // If the memory limit is exceeded, an appropriate status is returned in + // memory_tracker()->error() and any partially built index is discarded + // (equivalent to calling Minimize()). + // + // This method may be called multiple times in order to switch from one + // memory tracker to another or stop memory tracking altogether (by passing + // nullptr) in which case the memory usage due to this index is subtracted. + // + // REQUIRES: The lifetime of "tracker" must exceed the lifetime of the index + // unless set_memory_tracker(nullptr) is called to stop memory + // tracking before the index destructor is called. + // + // This implies that the S2MemoryTracker must be declared *before* + // the MutableS2ShapeIndex in the example above. + // + // CAVEATS: + // + // - This method is not const and is therefore not thread-safe. + // + // - Does not track memory used by the S2Shapes in the index. + // + // - While the index representation itself is tracked very accurately, + // the temporary data needed for index construction is tracked using + // heuristics and may be underestimated or overestimated. + // + // - Temporary memory usage is typically 10x larger than the final index + // size, however it can be reduced by specifying a suitable value for + // FLAGS_s2shape_index_tmp_memory_budget (the default is 100 MB). If + // more temporary memory than this is needed during construction, index + // updates will be split into multiple batches in order to keep the + // estimated temporary memory usage below this limit. + // + // - S2MemoryTracker::limit() has no effect on how much temporary memory + // MutableS2ShapeIndex will attempt to use during index construction; it + // simply causes an error to be returned when the limit would otherwise + // be exceeded. If you set a memory limit smaller than 100MB and want to + // reduce memory usage rather than simply generating an error then you + // should also set FLAGS_s2shape_index_tmp_memory_budget appropriately. + void set_memory_tracker(S2MemoryTracker* tracker); + S2MemoryTracker* memory_tracker() const { return mem_tracker_.tracker(); } + // The number of distinct shape ids that have been assigned. This equals // the number of shapes in the index provided that no shapes have ever been // removed. (Shape ids are not reused.) @@ -198,6 +265,18 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { // s2shapeutil::CompactEncodeTaggedShapes(index, encoder); // index.Encode(encoder); // + // The encoded size is typically much smaller than the in-memory size. + // Here are a few examples: + // + // Number of edges In-memory space used Encoded size (%) + // -------------------------------------------------------------- + // 8 192 8 4% + // 768 18,264 2,021 11% + // 3,784,212 80,978,992 17,039,020 21% + // + // The encoded form also has the advantage of being a contiguous block of + // memory. + // // REQUIRES: "encoder" uses the default constructor, so that its buffer // can be enlarged as necessary by calling Ensure(int). void Encode(Encoder* encoder) const; @@ -251,14 +330,20 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { // S2Point center() const; const S2ShapeIndexCell& cell() const; - // IteratorBase API: + // S2CellIterator 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; + + bool Locate(const S2Point& target) override { + return LocateImpl(*this, target); + } + + S2CellRelation Locate(S2CellId target) override { + return LocateImpl(*this, target); + } protected: const S2ShapeIndexCell* GetCell() const override; @@ -275,6 +360,9 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { // assigns a unique id to the shape (shape->id()) and returns that id. // Shape ids are assigned sequentially starting from 0 in the order shapes // are added. Invalidates all iterators and their associated data. + // + // Note that this method is not affected by S2MemoryTracker, i.e. shapes can + // continue to be added even once the specified limit has been reached. int Add(std::unique_ptr shape); // Removes the given shape from the index and return ownership to the caller. @@ -296,14 +384,27 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { size_t SpaceUsed() const override; // Calls to Add() and Release() are normally queued and processed on the - // first subsequent query (in a thread-safe way). This has many advantages, - // the most important of which is that sometimes there *is* no subsequent - // query, which lets us avoid building the index completely. + // first subsequent query (in a thread-safe way). Building the index lazily + // in this way has several advantages, the most important of which is that + // sometimes there *is* no subsequent query and the index doesn't need to be + // built at all. // - // This method forces any pending updates to be applied immediately. - // Calling this method is rarely a good idea. (One valid reason is to - // exclude the cost of building the index from benchmark results.) - void ForceBuild(); + // In contrast, ForceBuild() causes any pending updates to be applied + // immediately. It is thread-safe and may be called simultaneously with + // other "const" methods (see notes on thread safety above). Similarly this + // method is "const" since it does not modify the visible index contents. + // + // ForceBuild() should not normally be called since it prevents lazy index + // construction (which is usually benficial). Some reasons to use it + // include: + // + // - To exclude the cost of building the index from benchmark results. + // - To ensure that the first subsequent query is as fast as possible. + // - To ensure that the index can be built successfully without exceeding a + // specified S2MemoryTracker limit (see the constructor for details). + // + // Note that this method is thread-safe. + void ForceBuild() const; // Returns true if there are no pending updates that need to be applied. // This can be useful to avoid building the index unnecessarily, or for @@ -325,38 +426,37 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { friend class MutableS2ShapeIndexTest; friend class S2Stats; + class BatchGenerator; + class EdgeAllocator; + class InteriorTracker; struct BatchDescriptor; struct ClippedEdge; - class EdgeAllocator; struct FaceEdge; - class InteriorTracker; struct RemovedShape; + using ShapeEdgeId = s2shapeutil::ShapeEdgeId; using ShapeIdSet = std::vector; // When adding a new encoding, be aware that old binaries will not be able // to decode it. - static const unsigned char kCurrentEncodingVersionNumber = 0; + static constexpr 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; + void MarkIndexStale(); void MaybeApplyUpdates() const; void ApplyUpdatesThreadSafe(); void ApplyUpdatesInternal(); - void GetUpdateBatches(std::vector* batches) const; - static void GetBatchSizes(int num_items, int max_batches, - double final_bytes_per_item, - double high_water_bytes_per_item, - double preferred_max_bytes_per_batch, - std::vector* batch_sizes); + std::vector GetUpdateBatches() const; void ReserveSpace(const BatchDescriptor& batch, - std::vector all_edges[6]) const; - void AddShape(int id, std::vector all_edges[6], + std::vector all_edges[6]); + void AddShape(const S2Shape* shape, int edges_begin, int edges_end, + std::vector all_edges[6], InteriorTracker* tracker) const; void RemoveShape(const RemovedShape& removed, std::vector all_edges[6], InteriorTracker* tracker) const; + void FinishPartialShape(int shape_id); void AddFaceEdge(FaceEdge* edge, std::vector all_edges[6]) const; void UpdateFaceEdges(int face, const std::vector& face_edges, InteriorTracker* tracker); @@ -394,10 +494,6 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { std::vector child_edges[2], EdgeAllocator* alloc); - // The amount by which cells are "padded" to compensate for numerical errors - // when clipping line segments to cell boundaries. - static const double kCellPadding; - // The shapes in the index, accessed by their shape id. Removed shapes are // replaced by nullptr pointers. std::vector> shapes_; @@ -460,7 +556,7 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { FRESH, // There are no pending updates. }; // Reads and writes to this field are guarded by "lock_". - std::atomic index_status_; + std::atomic index_status_{FRESH}; // UpdateState holds temporary data related to thread synchronization. It // is only allocated while updates are being applied. @@ -485,19 +581,105 @@ class MutableS2ShapeIndex final : public S2ShapeIndex { }; std::unique_ptr update_state_; + S2MemoryTracker::Client mem_tracker_; + +#ifndef SWIG // Documented in the .cc file. - void UnlockAndSignal() - UNLOCK_FUNCTION(lock_) - UNLOCK_FUNCTION(update_state_->wait_mutex); + void UnlockAndSignal() ABSL_UNLOCK_FUNCTION(lock_) + ABSL_UNLOCK_FUNCTION(update_state_->wait_mutex); +#endif MutableS2ShapeIndex(const MutableS2ShapeIndex&) = delete; - void operator=(const MutableS2ShapeIndex&) = delete; + MutableS2ShapeIndex& operator=(const MutableS2ShapeIndex&) = delete; }; +// The following flag can be used to limit the amount of temporary memory used +// when building an S2ShapeIndex. See the .cc file for details. +// +// DEFAULT: 100 MB +S2_DECLARE_int64(s2shape_index_tmp_memory_budget); + ////////////////// Implementation details follow //////////////////// +// A BatchDescriptor represents a set of pending updates that will be applied +// at the same time. The batch consists of all edges in (shape id, edge id) +// order from "begin" (inclusive) to "end" (exclusive). Note that the last +// shape in a batch may have only some of its edges added. The first batch +// also implicitly includes all shapes being removed. "num_edges" is the +// total number of edges that will be added or removed in this batch. +struct MutableS2ShapeIndex::BatchDescriptor { + // REQUIRES: If end.edge_id != 0, it must refer to a valid edge. + ShapeEdgeId begin, end; + int num_edges; +}; + +// The purpose of BatchGenerator is to divide large updates into batches such +// that all batches use approximately the same amount of high-water memory. +// This class is defined here so that it can be tested independently. +class MutableS2ShapeIndex::BatchGenerator { + public: + // Given the total number of edges that will be removed and added, prepares + // to divide the edges into batches. "shape_id_begin" identifies the first + // shape whose edges will be added. + BatchGenerator(int num_edges_removed, int num_edges_added, + int shape_id_begin); + + // Indicates that the given shape will be added to the index. Shapes with + // few edges will be grouped together into a single batch, while shapes with + // many edges will be split over several batches if necessary. + void AddShape(int shape_id, int num_edges); + + // Returns a vector describing each batch. This method should be called + // once all shapes have been added. + std::vector Finish(); + + private: + // Returns a vector indicating the maximum number of edges in each batch. + // (The actual batch sizes are adjusted later in order to avoid splitting + // shapes between batches unnecessarily.) + static std::vector GetMaxBatchSizes(int num_edges_removed, + int num_edges_added); + + // Returns the maximum number of edges in the current batch. + int max_batch_size() const { return max_batch_sizes_[batch_index_]; } + + // Returns the maximum number of edges in the next batch. + int next_max_batch_size() const { return max_batch_sizes_[batch_index_ + 1]; } + + // Adds the given number of edges to the current batch. + void ExtendBatch(int num_edges) { + batch_size_ += num_edges; + } + + // Adds the given number of edges to the current batch, ending with the edge + // just before "batch_end", and then starts a new batch. + void FinishBatch(int num_edges, ShapeEdgeId batch_end); + + // A vector representing the ideal number of edges in each batch; the batch + // sizes gradually decrease to ensure that each batch uses approximately the + // same total amount of memory as the index grows. The actual batch sizes + // are then adjusted based on how many edges each shape has in order to + // avoid splitting shapes between batches unnecessarily. + std::vector max_batch_sizes_; + + // The maximum size of the current batch is determined by how many edges + // have been added to the index so far. For example if GetBatchSizes() + // returned {100, 70, 50, 30} and we have added 0 edges, the current batch + // size is 100. But if we have already added 90 edges then the current + // batch size would be 70, and if have added 150 edges the batch size would + // be 50. We keep track of (1) the current index into batch_sizes and (2) + // the number of edges remaining before we increment the batch index. + int batch_index_ = 0; + int batch_index_edges_left_ = 0; + + ShapeEdgeId batch_begin_; // The start of the current batch. + int shape_id_end_; // One beyond the last shape to be added. + int batch_size_ = 0; // The number of edges in the current batch. + std::vector batches_; // The completed batches so far. +}; + inline MutableS2ShapeIndex::Iterator::Iterator() : index_(nullptr) { } @@ -570,18 +752,15 @@ inline void MutableS2ShapeIndex::Iterator::Seek(S2CellId target) { inline std::unique_ptr MutableS2ShapeIndex::NewIterator(InitialPosition pos) const { - return absl::make_unique(this, pos); + return std::make_unique(this, pos); } -inline bool MutableS2ShapeIndex::is_fresh() const { - return index_status_.load(std::memory_order_relaxed) == FRESH; +inline void MutableS2ShapeIndex::ForceBuild() const { + MaybeApplyUpdates(); } -// Return true if this is the first update to the index. -inline bool MutableS2ShapeIndex::is_first_update() const { - // Note that it is not sufficient to check whether cell_map_ is empty, since - // entries are added during the update process. - return pending_additions_begin_ == 0; +inline bool MutableS2ShapeIndex::is_fresh() const { + return index_status_.load(std::memory_order_relaxed) == FRESH; } // Given that the given shape is being updated, return true if it is being diff --git a/src/s2/r1interval.h b/src/s2/r1interval.h index f1208038..a502b308 100644 --- a/src/s2/r1interval.h +++ b/src/s2/r1interval.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" @@ -66,8 +67,9 @@ class R1Interval { } } - // Accessors methods. + // The low bound of the interval. double lo() const { return bounds_[0]; } + // The high bound of the interval. double hi() const { return bounds_[1]; } // Methods to modify one endpoint of an existing R1Interval. Do not use @@ -97,10 +99,12 @@ class R1Interval { // is negative. double GetLength() const { return hi() - lo(); } + // Returns true if the given point is in the closed interval [lo, hi]. bool Contains(double p) const { return p >= lo() && p <= hi(); } + // Returns true if the given point is in the open interval (lo, hi). bool InteriorContains(double p) const { return p > lo() && p < hi(); } @@ -145,9 +149,14 @@ class R1Interval { // Expand the interval so that it contains the given point "p". void AddPoint(double p) { - if (is_empty()) { set_lo(p); set_hi(p); } - else if (p < lo()) { set_lo(p); } // NOLINT - else if (p > hi()) { set_hi(p); } // NOLINT + if (is_empty()) { + set_lo(p); + set_hi(p); + } else if (p < lo()) { + set_lo(p); + } else if (p > hi()) { + set_hi(p); + } } // Expand the interval so that it contains the given interval "y". diff --git a/src/s2/r2rect.cc b/src/s2/r2rect.cc index 030578a2..b68f3ddd 100644 --- a/src/s2/r2rect.cc +++ b/src/s2/r2rect.cc @@ -17,9 +17,8 @@ #include "s2/r2rect.h" -#include +#include -#include "s2/base/logging.h" #include "s2/r1interval.h" #include "s2/r2.h" @@ -30,11 +29,6 @@ R2Rect R2Rect::FromCenterSize(const R2Point& center, const R2Point& size) { center.y() + 0.5 * size.y())); } -R2Rect R2Rect::FromPointPair(const R2Point& p1, const R2Point& p2) { - return R2Rect(R1Interval::FromPointPair(p1.x(), p2.x()), - R1Interval::FromPointPair(p1.y(), p2.y())); -} - bool R2Rect::Contains(const R2Rect& other) const { return x().Contains(other.x()) && y().Contains(other.y()); } diff --git a/src/s2/r2rect.h b/src/s2/r2rect.h index 7700fae4..47c6cca8 100644 --- a/src/s2/r2rect.h +++ b/src/s2/r2rect.h @@ -19,6 +19,7 @@ #define S2_R2RECT_H_ #include +#include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" @@ -135,7 +136,7 @@ class R2Rect { // Return a rectangle that has been expanded on each side in the x-direction // by margin.x(), and on each side in the y-direction by margin.y(). If - // either margin is empty, then shrink the interval on the corresponding + // either margin is negative, then shrink the interval on the corresponding // sides instead. The resulting rectangle may be empty. Any expansion of // an empty rectangle remains empty. R2Rect Expanded(const R2Point& margin) const; @@ -152,6 +153,9 @@ class R2Rect { // Return true if two rectangles contains the same set of points. bool operator==(const R2Rect& other) const; + // Return true if two rectangles do not contain the same set of points. + bool operator!=(const R2Rect& other) const; + // Return true if the x- and y-intervals of the two rectangles are the same // up to the given tolerance (see r1interval.h for details). bool ApproxEquals(const R2Rect& other, double max_error = 1e-15) const; @@ -181,6 +185,11 @@ inline R2Rect R2Rect::Empty() { return R2Rect(R1Interval::Empty(), R1Interval::Empty()); } +inline R2Rect R2Rect::FromPointPair(const R2Point& p1, const R2Point& p2) { + return R2Rect(R1Interval::FromPointPair(p1.x(), p2.x()), + R1Interval::FromPointPair(p1.y(), p2.y())); +} + inline bool R2Rect::is_valid() const { // The x/y ranges must either be both empty or both non-empty. return x().is_empty() == y().is_empty(); @@ -229,6 +238,10 @@ inline bool R2Rect::operator==(const R2Rect& other) const { return x() == other.x() && y() == other.y(); } +inline bool R2Rect::operator!=(const R2Rect& other) const { + return !operator==(other); +} + std::ostream& operator<<(std::ostream& os, const R2Rect& r); #endif // S2_R2RECT_H_ diff --git a/src/s2/s1angle.cc b/src/s2/s1angle.cc index 78cceda6..6696aee3 100644 --- a/src/s2/s1angle.cc +++ b/src/s2/s1angle.cc @@ -17,11 +17,14 @@ #include "s2/s1angle.h" +#include + #include #include #include #include "s2/s2latlng.h" +#include "s2/s2point.h" S1Angle::S1Angle(const S2Point& x, const S2Point& y) : radians_(x.Angle(y)) { @@ -46,7 +49,8 @@ std::ostream& operator<<(std::ostream& os, S1Angle a) { double degrees = a.degrees(); char buffer[13]; int sz = snprintf(buffer, sizeof(buffer), "%.7f", degrees); - if (sz >= 0 && sz < sizeof(buffer)) { + // Fix sign/unsign comparison for client that use `-Wextra` (e.g. Chrome). + if (sz >= 0 && static_cast(sz) < sizeof(buffer)) { return os << buffer; } else { return os << degrees; diff --git a/src/s2/s1angle.h b/src/s2/s1angle.h index 96291e9c..93137c6c 100644 --- a/src/s2/s1angle.h +++ b/src/s2/s1angle.h @@ -131,23 +131,24 @@ class S1Angle { // Return the absolute value of an angle. S1Angle abs() const; + friend S1Angle abs(S1Angle a); // Comparison operators. - friend bool operator==(S1Angle x, S1Angle y); - friend bool operator!=(S1Angle x, S1Angle y); - friend bool operator<(S1Angle x, S1Angle y); - friend bool operator>(S1Angle x, S1Angle y); - friend bool operator<=(S1Angle x, S1Angle y); - friend bool operator>=(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator==(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator!=(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator<(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator>(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator<=(S1Angle x, S1Angle y); + friend IFNDEF_SWIG(constexpr) bool operator>=(S1Angle x, S1Angle y); // Simple arithmetic operators for manipulating S1Angles. - friend S1Angle operator-(S1Angle a); - friend S1Angle operator+(S1Angle a, S1Angle b); - friend S1Angle operator-(S1Angle a, S1Angle b); - friend S1Angle operator*(double m, S1Angle a); - friend S1Angle operator*(S1Angle a, double m); - friend S1Angle operator/(S1Angle a, double m); - friend double operator/(S1Angle a, S1Angle b); + friend IFNDEF_SWIG(constexpr) S1Angle operator-(S1Angle a); + friend IFNDEF_SWIG(constexpr) S1Angle operator+(S1Angle a, S1Angle b); + friend IFNDEF_SWIG(constexpr) S1Angle operator-(S1Angle a, S1Angle b); + friend IFNDEF_SWIG(constexpr) S1Angle operator*(double m, S1Angle a); + friend IFNDEF_SWIG(constexpr) S1Angle operator*(S1Angle a, double m); + friend IFNDEF_SWIG(constexpr) S1Angle operator/(S1Angle a, double m); + friend IFNDEF_SWIG(constexpr) double operator/(S1Angle a, S1Angle b); S1Angle& operator+=(S1Angle a); S1Angle& operator-=(S1Angle a); S1Angle& operator*=(double m); @@ -164,9 +165,9 @@ class S1Angle { // Normalize this angle to the range (-180, 180] degrees. void Normalize(); - // When S1Angle is used as a key in one of the btree container types - // (util/btree), indicate that linear rather than binary search should be - // used. This is much faster when the comparison function is cheap. + // When S1Angle is used as a key in one of the absl::btree container types, + // indicate that linear rather than binary search should be used. This is + // much faster when the comparison function is cheap. typedef std::true_type absl_btree_prefer_linear_node_search; private: @@ -215,55 +216,59 @@ inline S1Angle S1Angle::abs() const { return S1Angle(std::fabs(radians_)); } -inline bool operator==(S1Angle x, S1Angle y) { +inline S1Angle abs(S1Angle a) { + return S1Angle(std::fabs(a.radians_)); +} + +inline constexpr bool operator==(S1Angle x, S1Angle y) { return x.radians() == y.radians(); } -inline bool operator!=(S1Angle x, S1Angle y) { +inline constexpr bool operator!=(S1Angle x, S1Angle y) { return x.radians() != y.radians(); } -inline bool operator<(S1Angle x, S1Angle y) { +inline constexpr bool operator<(S1Angle x, S1Angle y) { return x.radians() < y.radians(); } -inline bool operator>(S1Angle x, S1Angle y) { +inline constexpr bool operator>(S1Angle x, S1Angle y) { return x.radians() > y.radians(); } -inline bool operator<=(S1Angle x, S1Angle y) { +inline constexpr bool operator<=(S1Angle x, S1Angle y) { return x.radians() <= y.radians(); } -inline bool operator>=(S1Angle x, S1Angle y) { +inline constexpr bool operator>=(S1Angle x, S1Angle y) { return x.radians() >= y.radians(); } -inline S1Angle operator-(S1Angle a) { +inline constexpr S1Angle operator-(S1Angle a) { return S1Angle::Radians(-a.radians()); } -inline S1Angle operator+(S1Angle a, S1Angle b) { +inline constexpr S1Angle operator+(S1Angle a, S1Angle b) { return S1Angle::Radians(a.radians() + b.radians()); } -inline S1Angle operator-(S1Angle a, S1Angle b) { +inline constexpr S1Angle operator-(S1Angle a, S1Angle b) { return S1Angle::Radians(a.radians() - b.radians()); } -inline S1Angle operator*(double m, S1Angle a) { +inline constexpr S1Angle operator*(double m, S1Angle a) { return S1Angle::Radians(m * a.radians()); } -inline S1Angle operator*(S1Angle a, double m) { +inline constexpr S1Angle operator*(S1Angle a, double m) { return S1Angle::Radians(m * a.radians()); } -inline S1Angle operator/(S1Angle a, double m) { +inline constexpr S1Angle operator/(S1Angle a, double m) { return S1Angle::Radians(a.radians() / m); } -inline double operator/(S1Angle a, S1Angle b) { +inline constexpr double operator/(S1Angle a, S1Angle b) { return a.radians() / b.radians(); } diff --git a/src/s2/s1chord_angle.cc b/src/s2/s1chord_angle.cc index eb5fccc4..a79b0759 100644 --- a/src/s2/s1chord_angle.cc +++ b/src/s2/s1chord_angle.cc @@ -17,11 +17,12 @@ #include "s2/s1chord_angle.h" +#include #include #include +#include #include "s2/s1angle.h" -#include "s2/s2pointutil.h" using std::max; using std::min; @@ -88,16 +89,17 @@ double S1ChordAngle::GetS2PointConstructorMaxError() const { double S1ChordAngle::GetS1AngleConstructorMaxError() const { // Assuming that an accurate math library is being used, the sin() call and - // the multiply each have a relative error of 0.5 * DBL_EPSILON. - return DBL_EPSILON * length2_; + // the multiply each have a relative error of 0.5 * DBL_EPSILON. However + // the sin() error is squared. + return 1.5 * DBL_EPSILON * length2_; } S1ChordAngle operator+(S1ChordAngle a, S1ChordAngle b) { // Note that this method is much more efficient than converting the chord // angles to S1Angles and adding those. It requires only one square root // plus a few additions and multiplications. - S2_DCHECK(!a.is_special()); - S2_DCHECK(!b.is_special()); + S2_DCHECK(!a.is_special()) << a; + S2_DCHECK(!b.is_special()) << b; // Optimization for the common case where "b" is an error tolerance // parameter that happens to be set to zero. @@ -120,14 +122,19 @@ S1ChordAngle operator+(S1ChordAngle a, S1ChordAngle b) { S1ChordAngle operator-(S1ChordAngle a, S1ChordAngle b) { // See comments in operator+(). - S2_DCHECK(!a.is_special()); - S2_DCHECK(!b.is_special()); + S2_DCHECK(!a.is_special()) << a; + S2_DCHECK(!b.is_special()) << b; double a2 = a.length2(), b2 = b.length2(); if (b2 == 0) return a; if (a2 <= b2) return S1ChordAngle::Zero(); double x = a2 * (1 - 0.25 * b2); double y = b2 * (1 - 0.25 * a2); - return S1ChordAngle(max(0.0, x + y - 2 * sqrt(x * y))); + + // The calculation below is formulated differently (with two square roots + // rather than one) to avoid excessive cancellation error when two nearly + // equal values are subtracted. + double c = max(0.0, sqrt(x) - sqrt(y)); + return S1ChordAngle(c * c); } double sin2(S1ChordAngle a) { diff --git a/src/s2/s1chord_angle.h b/src/s2/s1chord_angle.h index 40772213..25659cde 100644 --- a/src/s2/s1chord_angle.h +++ b/src/s2/s1chord_angle.h @@ -18,28 +18,104 @@ #ifndef S2_S1CHORD_ANGLE_H_ #define S2_S1CHORD_ANGLE_H_ +#include #include #include #include #include +#include "s2/base/integral_types.h" #include "s2/_fp_contract_off.h" #include "s2/s1angle.h" +#include "s2/s2point.h" #include "s2/s2pointutil.h" // S1ChordAngle represents the angle subtended by a chord (i.e., the straight // line segment connecting two points on the sphere). Its representation // makes it very efficient for computing and comparing distances, but unlike // S1Angle it is only capable of representing angles between 0 and Pi radians. -// Generally, S1ChordAngle should only be used in loops where many angles need -// to be calculated and compared. Otherwise it is simpler to use S1Angle. +// S1ChordAngle is intended for applications where many angles need to be +// computed and compared, otherwise it is simpler to use S1Angle. // // S1ChordAngle also loses some accuracy as the angle approaches Pi radians. -// 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 nanometer), -// which is about the same as S1Angle. +// There are several different ways to measure this error, including the +// representational error (i.e., how accurately S1ChordAngle can represent +// angles near Pi radians), the conversion error (i.e., how much precision is +// lost when an S1Angle is converted to an S1ChordAngle), and the measurement +// error (i.e., how accurate the S1ChordAngle(a, b) constructor is when the +// points A and B are separated by angles close to Pi radians). All of these +// errors differ by a small constant factor. +// +// For the measurement error (which is the largest of these errors and also +// the most important in practice), let the angle between A and B be (Pi - x) +// radians, i.e. A and B are within "x" radians of being antipodal. The +// corresponding chord length is +// +// r = 2 * sin((Pi - x) / 2) = 2 * cos(x / 2) . +// +// For values of x not close to Pi the relative error in the squared chord +// length is at most 4.5 * DBL_EPSILON (see GetS2PointConstructorMaxError). +// The relative error in "r" is thus at most 2.25 * DBL_EPSILON ~= 5e-16. To +// convert this error into an equivalent angle, we have +// +// |dr / dx| = sin(x / 2) +// +// and therefore +// +// |dx| = dr / sin(x / 2) +// = 5e-16 * (2 * cos(x / 2)) / sin(x / 2) +// = 1e-15 / tan(x / 2) +// +// The maximum error is attained when +// +// x = |dx| +// = 1e-15 / tan(x / 2) +// ~= 1e-15 / (x / 2) +// ~= sqrt(2e-15) +// +// In summary, the measurement error for an angle (Pi - x) is at most +// +// dx = min(1e-15 / tan(x / 2), sqrt(2e-15)) +// (~= min(2e-15 / x, sqrt(2e-15)) when x is small). +// +// On the Earth's surface (assuming a radius of 6371km), this corresponds to +// the following worst-case measurement errors: +// +// Accuracy: Unless antipodal to within: +// --------- --------------------------- +// 6.4 nanometers 10,000 km (90 degrees) +// 1 micrometer 81.2 kilometers +// 1 millimeter 81.2 meters +// 1 centimeter 8.12 meters +// 28.5 centimeters 28.5 centimeters +// +// The representational and conversion errors referred to earlier are somewhat +// smaller than this. For example, maximum distance between adjacent +// representable S1ChordAngle values is only 13.5 cm rather than 28.5 cm. To +// see this, observe that the closest representable value to r^2 = 4 is +// r^2 = 4 * (1 - DBL_EPSILON / 2). Thus r = 2 * (1 - DBL_EPSILON / 4) and +// the angle between these two representable values is +// +// x = 2 * acos(r / 2) +// = 2 * acos(1 - DBL_EPSILON / 4) +// ~= 2 * asin(sqrt(DBL_EPSILON / 2) +// ~= sqrt(2 * DBL_EPSILON) +// ~= 2.1e-8 +// +// which is 13.5 cm on the Earth's surface. +// +// The worst case rounding error occurs when the value halfway between these +// two representable values is rounded up to 4. This halfway value is +// r^2 = (4 * (1 - DBL_EPSILON / 4)), thus r = 2 * (1 - DBL_EPSILON / 8) and +// the worst case rounding error is +// +// x = 2 * acos(r / 2) +// = 2 * acos(1 - DBL_EPSILON / 8) +// ~= 2 * asin(sqrt(DBL_EPSILON / 4) +// ~= sqrt(DBL_EPSILON) +// ~= 1.5e-8 +// +// which is 9.5 cm on the Earth's surface. // // This class is intended to be copied by value as desired. It uses // the default copy constructor and assignment operator. @@ -213,9 +289,9 @@ class S1ChordAngle { // Infinity() are both considered valid. bool is_valid() const; - // When S1ChordAngle is used as a key in one of the btree container types - // (util/btree), indicate that linear rather than binary search should be - // used. This is much faster when the comparison function is cheap. + // When S1ChordAngle is used as a key in one of the absl::btree container + // types, indicate that linear rather than binary search should be used. + // This is much faster when the comparison function is cheap. typedef std::true_type absl_btree_prefer_linear_node_search; private: diff --git a/src/s2/s1interval.cc b/src/s2/s1interval.cc index d47875d3..eaf1ebb8 100644 --- a/src/s2/s1interval.cc +++ b/src/s2/s1interval.cc @@ -21,8 +21,6 @@ #include #include -#include "s2/base/logging.h" - using std::fabs; using std::max; diff --git a/src/s2/s1interval.h b/src/s2/s1interval.h index b37471ab..b11b2baf 100644 --- a/src/s2/s1interval.h +++ b/src/s2/s1interval.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "s2/base/logging.h" #include "s2/_fp_contract_off.h" @@ -180,6 +181,9 @@ class S1Interval { // Return true if two intervals contains the same set of points. bool operator==(const S1Interval& y) const; + // Return true if two intervals do not contain the same set of points. + bool operator!=(const S1Interval& y) const; + // Return true if this interval can be transformed into the given interval by // moving each endpoint by at most "max_error" (and without the endpoints // crossing, which would invert the interval). Empty and full intervals are @@ -249,6 +253,10 @@ inline bool S1Interval::operator==(const S1Interval& y) const { return lo() == y.lo() && hi() == y.hi(); } +inline bool S1Interval::operator!=(const S1Interval& y) const { + return !operator==(y); +} + inline void S1Interval::set_lo(double p) { bounds_[0] = p; S2_DCHECK(is_valid()); diff --git a/src/s2/s2boolean_operation.cc b/src/s2/s2boolean_operation.cc index 4ef36c87..ef9a3084 100644 --- a/src/s2/s2boolean_operation.cc +++ b/src/s2/s2boolean_operation.cc @@ -1,4 +1,3 @@ -#include "cpp-compat.h" // Copyright 2017 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,32 +64,57 @@ #include "s2/s2boolean_operation.h" +#include +#include + #include +#include #include #include +#include #include +#include +#include "absl/cleanup/cleanup.h" #include "absl/container/btree_map.h" -#include "absl/memory/memory.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/inlined_vector.h" +#include "absl/strings/string_view.h" + +#include "s2/base/integral_types.h" +#include "s2/id_set_lexicon.h" +#include "s2/s1angle.h" #include "s2/s2builder.h" +#include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" #include "s2/s2builderutil_snap_functions.h" +#include "s2/s2cell_id.h" #include "s2/s2contains_point_query.h" #include "s2/s2crossing_edge_query.h" -#include "s2/s2edge_crosser.h" #include "s2/s2edge_crossings.h" +#include "s2/s2error.h" #include "s2/s2measures.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" #include "s2/s2predicates.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" #include "s2/s2shape_index_measures.h" +#include "s2/s2shapeutil_shape_edge.h" +#include "s2/s2shapeutil_shape_edge_id.h" #include "s2/s2shapeutil_visit_crossing_edge_pairs.h" +#include "s2/value_lexicon.h" // TODO(ericv): Remove this debugging output at some point. extern bool s2builder_verbose; namespace { // Anonymous namespace for helper classes. -using absl::make_unique; +using absl::flat_hash_map; +using absl::string_view; +using std::lower_bound; using std::make_pair; +using std::make_unique; using std::max; using std::min; using std::pair; @@ -147,8 +171,10 @@ class CrossingInputEdge { InputEdgeId input_id_ : 31; }; -// InputEdgeCrossings represents all pairs of intersecting input edges. -// It is sorted in lexicographic order. +// InputEdgeCrossings represents all pairs of intersecting input edges and +// also certain GraphEdgeClipper state modifications (kSetInside, etc). +// It is sorted lexicographically except for entries representing state +// modifications, which are sorted by the first InputEdgeId only. using InputEdgeCrossings = vector>; // Given two input edges A and B that intersect, suppose that A maps to a @@ -202,7 +228,7 @@ static vector GetInputEdgeChainOrder( // chain order (e.g. AB, BC, CD). vector> vmap; // Map from source vertex to edge id. vector indegree(g.num_vertices()); // Restricted to current input edge. - for (int end, begin = 0; begin < order.size(); begin = end) { + for (size_t end, begin = 0; begin < order.size(); begin = end) { // Gather the edges that came from a single input edge. InputEdgeId input_id = input_ids[order[begin]]; for (end = begin; end < order.size(); ++end) { @@ -213,7 +239,7 @@ static vector GetInputEdgeChainOrder( // Build a map from the source vertex of each edge to its edge id, // and also compute the indegree at each vertex considering only the edges // that came from the current input edge. - for (int i = begin; i < end; ++i) { + for (size_t i = begin; i < end; ++i) { EdgeId e = order[i]; vmap.push_back(make_pair(g.edge(e).first, e)); indegree[g.edge(e).second] += 1; @@ -222,12 +248,12 @@ static vector GetInputEdgeChainOrder( // Find the starting edge for building the edge chain. EdgeId next = g.num_edges(); - for (int i = begin; i < end; ++i) { + for (size_t i = begin; i < end; ++i) { EdgeId e = order[i]; if (indegree[g.edge(e).first] == 0) next = e; } // Build the edge chain. - for (int i = begin; ;) { + for (size_t i = begin;;) { order[i] = next; VertexId v = g.edge(next).second; indegree[v] = 0; // Clear as we go along. @@ -304,12 +330,14 @@ class GraphEdgeClipper { vector rank_; // The rank of each graph edge within order_. }; -GraphEdgeClipper::GraphEdgeClipper( - const Graph& g, const vector& input_dimensions, - const InputEdgeCrossings& input_crossings, - vector* new_edges, - vector* new_input_edge_ids) - : g_(g), in_(g), out_(g), +GraphEdgeClipper::GraphEdgeClipper(const Graph& g, + const vector& input_dimensions, + const InputEdgeCrossings& input_crossings, + vector* new_edges, + vector* new_input_edge_ids) + : g_(g), + in_(g), + out_(g), input_dimensions_(input_dimensions), input_crossings_(input_crossings), new_edges_(new_edges), @@ -317,9 +345,13 @@ GraphEdgeClipper::GraphEdgeClipper( input_ids_(g.input_edge_id_set_ids()), order_(GetInputEdgeChainOrder(g_, input_ids_)), rank_(order_.size()) { - for (int i = 0; i < order_.size(); ++i) { + for (size_t i = 0; i < order_.size(); ++i) { rank_[order_[i]] = i; } + // new_edges_ is obtained by filtering the graph edges and therefore the + // number of graph edges is an upper bound on its size. + new_edges_->reserve(g_.num_edges()); + new_input_edge_ids_->reserve(g_.num_edges()); } inline void GraphEdgeClipper::AddEdge(Graph::Edge edge, @@ -340,7 +372,7 @@ void GraphEdgeClipper::Run() { bool invert_b = false; bool reverse_a = false; auto next = input_crossings_.begin(); - for (int i = 0; i < order_.size(); ++i) { + for (size_t i = 0; i < order_.size(); ++i) { // For each input edge (the "A" input edge), gather all the input edges // that cross it (the "B" input edges). InputEdgeId a_input_id = input_ids_[order_[i]]; @@ -393,8 +425,8 @@ void GraphEdgeClipper::Run() { } --i; if (s2builder_verbose) { - cpp_compat_cout << "input edge " << a_input_id << " (inside=" << inside << "):"; - for (VertexId id : a_vertices) cpp_compat_cout << " " << id; + std::cout << "input edge " << a_input_id << " (inside=" << inside << "):"; + for (VertexId id : a_vertices) std::cout << " " << id; } // Now for each B edge chain, decide which vertex of the A chain it // crosses, and keep track of the number of signed crossings at each A @@ -416,18 +448,18 @@ void GraphEdgeClipper::Run() { a_num_crossings.resize(a_vertices.size()); a_isolated.clear(); a_isolated.resize(a_vertices.size()); - for (int bi = 0; bi < b_input_edges.size(); ++bi) { + for (size_t bi = 0; bi < b_input_edges.size(); ++bi) { bool left_to_right = b_input_edges[bi].left_to_right(); int a_index = GetCrossedVertexIndex(a_vertices, b_edges[bi], left_to_right); if (a_index >= 0) { if (s2builder_verbose) { - cpp_compat_cout << std::endl << " " << "b input edge " + std::cout << std::endl << " " << "b input edge " << b_input_edges[bi].input_id() << " (l2r=" << left_to_right << ", crossing=" << a_vertices[a_index] << ")"; for (const auto& x : b_edges[bi]) { const Graph::Edge& e = g_.edge(x.id); - cpp_compat_cout << " (" << e.first << ", " << e.second << ")"; + std::cout << " (" << e.first << ", " << e.second << ")"; } } // Keep track of the number of signed crossings (see above). @@ -440,10 +472,10 @@ void GraphEdgeClipper::Run() { a_isolated[a_index] = true; } else { // TODO(b/112043775): fix this condition. - S2_LOG(DFATAL) << "Failed to get crossed vertex index."; + S2_LOG(ERROR) << "Failed to get crossed vertex index."; } } - if (s2builder_verbose) cpp_compat_cout << std::endl; + if (s2builder_verbose) std::cout << std::endl; // Finally, we iterate through the A edge chain, keeping track of the // number of signed crossings as we go along. The "multiplicity" is @@ -452,7 +484,7 @@ void GraphEdgeClipper::Run() { // up the edge crossings in the correct order. (The multiplicity is // almost always either 0 or 1 except in very rare cases.) int multiplicity = inside + a_num_crossings[0]; - for (int ai = 1; ai < a_vertices.size(); ++ai) { + for (size_t ai = 1; ai < a_vertices.size(); ++ai) { if (multiplicity != 0) { a_isolated[ai - 1] = a_isolated[ai] = false; } @@ -474,7 +506,7 @@ void GraphEdgeClipper::Run() { // Output any isolated polyline vertices. // TODO(ericv): Only do this if an output layer wants degenerate edges. if (input_dimensions_[a_input_id] != 0) { - for (int ai = 0; ai < a_vertices.size(); ++ai) { + for (size_t ai = 0; ai < a_vertices.size(); ++ai) { if (a_isolated[ai]) { AddEdge(Graph::Edge(a_vertices[ai], a_vertices[ai]), a_input_id); } @@ -534,8 +566,12 @@ int GraphEdgeClipper::GetVertexRank(const CrossingGraphEdge& e) const { int GraphEdgeClipper::GetCrossedVertexIndex( const vector& a, const CrossingGraphEdgeVector& b, bool left_to_right) const { - S2_DCHECK(!a.empty()); - S2_DCHECK(!b.empty()); + if (a.empty() || b.empty()) { + S2_LOG(ERROR) << "GraphEdgeClipper::GetCrossedVertexIndex called with " + << a.size() << " vertex ids and " << b.size() + << " crossing graph edges."; + return -1; + } // The reason this calculation is tricky is that after snapping, the A and B // chains may meet and separate several times. For example, if B crosses A @@ -631,20 +667,23 @@ int GraphEdgeClipper::GetCrossedVertexIndex( // in which case we could check which side of the B chain the A edge is on // and use this to limit the possible crossing locations. if (b_first >= 0 && b_last >= 0) { - // The B subchain connects the first and last vertices of A. Test whether - // the chain includes any interior vertices of A. We do this indirectly - // by testing whether any edge of B has restricted the range of allowable - // crossing vertices (since any interior edge of the B subchain incident - // to any interior edge of A is guaranteed to do so). - int min_rank = order_.size(), max_rank = -1; + // Swap the edges if necessary so that they are in B chain order. + if (b_reversed) swap(b_first, b_last); + + // The B subchain connects the first and last vertices of A. We test + // whether the chain includes any interior vertices of A by iterating + // through the incident B edges again, looking for ones that belong to + // the B subchain and are not incident to the first or last vertex of A. + bool has_interior_vertex = false; for (const auto& e : b) { - min_rank = min(min_rank, GetVertexRank(e)); - max_rank = max(max_rank, GetVertexRank(e)); + if (e.a_index > 0 && e.a_index < n - 1 && + rank_[e.id] >= rank_[b_first] && rank_[e.id] <= rank_[b_last]) { + has_interior_vertex = true; + break; + } } - if (lo <= min_rank && hi >= max_rank) { + if (!has_interior_vertex) { // The B subchain is not incident to any interior vertex of A. - // Swap the edges if necessary so that they are in B chain order. - if (b_reversed) swap(b_first, b_last); bool on_left = EdgeChainOnLeft(a, b_first, b_last); if (left_to_right == on_left) { lo = max(lo, rank_[b_last] + 1); @@ -688,7 +727,7 @@ bool GraphEdgeClipper::EdgeChainOnLeft( } // Now B is to the left of A if and only if the loop is counterclockwise. double sum = 0; - for (int i = 2; i < loop.size(); ++i) { + for (size_t i = 2; i < loop.size(); ++i) { sum += S2::TurnAngle(g_.vertex(loop[i - 2]), g_.vertex(loop[i - 1]), g_.vertex(loop[i])); } @@ -704,11 +743,12 @@ class EdgeClippingLayer : public S2Builder::Layer { public: EdgeClippingLayer(const vector>* layers, const vector* input_dimensions, - const InputEdgeCrossings* input_crossings) + const InputEdgeCrossings* input_crossings, + S2MemoryTracker::Client* tracker) : layers_(*layers), input_dimensions_(*input_dimensions), - input_crossings_(*input_crossings) { - } + input_crossings_(*input_crossings), + tracker_(tracker) {} // Layer interface: GraphOptions graph_options() const override; @@ -718,6 +758,7 @@ class EdgeClippingLayer : public S2Builder::Layer { const vector>& layers_; const vector& input_dimensions_; const InputEdgeCrossings& input_crossings_; + S2MemoryTracker::Client* tracker_; }; GraphOptions EdgeClippingLayer::graph_options() const { @@ -728,30 +769,32 @@ GraphOptions EdgeClippingLayer::graph_options() const { DuplicateEdges::KEEP, SiblingPairs::KEEP); } -// Helper function (in anonymous namespace) to create an S2Builder::Graph from -// a vector of edges. -Graph MakeGraph( - const Graph& g, GraphOptions* options, vector* new_edges, - vector* new_input_edge_ids, - IdSetLexicon* new_input_edge_id_set_lexicon, S2Error* error) { - if (options->edge_type() == EdgeType::UNDIRECTED) { - // Create a reversed edge for every edge. - int n = new_edges->size(); - new_edges->reserve(2 * n); - new_input_edge_ids->reserve(2 * n); - for (int i = 0; i < n; ++i) { - new_edges->push_back(Graph::reverse((*new_edges)[i])); - new_input_edge_ids->push_back(IdSetLexicon::EmptySetId()); - } +void EdgeClippingLayer::Build(const Graph& g, S2Error* error) { + // Data per graph edge: + // vector order_; + // vector rank_; + // vector new_edges; + // vector new_input_edge_ids; + // Data per graph vertex: + // Graph::VertexInMap in_; + // Graph::VertexOutMap out_; + // + // The first and last two vectors above are freed upon GraphEdgeClipper + // destruction. There is also a temporary vector "indegree" in + // GetInputEdgeChainOrder() but this does not affect peak memory usage. + int64 tmp_bytes = g.num_edges() * (sizeof(EdgeId) + sizeof(int)) + + g.num_vertices() * (2 * sizeof(EdgeId)); + int64 final_bytes = + g.num_edges() * (sizeof(Graph::Edge) + sizeof(InputEdgeIdSetId)); + + // The order of the calls below is important. Note that all memory tracked + // through this client is automatically untallied upon object destruction. + if (!tracker_->Tally(final_bytes) || !tracker_->TallyTemp(tmp_bytes)) { + // We don't need to copy memory tracking errors to "error" because this + // is already done for us in S2BooleanOperation::Impl::Build(). + return; } - Graph::ProcessEdges(options, new_edges, new_input_edge_ids, - new_input_edge_id_set_lexicon, error); - return Graph(*options, &g.vertices(), new_edges, new_input_edge_ids, - new_input_edge_id_set_lexicon, &g.label_set_ids(), - &g.label_set_lexicon(), g.is_full_polygon_predicate()); -} -void EdgeClippingLayer::Build(const Graph& g, S2Error* error) { // The bulk of the work is handled by GraphEdgeClipper. vector new_edges; vector new_input_edge_ids; @@ -759,44 +802,55 @@ void EdgeClippingLayer::Build(const Graph& g, S2Error* error) { GraphEdgeClipper(g, input_dimensions_, input_crossings_, &new_edges, &new_input_edge_ids).Run(); if (s2builder_verbose) { - cpp_compat_cout << "Edges after clipping: " << std::endl; - for (int i = 0; i < new_edges.size(); ++i) { - cpp_compat_cout << " " << new_input_edge_ids[i] << " (" << new_edges[i].first + std::cout << "Edges after clipping: " << std::endl; + for (size_t i = 0; i < new_edges.size(); ++i) { + std::cout << " " << new_input_edge_ids[i] << " (" << new_edges[i].first << ", " << new_edges[i].second << ")" << std::endl; } } - // Construct one or more graphs from the clipped edges and pass them to the - // given output layer(s). - IdSetLexicon new_input_edge_id_set_lexicon; + // Construct one or more subgraphs from the clipped edges and pass them to + // the given output layer(s). We start with a copy of the input graph's + // IdSetLexicon because this is necessary in general, even though in this + // case it is guaranteed to be empty because no edges have been merged. + IdSetLexicon new_input_edge_id_set_lexicon = g.input_edge_id_set_lexicon(); if (layers_.size() == 1) { - GraphOptions options = layers_[0]->graph_options(); - Graph new_graph = MakeGraph(g, &options, &new_edges, &new_input_edge_ids, - &new_input_edge_id_set_lexicon, error); - layers_[0]->Build(new_graph, error); + Graph new_graph = g.MakeSubgraph( + layers_[0]->graph_options(), &new_edges, &new_input_edge_ids, + &new_input_edge_id_set_lexicon, g.is_full_polygon_predicate(), + error, tracker_); + if (tracker_->ok()) layers_[0]->Build(new_graph, error); + tracker_->Untally(new_edges); + tracker_->Untally(new_input_edge_ids); } else { // The Graph objects must be valid until the last Build() call completes, // so we store all of the graph data in arrays with 3 elements. S2_DCHECK_EQ(3, layers_.size()); vector layer_edges[3]; vector layer_input_edge_ids[3]; - S2Builder::GraphOptions layer_options[3]; - vector layer_graphs; // No default constructor. - layer_graphs.reserve(3); // Separate the edges according to their dimension. - for (int i = 0; i < new_edges.size(); ++i) { + for (size_t i = 0; i < new_edges.size(); ++i) { int d = input_dimensions_[new_input_edge_ids[i]]; + if (!tracker_->AddSpace(&layer_edges[d], 1)) return; + if (!tracker_->AddSpace(&layer_input_edge_ids[d], 1)) return; layer_edges[d].push_back(new_edges[i]); layer_input_edge_ids[d].push_back(new_input_edge_ids[i]); } // Clear variables to save space. - vector().swap(new_edges); - vector().swap(new_input_edge_ids); + if (!tracker_->Clear(&new_edges)) return; + if (!tracker_->Clear(&new_input_edge_ids)) return; + + vector layer_graphs; // No default constructor. + layer_graphs.reserve(3); + for (int d = 0; d < 3; ++d) { + layer_graphs.push_back(g.MakeSubgraph( + layers_[d]->graph_options(), &layer_edges[d], + &layer_input_edge_ids[d], &new_input_edge_id_set_lexicon, + g.is_full_polygon_predicate(), error, tracker_)); + if (tracker_->ok()) layers_[d]->Build(layer_graphs[d], error); + } for (int d = 0; d < 3; ++d) { - layer_options[d] = layers_[d]->graph_options(); - layer_graphs.push_back(MakeGraph( - g, &layer_options[d], &layer_edges[d], &layer_input_edge_ids[d], - &new_input_edge_id_set_lexicon, error)); - layers_[d]->Build(layer_graphs[d], error); + tracker_->Untally(layer_edges[d]); + tracker_->Untally(layer_input_edge_ids[d]); } } } @@ -806,13 +860,16 @@ void EdgeClippingLayer::Build(const Graph& g, S2Error* error) { class S2BooleanOperation::Impl { public: explicit Impl(S2BooleanOperation* op) - : op_(op), index_crossings_first_region_id_(-1) { + : op_(op), index_crossings_first_region_id_(-1), + tracker_(op->options_.memory_tracker()) { } + bool Build(S2Error* error); private: class CrossingIterator; class CrossingProcessor; + using ShapeEdge = s2shapeutil::ShapeEdge; using ShapeEdgeId = s2shapeutil::ShapeEdgeId; @@ -828,7 +885,7 @@ class S2BooleanOperation::Impl { // True if "a_edge" crosses "b_edge" from left to right. Undefined if // is_interior_crossing is false. - uint32 left_to_right: 1; + uint32 left_to_right : 1; // Equal to S2::VertexCrossing(a_edge, b_edge). Undefined if "a_edge" and // "b_edge" do not share exactly one vertex or either edge is degenerate. @@ -843,6 +900,7 @@ class S2BooleanOperation::Impl { friend bool operator==(const IndexCrossing& x, const IndexCrossing& y) { return x.a == y.a && x.b == y.b; } + friend bool operator<(const IndexCrossing& x, const IndexCrossing& y) { // The compiler (2017) doesn't optimize the following as well: // return x.a < y.a || (x.a == y.a && x.b < y.b); @@ -857,6 +915,34 @@ class S2BooleanOperation::Impl { }; using IndexCrossings = vector; + class MemoryTracker : public S2MemoryTracker::Client { + public: + using S2MemoryTracker::Client::Client; + + // Used to track memory used by CrossingProcessor::source_id_map_. (The + // type is a template parameter so that SourceIdMap can be private.) + template + bool TallySourceIdMap(int num_entries) { + int64 delta_bytes = num_entries * GetBtreeMinBytesPerEntry(); + source_id_map_bytes_ += delta_bytes; + return Tally(delta_bytes); + } + + // Used to clear CrossingProcessor::source_id_map_ and update the tracked + // memory usage accordingly. + template + bool ClearSourceIdMap(T* source_id_map) { + source_id_map->clear(); + Tally(-source_id_map_bytes_); + source_id_map_bytes_ = 0; + return ok(); + } + + private: + // The amount of memory used by CrossingProcessor::source_id_map_. + int64 source_id_map_bytes_ = 0; + }; + bool is_boolean_output() const { return op_->result_empty_ != nullptr; } // All of the methods below support "early exit" in the case of boolean @@ -873,8 +959,8 @@ class S2BooleanOperation::Impl { S2ContainsPointQuery* query, CrossingProcessor* cp); static bool HasInterior(const S2ShapeIndex& index); - static bool AddIndexCrossing(const ShapeEdge& a, const ShapeEdge& b, - bool is_interior, IndexCrossings* crossings); + bool AddIndexCrossing(const ShapeEdge& a, const ShapeEdge& b, + bool is_interior, IndexCrossings* crossings); bool GetIndexCrossings(int region_id); bool AddBoundaryPair(bool invert_a, bool invert_b, bool invert_result, CrossingProcessor* cp); @@ -889,13 +975,18 @@ class S2BooleanOperation::Impl { const S2ShapeIndex& b) const; bool IsFullPolygonSymmetricDifference(const S2ShapeIndex& a, const S2ShapeIndex& b) const; + void DoBuild(S2Error* error); // A bit mask representing all six faces of the S2 cube. static constexpr uint8 kAllFacesMask = 0x3f; S2BooleanOperation* op_; - // The S2Builder used to construct the output. + // The S2Builder options used to construct the output. + S2Builder::Options builder_options_; + + // The S2Builder used to construct the output. Note that the S2Builder + // object is created only when is_boolean_output() is false. unique_ptr builder_; // A vector specifying the dimension of each edge added to S2Builder. @@ -922,6 +1013,9 @@ class S2BooleanOperation::Impl { // Temporary storage used in GetChainStarts(), declared here to avoid // repeatedly allocating memory. IndexCrossings tmp_crossings_; + + // An object to track the memory usage of this class. + MemoryTracker tracker_; }; const s2shapeutil::ShapeEdgeId S2BooleanOperation::Impl::kSentinel( @@ -1031,15 +1125,17 @@ class S2BooleanOperation::Impl::CrossingProcessor { // be nullptr. CrossingProcessor(const PolygonModel& polygon_model, const PolylineModel& polyline_model, - bool polyline_loops_have_boundaries, - S2Builder* builder, + bool polyline_loops_have_boundaries, S2Builder* builder, vector* input_dimensions, - InputEdgeCrossings *input_crossings) - : polygon_model_(polygon_model), polyline_model_(polyline_model), + InputEdgeCrossings* input_crossings, MemoryTracker* tracker) + : polygon_model_(polygon_model), + polyline_model_(polyline_model), polyline_loops_have_boundaries_(polyline_loops_have_boundaries), - builder_(builder), input_dimensions_(input_dimensions), - input_crossings_(input_crossings), prev_inside_(false) { - } + builder_(builder), + input_dimensions_(input_dimensions), + input_crossings_(input_crossings), + tracker_(tracker), + prev_inside_(false) {} // Starts processing edges from the given region. "invert_a", "invert_b", // and "invert_result" indicate whether region A, region B, and/or the @@ -1085,8 +1181,8 @@ class S2BooleanOperation::Impl::CrossingProcessor { // edge; it crosses the edge from left to right iff the second parameter // is "true". using SourceEdgeCrossing = pair; - struct PointCrossingResult; struct EdgeCrossingResult; + struct PointCrossingResult; InputEdgeId input_edge_id() const { return input_dimensions_->size(); } @@ -1112,10 +1208,22 @@ class S2BooleanOperation::Impl::CrossingProcessor { return (polyline_model_ != PolylineModel::OPEN || edge_id > chain_start); } + bool is_degenerate(ShapeEdgeId a_id) const { + return is_degenerate_hole_.contains(a_id); + } + void AddCrossing(const SourceEdgeCrossing& crossing) { + if (!tracker_->AddSpace(&source_edge_crossings_, 1)) return; source_edge_crossings_.push_back(make_pair(input_edge_id(), crossing)); } + void AddInteriorCrossing(const SourceEdgeCrossing& crossing) { + // Crossing edges are queued until the S2Builder edge that they are + // supposed to be associated with is created (see AddEdge() and + // pending_source_edge_crossings_ for details). + pending_source_edge_crossings_.push_back(crossing); + } + void SetClippingState(InputEdgeId parameter, bool state) { AddCrossing(SourceEdgeCrossing(SourceId(parameter), state)); } @@ -1126,18 +1234,31 @@ class S2BooleanOperation::Impl::CrossingProcessor { int dimension, int interior_crossings) { if (builder_ == nullptr) return false; // Boolean output. if (interior_crossings > 0) { + // Add the edges that cross this edge to the output so that + // GraphEdgeClipper can find them. + if (!tracker_->AddSpace(&source_edge_crossings_, + pending_source_edge_crossings_.size())) { + return false; + } + for (const auto& crossing : pending_source_edge_crossings_) { + source_edge_crossings_.push_back(make_pair(input_edge_id(), crossing)); + } // Build a map that translates temporary edge ids (SourceId) to // the representation used by EdgeClippingLayer (InputEdgeId). + if (!tracker_->TallySourceIdMap(1)) { + return false; + } SourceId src_id(a_region_id_, a_id.shape_id, a_id.edge_id); source_id_map_[src_id] = input_edge_id(); } // Set the GraphEdgeClipper's "inside" state to match ours. if (inside_ != prev_inside_) SetClippingState(kSetInside, inside_); + if (!tracker_->AddSpace(input_dimensions_, 1)) return false; input_dimensions_->push_back(dimension); builder_->AddEdge(a.v0, a.v1); inside_ ^= (interior_crossings & 1); prev_inside_ = inside_; - return true; + return tracker_->ok(); } // Supports "early exit" in the case of boolean results by returning false @@ -1145,10 +1266,11 @@ class S2BooleanOperation::Impl::CrossingProcessor { bool AddPointEdge(const S2Point& p, int dimension) { if (builder_ == nullptr) return false; // Boolean output. if (!prev_inside_) SetClippingState(kSetInside, true); + if (!tracker_->AddSpace(input_dimensions_, 1)) return false; input_dimensions_->push_back(dimension); builder_->AddEdge(p, p); prev_inside_ = true; - return true; + return tracker_->ok(); } bool ProcessEdge0(ShapeEdgeId a_id, const S2Shape::Edge& a, @@ -1166,9 +1288,11 @@ class S2BooleanOperation::Impl::CrossingProcessor { bool IsPolylineVertexInside(bool matches_polyline, bool matches_polygon) const; - bool IsPolylineEdgeInside(const EdgeCrossingResult& r) const; + bool IsPolylineEdgeInside(const EdgeCrossingResult& r, + bool is_degenerate) const; bool PolylineEdgeContainsVertex(const S2Point& v, - const CrossingIterator& it) const; + const CrossingIterator& it, + int dimension) const; // Constructor parameters: @@ -1182,9 +1306,10 @@ class S2BooleanOperation::Impl::CrossingProcessor { // edges belong to the output. The auxiliary information consists of the // dimension of each input edge, and set of input edges from the other // region that cross each input input edge. - S2Builder* builder_; + S2Builder* builder_; // (nullptr if boolean output was requested) vector* input_dimensions_; InputEdgeCrossings* input_crossings_; + MemoryTracker* tracker_; // Fields set by StartBoundary: @@ -1220,16 +1345,34 @@ class S2BooleanOperation::Impl::CrossingProcessor { // // All crossings are represented twice, once to indicate that an edge from // polygon 0 is crossed by an edge from polygon 1, and once to indicate that - // an edge from polygon 1 is crossed by an edge from polygon 0. + // an edge from polygon 1 is crossed by an edge from polygon 0. The entries + // are sorted lexicographically by their eventual InputEdgeIds except for + // GraphEdgeClipper state modifications, which are sorted by the first + // InputEdgeId only. using SourceEdgeCrossings = vector>; SourceEdgeCrossings source_edge_crossings_; - // A map that translates from SourceId (the (region_id, shape_id, - // edge_id) triple that identifies an S2ShapeIndex edge) to InputEdgeId (the + // A set of edges that cross the current edge being processed by + // ProcessEdge() but that have not yet been associated with a particular + // S2Builder edge. This is necessary because ProcessEdge can create up to + // three S2Builder edges per input edge: one to represent the edge interior, + // and up to two more to represent an isolated start and/or end vertex. The + // crossing edges must be associated with the S2Builder edge that represents + // the edge interior, and they are stored here until that edge is created. + vector pending_source_edge_crossings_; + + // A map that translates from SourceId (the (region_id, shape_id, edge_id) + // triple that identifies an S2ShapeIndex edge) to InputEdgeId (the // sequentially increasing numbers assigned to input edges by S2Builder). using SourceIdMap = absl::btree_map; SourceIdMap source_id_map_; + // For each edge in region B that defines a degenerate loop (either a point + // loop or a sibling pair), indicates whether that loop represents a shell + // or a hole. This information is used during the second pass of + // AddBoundaryPair() to determine the output for degenerate edges. + flat_hash_map is_degenerate_hole_; + // Indicates whether the point being processed along the current edge chain // is in the polygonal interior of the opposite region, using semi-open // boundaries. If "invert_b_" is true then this field is inverted. @@ -1364,7 +1507,7 @@ S2BooleanOperation::Impl::CrossingProcessor::ProcessPointCrossings( if (it->b_dimension() == 0) { r.matches_point = true; } else if (it->b_dimension() == 1) { - if (PolylineEdgeContainsVertex(a0, *it)) { + if (PolylineEdgeContainsVertex(a0, *it, 0)) { r.matches_polyline = true; } } else { @@ -1374,37 +1517,44 @@ S2BooleanOperation::Impl::CrossingProcessor::ProcessPointCrossings( return r; } -// EdgeCrossingResult describes the relationship between an edge from region A -// ("a_edge") and a set of crossing edges from region B. For example, -// "matches_polygon" indicates whether "a_edge" matches a polygon edge from +// EdgeCrossingResult describes the relationship between an edge (a0, a1) from +// region A and a set of crossing edges from region B. For example, +// "matches_polygon" indicates whether (a0, a1) matches a polygon edge from // region B. struct S2BooleanOperation::Impl::CrossingProcessor::EdgeCrossingResult { - EdgeCrossingResult() - : matches_polyline(false), matches_polygon(false), matches_sibling(false), - a0_matches_polyline(false), a1_matches_polyline(false), - a0_matches_polygon(false), a1_matches_polygon(false), - a0_crossings(0), a1_crossings(0), interior_crossings(0) { - } - // These fields indicate that "a_edge" exactly matches an edge of B. - bool matches_polyline; // Matches polyline edge (either direction). - bool matches_polygon; // Matches polygon edge (same direction). - bool matches_sibling; // Matches polygon edge (reverse direction). - - // These fields indicate that a vertex of "a_edge" matches a polyline vertex - // of B *and* the polyline contains that vertex. - bool a0_matches_polyline; // Start vertex matches contained polyline vertex. - bool a1_matches_polyline; // End vertex matches contained polyline vertex. - - // These fields indicate that a vertex of "a_edge" matches a polygon vertex + // These fields indicate that (a0, a1) exactly matches an edge of B. + bool matches_polyline = false; // Matches polyline edge (either direction). + + // These fields indicate that a B polyline contains the degenerate polyline + // (a0, a0) or (a1, a1). (This is identical to whether the B polyline + // contains the point a0 or a1 except when the B polyline is degenerate, + // since a degenerate polyline VV contains itself in all boundary models but + // contains the point V only in the CLOSED polyline model.) + bool a0_matches_polyline = false; // B polyline contains (a0, a0) + bool a1_matches_polyline = false; // B polyline contains (a1, a1) + + // These fields indicate that a vertex of (a0, a1) matches a polygon vertex // of B. (Unlike with polylines, the polygon may not contain that vertex.) - bool a0_matches_polygon; // Start vertex matches polygon vertex. - bool a1_matches_polygon; // End vertex matches polygon vertex. - - // These fields count the number of edge crossings at the start vertex, end - // vertex, and interior of "a_edge". - int a0_crossings; // Count of polygon crossings at start vertex. - int a1_crossings; // Count of polygon crossings at end vertex. - int interior_crossings; // Count of polygon crossings in edge interior. + bool a0_matches_polygon = false; // a0 matches polygon vertex. + bool a1_matches_polygon = false; // a1 matches polygon vertex. + + // When a0 != a1, the first two fields identify any B polygon edge that + // exactly matches (a0, a1) or the sibling edge (a1, a0). The third field + // identifies any B polygon edge that exactly matches (a0, a0). + ShapeEdgeId polygon_match_id; // B polygon edge that matches (a0, a1). + ShapeEdgeId sibling_match_id; // B polygon edge that matches (a1, a0). + ShapeEdgeId a0_loop_match_id; // B polygon edge that matches (a0, a0). + + // Convenience functions to test whether a matching edge was found. + bool matches_polygon() const { return polygon_match_id.edge_id >= 0; } + bool matches_sibling() const { return sibling_match_id.edge_id >= 0; } + bool loop_matches_a0() const { return a0_loop_match_id.edge_id >= 0; } + + // These fields count the number of edge crossings at a0, a1, and the + // interior of (a0, a1). + int a0_crossings = 0; // Count of polygon crossings at a0. + int a1_crossings = 0; // Count of polygon crossings at a1. + int interior_crossings = 0; // Count of polygon crossings in edge interior. }; // Processes an edge of dimension 1 (i.e., a polyline edge) from region A. @@ -1426,8 +1576,9 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge1( // Test whether the entire polyline edge should be emitted (or not emitted) // because it matches a polyline or polygon edge. + bool is_degenerate = (a.v0 == a.v1); inside_ ^= (r.a0_crossings & 1); - if (inside_ != IsPolylineEdgeInside(r)) { + if (inside_ != IsPolylineEdgeInside(r, is_degenerate)) { inside_ ^= true; // Invert the inside_ state. ++r.a1_crossings; // Restore the correct (semi-open) state later. } @@ -1440,7 +1591,7 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge1( // This is the first vertex of a polyline loop, so we can't decide if it // is isolated until we process the last polyline edge. chain_v0_emitted_ = inside_; - } else if (is_v0_isolated(a_id) && + } else if (is_v0_isolated(a_id) && !is_degenerate && polyline_contains_v0(a_id.edge_id, chain_start_) && a0_inside) { if (!AddPointEdge(a.v0, 1)) return false; } @@ -1465,7 +1616,8 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge1( // Special case to test whether the last vertex of a polyline should be // emitted as an isolated vertex. - if (it->crossings_complete() && is_chain_last_vertex_isolated(a_id) && + if (it->crossings_complete() && !is_degenerate && + is_chain_last_vertex_isolated(a_id) && (polyline_model_ == PolylineModel::CLOSED || (!polyline_loops_have_boundaries_ && a.v1 == a_shape_->chain_edge(chain_id_, chain_start_).v0)) && @@ -1481,8 +1633,8 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge1( // vertex matches a polyline/polygon vertex of the opposite region. bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineVertexInside( bool matches_polyline, bool matches_polygon) const { - // "contained" indicates whether the current point is inside the polygonal - // interior of the opposite region using semi-open boundaries. + // Initially "contained" indicates whether the current point is inside the + // polygonal interior of region B using semi-open boundaries. bool contained = inside_ ^ invert_b_; // For UNION the output includes duplicate polylines. The test below @@ -1500,19 +1652,37 @@ bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineVertexInside( // Returns true if the current polyline edge is contained by the opposite // region (after inversion if "invert_b_" is true). inline bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineEdgeInside( - const EdgeCrossingResult& r) const { - // "contained" indicates whether the current point is inside the polygonal - // interior of the opposite region using semi-open boundaries. + const EdgeCrossingResult& r, bool is_degenerate) const { + // Initially "contained" indicates whether the current point (just past a0) + // is inside the polygonal interior of region B using semi-open boundaries. bool contained = inside_ ^ invert_b_; + + // Note that if r.matches_polyline and is_union_ is true, then "contained" + // will be false (unless there is also a matching polygon edge) since + // polyline edges are not allowed in the interior of B. In this case we + // leave "contained" as false since it causes both matching edges to be + // emitted. if (r.matches_polyline && !is_union_) { contained = true; - } else if (r.matches_polygon) { + } else if (is_degenerate) { + // First allow the polygon boundary model to override the semi-open rules. + // Note that a polygon vertex (dimension 2) is considered to completely + // contain degenerate OPEN and SEMI_OPEN polylines (dimension 1) even + // though the latter do not contain any points. This is because dimension + // 2 points are considered to be a strict superset of dimension 1 points. + if (polygon_model_ != PolygonModel::SEMI_OPEN && r.a0_matches_polygon) { + contained = (polygon_model_ == PolygonModel::CLOSED); + } + // Note that r.a0_matches_polyline is true if and only if some B polyline + // contains the degenerate polyline (a0, a0). + if (r.a0_matches_polyline && !is_union_) contained = true; + } else if (r.matches_polygon()) { // In the SEMI_OPEN model, polygon sibling pairs cancel each other and // have no effect on point or edge containment. - if (!(r.matches_sibling && polygon_model_ == PolygonModel::SEMI_OPEN)) { + if (!(polygon_model_ == PolygonModel::SEMI_OPEN && r.matches_sibling())) { contained = (polygon_model_ != PolygonModel::OPEN); } - } else if (r.matches_sibling) { + } else if (r.matches_sibling()) { contained = (polygon_model_ == PolygonModel::CLOSED); } // Finally, invert the result if the opposite region should be inverted. @@ -1525,48 +1695,184 @@ inline bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineEdgeInside( // as soon as the result is known to be non-empty. bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge2( ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) { - // In order to keep only one copy of any shared polygon edges, we only - // output shared edges when processing the second region. + // Whenever the two regions contain the same edge, or opposite edges of a + // sibling pair, or one region contains a point loop while the other + // contains a matching vertex, then in general the result depends on whether + // one or both sides represent a degenerate shell or hole. + // + // In each pass it is easy to determine whether edges in region B represent + // degenerate geometry, and if so whether they represent a shell or hole, + // since this can be determined from the inside_ state and the + // matches_polygon() / matches_sibling() methods of EdgeCrossingResult. + // However this information is not readily available for region A. + // + // We handle this by saving the shell/hole status of each degenerate loop in + // region B during the first pass, and deferring the processing of any edges + // that meet the criteria above until the second pass. (Note that regions + // A,B correspond to regions 0,1 respectively in the first pass whereas they + // refer to regions 1,0 respectively in the second pass.) + // + // The first pass ignores: + // - degenerate edges of A that are incident to any edge of B + // - non-degenerate edges of A that match or are siblings to an edge of B + // + // The first pass also records the shell/hole status of: + // - degenerate edges of B that are incident to any edge of A + // - sibling pairs of B where either edge matches an edge of A + // + // The second pass processes and perhaps outputs: + // - degenerate edges of B that are incident to any edge of A + // - non-degenerate edges of B that match or are siblings to an edge of A + // + // The following flag indicates that we are in the second pass described + // above, i.e. that we are emitting any necessary edges that were ignored by + // the first pass. bool emit_shared = (a_region_id_ == 1); // Degeneracies such as isolated vertices and sibling pairs can only be // created by intersecting CLOSED polygons or unioning OPEN polygons. - bool emit_degenerate = + bool create_degen = (polygon_model_ == PolygonModel::CLOSED && !invert_a_ && !invert_b_) || (polygon_model_ == PolygonModel::OPEN && invert_a_ && invert_b_); + // In addition, existing degeneracies are kept when an open boundary is + // subtracted. Note that "keep_degen_b" is only defined for completeness. + // It is needed to ensure that the "reverse subtraction operator" (B - A) + // preserves degeneracies correctly, however in practice this operator is + // only used internally to implement symmetric difference, and in that + // situation the preserved degeneracy is always removed from the final + // result because it overlaps other geometry. + bool keep_degen_a = (polygon_model_ == PolygonModel::OPEN && invert_b_); + bool keep_degen_b = (polygon_model_ == PolygonModel::OPEN && invert_a_); + EdgeCrossingResult r = ProcessEdgeCrossings(a_id, a, it); S2_DCHECK(!r.matches_polyline); - inside_ ^= (r.a0_crossings & 1); // If only one region is inverted, matching/sibling relations are reversed. - // TODO(ericv): Update the following code to handle degenerate loops. - S2_DCHECK(!r.matches_polygon || !r.matches_sibling); - if (invert_a_ != invert_b_) swap(r.matches_polygon, r.matches_sibling); - - // Test whether the entire polygon edge should be emitted (or not emitted) - // because it matches a polygon edge or its sibling. - bool new_inside = inside_; + if (invert_a_ != invert_b_) swap(r.polygon_match_id, r.sibling_match_id); + + bool is_point = (a.v0 == a.v1); + if (!emit_shared) { + // Remember the shell/hole status of degenerate B edges that are incident + // to any edge of A. (We don't need to do this for vertex a1 since it is + // the same as vertex a0 of the following A loop edge.) + if (r.loop_matches_a0()) { + is_degenerate_hole_[r.a0_loop_match_id] = inside_; + if (is_point) return true; + } - // Shared edge are emitted only while processing the second region. - if (r.matches_polygon) new_inside = emit_shared; + // Point loops are handled identically to points in the semi-open model, + // and are easier to process in the first pass (since otherwise in the + // r.a0_matches_polygon case we would need to remember the containment + // status of the matching vertex). Otherwise we defer processing such + // loops to the second pass so that we can distinguish whether the + // degenerate edge represents a hole or shell. + if (polygon_model_ != PolygonModel::SEMI_OPEN) { + if (is_point && r.a0_matches_polygon) return true; + } + } + inside_ ^= (r.a0_crossings & 1); + if (!emit_shared) { + // Defer processing A edges that match or are siblings to an edge of B. + if (r.matches_polygon() || r.matches_sibling()) { + // For sibling pairs, also remember their shell/hole status. + if (r.matches_polygon() && r.matches_sibling()) { + is_degenerate_hole_[r.polygon_match_id] = inside_; + is_degenerate_hole_[r.sibling_match_id] = inside_; + } + S2_DCHECK_EQ(r.interior_crossings, 0); + inside_ ^= (r.a1_crossings & 1); + return true; + } + } - // Sibling pairs are emitted only when degeneracies are desired. - if (r.matches_sibling) new_inside = emit_degenerate; - if (inside_ != new_inside) { - inside_ ^= true; // Invert the inside_ state. - ++r.a1_crossings; // Restore the correct (semi-open) state later. + // Remember whether the B geometry represents a sibling pair hole. + bool is_b_hole = r.matches_polygon() && r.matches_sibling() && inside_; + + // At this point, "inside_" indicates whether the initial part of the A edge + // is contained by the B geometry using semi-open rules. The following code + // implements the various other polygon boundary rules by changing the value + // of "inside_" when necessary to indicate whether the current A edge should + // be emitted to the output or not. "semi_open_inside" remembers the true + // value of "inside_" so that it can be restored later. + bool semi_open_inside = inside_; + if (is_point) { + if (r.loop_matches_a0()) { + // Both sides are point loops. The edge is kept only: + // - for closed intersection, open union, and open difference; + // - if A and B are both holes or both shells. + inside_ = create_degen || keep_degen_a || + (inside_ == is_degenerate_hole_[r.a0_loop_match_id]); + } else if (r.a0_matches_polygon) { + // A point loop in A matches a polygon vertex in B. Note that this code + // can emit an extra isolated vertex if A represents a point hole, but + // this doesn't matter (see comments on the call to AddPointEdge below). + if (polygon_model_ != PolygonModel::SEMI_OPEN) { + inside_ = create_degen || keep_degen_a; + } + } + } else if (r.matches_polygon()) { + if (is_degenerate(a_id)) { + // The A edge has a sibling. The edge is kept only: + // - for closed intersection, open union, and open difference; + // - if the A sibling pair is a hole and the B edge has no sibling; or + // - if the B geometry is also a sibling pair and A and B are both + // holes or both shells. + inside_ = create_degen || keep_degen_a || + (!r.matches_sibling() || inside_) == is_degenerate_hole_[a_id]; + } else { + // Matching edges are kept unless the B geometry is a sibling pair, in + // which case it is kept only for closed intersection, open union, and + // open difference. + if (!r.matches_sibling() || create_degen || keep_degen_b) inside_ = true; + } + } else if (r.matches_sibling()) { + if (is_degenerate(a_id)) { + // The A edge has a sibling. The edge is kept only if A is a sibling + // pair shell and the operation is closed intersection, open union, or + // open difference. + inside_ = (create_degen || keep_degen_a) && !is_degenerate_hole_[a_id]; + } else { + inside_ = create_degen; + } + } + if (inside_ != semi_open_inside) { + ++r.a1_crossings; // Restores the correct (semi-open) state later. } // Test whether the first vertex of this edge should be emitted as an - // isolated degenerate vertex. - if (a_id.edge_id == chain_start_) { - chain_v0_emitted_ = inside_; - } else if (emit_shared && emit_degenerate && r.a0_matches_polygon && - is_v0_isolated(a_id)) { + // isolated degenerate vertex. This is only needed in the second pass when: + // - a0 matches a vertex of the B polygon; + // - the initial part of the A edge will not be emitted; and + // - the operation is closed intersection or open union, or open difference + // and the B geometry is a point loop. + // + // The logic does not attempt to avoid redundant extra vertices (e.g. the + // extra code in ProcessEdge1() that checks whether the vertex is the + // endpoint of the preceding emitted edge) since these these will be removed + // during S2Builder::Graph creation by DegenerateEdges::DISCARD or + // DISCARD_EXCESS (which are necessary in any case due to snapping). + if (emit_shared && r.a0_matches_polygon && !inside_ && + (create_degen || (keep_degen_b && r.loop_matches_a0()))) { if (!AddPointEdge(a.v0, 2)) return false; } + // Since we skipped edges in the first pass that only had a sibling pair + // match in the B geometry, we sometimes need to emit the sibling pair of an + // edge in the second pass. This happens only if: + // - the operation is closed intersection, open union, or open difference; + // - the A geometry is not a sibling pair (since otherwise we will process + // that edge as well); and + // - the B geometry is not a sibling pair hole (since then only one edge + // should be emitted). + if (r.matches_sibling() && (create_degen || keep_degen_b) && + !is_degenerate(a_id) && !is_b_hole) { + S2Shape::Edge sibling(a.v1, a.v0); + if (!AddEdge(r.sibling_match_id, sibling, 2 /*dimension*/, 0)) { + return false; + } + } + // Test whether the entire edge or any part of it belongs to the output. if (inside_ || r.interior_crossings > 0) { // Note: updates "inside_" to correspond to the state just before a1. @@ -1574,10 +1880,6 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge2( return false; } } - - // Remember whether the edge portion just before "a1" was emitted, so that - // we can decide whether "a1" need to be emitted as an isolated vertex. - if (inside_) v0_emitted_max_edge_id_ = a_id.edge_id + 1; inside_ ^= (r.a1_crossings & 1); // Verify that edge crossings are being counted correctly. @@ -1585,13 +1887,6 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge2( S2_DCHECK_EQ(MakeS2ContainsPointQuery(&it->b_index()).Contains(a.v1), inside_ ^ invert_b_); } - - // Special case to test whether the last vertex of a loop should be emitted - // as an isolated degenerate vertex. - if (emit_shared && emit_degenerate && r.a1_matches_polygon && - it->crossings_complete() && is_chain_last_vertex_isolated(a_id)) { - if (!AddPointEdge(a.v1, 2)) return false; - } return true; } @@ -1600,50 +1895,57 @@ bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge2( // // NOTE(ericv): We could save a bit of work when matching polygon vertices by // passing in a flag saying whether this information is needed. For example -// if is only needed in ProcessEdge2 when (emit_shared && emit_degenerate). +// it is only needed in ProcessEdge2 when (emit_shared && create_degenerate). S2BooleanOperation::Impl::CrossingProcessor::EdgeCrossingResult S2BooleanOperation::Impl::CrossingProcessor::ProcessEdgeCrossings( ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) { + pending_source_edge_crossings_.clear(); EdgeCrossingResult r; if (it->Done(a_id)) return r; - // TODO(ericv): bool a_degenerate = (a.v0 == a.v1); for (; !it->Done(a_id); it->Next()) { - // Polylines and polygons are not affected by point geometry. + // Polyline and polygon "inside" states are not affected by point geometry. if (it->b_dimension() == 0) continue; S2Shape::Edge b = it->b_edge(); if (it->is_interior_crossing()) { // The crossing occurs in the edge interior. The condition below says - // that (1) polyline crossings don't affect polygon output, and (2) - // subtracting a crossing polyline from a polyline has no effect. + // that (1) polyline crossings don't affect the polygon "inside" state, + // and (2) subtracting a crossing polyline from a polyline does not + // affect its "inside" state. (Note that vertices are still created at + // the intersection points.) if (a_dimension_ <= it->b_dimension() && !(invert_b_ != invert_result_ && it->b_dimension() == 1)) { SourceId src_id(b_region_id_, it->b_shape_id(), it->b_edge_id()); - AddCrossing(make_pair(src_id, it->left_to_right())); + AddInteriorCrossing(make_pair(src_id, it->left_to_right())); } r.interior_crossings += (it->b_dimension() == 1) ? 2 : 1; } else if (it->b_dimension() == 1) { - // Polygons are not affected by polyline geometry. + // The polygon "inside" state is not affected by polyline geometry. if (a_dimension_ == 2) continue; if ((a.v0 == b.v0 && a.v1 == b.v1) || (a.v0 == b.v1 && a.v1 == b.v0)) { r.matches_polyline = true; } if ((a.v0 == b.v0 || a.v0 == b.v1) && - PolylineEdgeContainsVertex(a.v0, *it)) { + PolylineEdgeContainsVertex(a.v0, *it, 1)) { r.a0_matches_polyline = true; } if ((a.v1 == b.v0 || a.v1 == b.v1) && - PolylineEdgeContainsVertex(a.v1, *it)) { + PolylineEdgeContainsVertex(a.v1, *it, 1)) { r.a1_matches_polyline = true; } } else { S2_DCHECK_EQ(2, it->b_dimension()); - if (a.v0 == b.v0 && a.v1 == b.v1) { + if (a.v0 == a.v1 || b.v0 == b.v1) { + // There are no edge crossings since at least one edge is degenerate. + if (a.v0 == b.v0 && a.v0 == b.v1) { + r.a0_loop_match_id = it->b_id(); + } + } else if (a.v0 == b.v0 && a.v1 == b.v1) { ++r.a0_crossings; - r.matches_polygon = true; + r.polygon_match_id = it->b_id(); } else if (a.v0 == b.v1 && a.v1 == b.v0) { ++r.a0_crossings; - r.matches_sibling = true; + r.sibling_match_id = it->b_id(); } else if (it->is_vertex_crossing()) { if (a.v0 == b.v0 || a.v0 == b.v1) { ++r.a0_crossings; @@ -1664,13 +1966,18 @@ S2BooleanOperation::Impl::CrossingProcessor::ProcessEdgeCrossings( // Returns true if the vertex "v" is contained by the polyline edge referred // to by the CrossingIterator "it", taking into account the PolylineModel. +// "dimension" is 0 or 1 according to whether "v" should be modeled as a point +// or as a degenerate polyline. (This only makes a difference when the +// containing polyline is degenerate, since the polyline AA contains itself in +// all boundary models but contains the point A only in the CLOSED model.) // // REQUIRES: it.b_dimension() == 1 // REQUIRES: "v" is an endpoint of it.b_edge() bool S2BooleanOperation::Impl::CrossingProcessor::PolylineEdgeContainsVertex( - const S2Point& v, const CrossingIterator& it) const { + const S2Point& v, const CrossingIterator& it, int dimension) const { S2_DCHECK_EQ(1, it.b_dimension()); S2_DCHECK(it.b_edge().v0 == v || it.b_edge().v1 == v); + S2_DCHECK(dimension == 0 || dimension == 1); // Closed polylines contain all their vertices. if (polyline_model_ == PolylineModel::CLOSED) return true; @@ -1680,11 +1987,14 @@ bool S2BooleanOperation::Impl::CrossingProcessor::PolylineEdgeContainsVertex( const auto& b_chain = it.b_chain_info(); int b_edge_id = it.b_edge_id(); - // The last polyline vertex is never contained. (For polyline loops, it is - // sufficient to treat the first vertex as begin contained.) This case also - // handles degenerate polylines (polylines with one edge where v0 == v1), - // which do not contain any points. - if (b_edge_id == b_chain.limit - 1 && v == it.b_edge().v1) return false; + // A polyline contains its last vertex only when the polyline is degenerate + // (v0 == v1) and "v" is modeled as a degenerate polyline (dimension == 1). + // This corresponds to the fact that the polyline AA contains itself in all + // boundary models, but contains the point A only in the CLOSED model. + if (b_edge_id == b_chain.limit - 1 && v == it.b_edge().v1 && + (dimension == 0 || b_edge_id > 0 || v != it.b_edge().v0)) { + return false; + } // Otherwise all interior vertices are contained. The first polyline // vertex is contained if either the polyline model is not OPEN, or the @@ -1699,20 +2009,21 @@ bool S2BooleanOperation::Impl::CrossingProcessor::PolylineEdgeContainsVertex( // Translates the temporary representation of crossing edges (SourceId) into // the format expected by EdgeClippingLayer (InputEdgeId). void S2BooleanOperation::Impl::CrossingProcessor::DoneBoundaryPair() { + tracker_->AddSpaceExact(input_crossings_, source_edge_crossings_.size()); + if (!tracker_->TallySourceIdMap(3)) return; + // Add entries that translate the "special" crossings. source_id_map_[SourceId(kSetInside)] = kSetInside; source_id_map_[SourceId(kSetInvertB)] = kSetInvertB; source_id_map_[SourceId(kSetReverseA)] = kSetReverseA; - input_crossings_->reserve(input_crossings_->size() + - source_edge_crossings_.size()); for (const auto& tmp : source_edge_crossings_) { auto it = source_id_map_.find(tmp.second.first); S2_DCHECK(it != source_id_map_.end()); input_crossings_->push_back(make_pair( tmp.first, CrossingInputEdge(it->second, tmp.second.second))); } - source_edge_crossings_.clear(); - source_id_map_.clear(); + tracker_->Clear(&source_edge_crossings_); + tracker_->ClearSourceIdMap(&source_id_map_); } // Clips the boundary of A to the interior of the opposite region B and adds @@ -1832,6 +2143,7 @@ bool S2BooleanOperation::Impl::GetChainStarts( ShapeEdge a(shape_id, chain.start, a_shape->chain_edge(chain_id, 0)); bool inside = (b_has_interior && query.Contains(a.v0())) != invert_b; if (inside) { + if (!tracker_.AddSpace(chain_starts, 1)) return false; chain_starts->push_back(ShapeEdgeId(shape_id, chain.start)); } if (is_boolean_output()) { @@ -1841,6 +2153,7 @@ bool S2BooleanOperation::Impl::GetChainStarts( } } } + if (!tracker_.AddSpace(chain_starts, 1)) return false; chain_starts->push_back(kSentinel); return true; } @@ -1882,6 +2195,7 @@ bool S2BooleanOperation::Impl::HasInterior(const S2ShapeIndex& index) { inline bool S2BooleanOperation::Impl::AddIndexCrossing( const ShapeEdge& a, const ShapeEdge& b, bool is_interior, IndexCrossings* crossings) { + if (!tracker_.AddSpace(crossings, 1)) return false; crossings->push_back(IndexCrossing(a.id(), b.id())); IndexCrossing* crossing = &crossings->back(); if (is_interior) { @@ -1889,6 +2203,8 @@ inline bool S2BooleanOperation::Impl::AddIndexCrossing( if (s2pred::Sign(a.v0(), a.v1(), b.v0()) > 0) { crossing->left_to_right = true; } + builder_->AddIntersection( + S2::GetIntersection(a.v0(), a.v1(), b.v0(), b.v1())); } else { // TODO(ericv): This field isn't used unless one shape is a polygon and // the other is a polyline or polygon, but we don't have the shape @@ -1909,6 +2225,9 @@ bool S2BooleanOperation::Impl::GetIndexCrossings(int region_id) { if (region_id == index_crossings_first_region_id_) return true; if (index_crossings_first_region_id_ < 0) { S2_DCHECK_EQ(region_id, 0); // For efficiency, not correctness. + // TODO(ericv): This would be more efficient if VisitCrossingEdgePairs() + // returned the sign (+1 or -1) of the interior crossing, i.e. + // "int interior_crossing_sign" rather than "bool is_interior". if (!s2shapeutil::VisitCrossingEdgePairs( *op_->regions_[0], *op_->regions_[1], s2shapeutil::CrossingType::ALL, @@ -1928,6 +2247,7 @@ bool S2BooleanOperation::Impl::GetIndexCrossings(int region_id) { index_crossings_.end()); } // Add a sentinel value to simplify the loop logic. + tracker_.AddSpace(&index_crossings_, 1); index_crossings_.push_back(IndexCrossing(kSentinel, kSentinel)); index_crossings_first_region_id_ = 0; } @@ -1941,7 +2261,7 @@ bool S2BooleanOperation::Impl::GetIndexCrossings(int region_id) { std::sort(index_crossings_.begin(), index_crossings_.end()); index_crossings_first_region_id_ = region_id; } - return true; + return tracker_.ok(); } // Supports "early exit" in the case of boolean results by returning false @@ -1951,16 +2271,19 @@ bool S2BooleanOperation::Impl::AddBoundaryPair( // Optimization: if the operation is DIFFERENCE or SYMMETRIC_DIFFERENCE, // it is worthwhile checking whether the two regions are identical (in which // case the output is empty). - // - // TODO(ericv): When boolean output is requested there are other quick - // checks that could be done here, such as checking whether a full cell from - // one S2ShapeIndex intersects a non-empty cell of the other S2ShapeIndex. - auto type = op_->op_type(); + auto type = op_->op_type_; if (type == OpType::DIFFERENCE || type == OpType::SYMMETRIC_DIFFERENCE) { if (AreRegionsIdentical()) return true; - } else if (!is_boolean_output()) { + } else if (is_boolean_output()) { + // TODO(ericv): When boolean output is requested there are other quick + // checks that could be done here, such as checking whether a full cell from + // one S2ShapeIndex intersects a non-empty cell of the other S2ShapeIndex. } vector a_starts, b_starts; + auto _ = absl::MakeCleanup([&]() { + tracker_.Untally(a_starts); + tracker_.Untally(b_starts); + }); if (!GetChainStarts(0, invert_a, invert_b, invert_result, cp, &a_starts) || !GetChainStarts(1, invert_b, invert_a, invert_result, cp, &b_starts) || !AddBoundary(0, invert_a, invert_b, invert_result, a_starts, cp) || @@ -1968,7 +2291,7 @@ bool S2BooleanOperation::Impl::AddBoundaryPair( return false; } if (!is_boolean_output()) cp->DoneBoundaryPair(); - return true; + return tracker_.ok(); } // Supports "early exit" in the case of boolean results by returning false @@ -1978,7 +2301,8 @@ bool S2BooleanOperation::Impl::BuildOpType(OpType op_type) { CrossingProcessor cp(op_->options_.polygon_model(), op_->options_.polyline_model(), op_->options_.polyline_loops_have_boundaries(), - builder_.get(), &input_dimensions_, &input_crossings_); + builder_.get(), &input_dimensions_, &input_crossings_, + &tracker_); switch (op_type) { case OpType::UNION: // A | B == ~(~A & ~B) @@ -1990,6 +2314,11 @@ bool S2BooleanOperation::Impl::BuildOpType(OpType op_type) { case OpType::DIFFERENCE: // A - B = A & ~B + // + // Note that degeneracies are implemented such that the symmetric + // operation (-B + A) also produces correct results. This can be tested + // by swapping op_->regions[0, 1] and calling AddBoundaryPair(true, + // false, false), which computes (~B & A). return AddBoundaryPair(false, true, false, &cp); case OpType::SYMMETRIC_DIFFERENCE: @@ -2049,7 +2378,7 @@ bool S2BooleanOperation::Impl::IsFullPolygonResult( // but would also allows all cases to be handled 100% robustly. const S2ShapeIndex& a = *op_->regions_[0]; const S2ShapeIndex& b = *op_->regions_[1]; - switch (op_->op_type()) { + switch (op_->op_type_) { case OpType::UNION: return IsFullPolygonUnion(a, b); @@ -2162,8 +2491,7 @@ bool S2BooleanOperation::Impl::IsFullPolygonSymmetricDifference( // To determine whether the result is ambiguous, we compute a rough estimate // of the maximum expected area error (including errors due to snapping), // using the worst-case error bound for a hemisphere defined by 4 vertices. - auto edge_snap_radius = op_->options_.snap_function().snap_radius() + - S2::kIntersectionError; // split_crossing_edges + auto edge_snap_radius = builder_options_.edge_snap_radius(); double hemisphere_area_error = 2 * M_PI * edge_snap_radius.radians() + 40 * DBL_EPSILON; // GetCurvatureMaxError @@ -2193,29 +2521,48 @@ bool S2BooleanOperation::Impl::IsFullPolygonSymmetricDifference( return error_sign > 0; } +// When subtracting regions, we can save a lot of work by detecting the +// relatively common case where the two regions are identical. bool S2BooleanOperation::Impl::AreRegionsIdentical() const { const S2ShapeIndex* a = op_->regions_[0]; const S2ShapeIndex* b = op_->regions_[1]; if (a == b) return true; + + // If the regions are not identical, we would like to detect that fact as + // quickly as possible. In particular we would like to avoid fully decoding + // both shapes if they are represented as encoded shape types. + // + // First we test whether the two geometries have the same dimensions and + // chain structure. This can be done without decoding any S2Points. int num_shape_ids = a->num_shape_ids(); if (num_shape_ids != b->num_shape_ids()) return false; for (int s = 0; s < num_shape_ids; ++s) { const S2Shape* a_shape = a->shape(s); const S2Shape* b_shape = b->shape(s); - if (a_shape->dimension() != b_shape->dimension()) return false; - if (a_shape->dimension() == 2) { - auto a_ref = a_shape->GetReferencePoint(); - auto b_ref = b_shape->GetReferencePoint(); - if (a_ref.point != b_ref.point) return false; - if (a_ref.contained != b_ref.contained) return false; - } + int dimension = a_shape->dimension(); + if (dimension != b_shape->dimension()) return false; int num_chains = a_shape->num_chains(); if (num_chains != b_shape->num_chains()) return false; + int num_edges = a_shape->num_edges(); + if (num_edges != b_shape->num_edges()) return false; + if (dimension == 0) { + S2_DCHECK_EQ(num_edges, num_chains); // All chains are of length 1. + continue; + } for (int c = 0; c < num_chains; ++c) { S2Shape::Chain a_chain = a_shape->chain(c); S2Shape::Chain b_chain = b_shape->chain(c); S2_DCHECK_EQ(a_chain.start, b_chain.start); if (a_chain.length != b_chain.length) return false; + } + } + // Next we test whether both geometries have the same vertex positions. + for (int s = 0; s < num_shape_ids; ++s) { + const S2Shape* a_shape = a->shape(s); + const S2Shape* b_shape = b->shape(s); + int num_chains = a_shape->num_chains(); + for (int c = 0; c < num_chains; ++c) { + S2Shape::Chain a_chain = a_shape->chain(c); for (int i = 0; i < a_chain.length; ++i) { S2Shape::Edge a_edge = a_shape->chain_edge(c, i); S2Shape::Edge b_edge = b_shape->chain_edge(c, i); @@ -2223,32 +2570,36 @@ bool S2BooleanOperation::Impl::AreRegionsIdentical() const { if (a_edge.v1 != b_edge.v1) return false; } } + // Note that we don't need to test whether both shapes have the same + // GetReferencePoint(), because S2Shape requires that the full geometry of + // the shape (including its interior) must be derivable from its chains + // and edges. This is why the "full loop" exists; see s2shape.h. } return true; } -bool S2BooleanOperation::Impl::Build(S2Error* error) { - error->Clear(); +void S2BooleanOperation::Impl::DoBuild(S2Error* error) { + if (!tracker_.ok()) return; + builder_options_ = S2Builder::Options(op_->options_.snap_function()); + builder_options_.set_intersection_tolerance(S2::kIntersectionError); + builder_options_.set_memory_tracker(tracker_.tracker()); + if (op_->options_.split_all_crossing_polyline_edges()) { + builder_options_.set_split_crossing_edges(true); + } + // TODO(ericv): Ideally idempotent() should be true, but existing clients + // expect vertices closer than the full "snap_radius" to be snapped. + builder_options_.set_idempotent(false); + if (is_boolean_output()) { // BuildOpType() returns true if and only if the result has no edges. S2Builder::Graph g; // Unused by IsFullPolygonResult() implementation. *op_->result_empty_ = - BuildOpType(op_->op_type()) && !IsFullPolygonResult(g, error); - return true; + BuildOpType(op_->op_type_) && !IsFullPolygonResult(g, error); + return; } - // TODO(ericv): Rather than having S2Builder split the edges, it would be - // faster to call AddVertex() in this class and have a new S2Builder - // option that increases the edge_snap_radius_ to account for errors in - // the intersection point (the way that split_crossing_edges does). - S2Builder::Options options(op_->options_.snap_function()); - options.set_split_crossing_edges(true); - - // TODO(ericv): Ideally idempotent() should be true, but existing clients - // expect vertices closer than the full "snap_radius" to be snapped. - options.set_idempotent(false); - builder_ = make_unique(options); + builder_ = make_unique(builder_options_); builder_->StartLayer(make_unique( - &op_->layers_, &input_dimensions_, &input_crossings_)); + &op_->layers_, &input_dimensions_, &input_crossings_, &tracker_)); // Add a predicate that decides whether a result with no polygon edges should // be interpreted as the empty polygon or the full polygon. @@ -2256,8 +2607,19 @@ bool S2BooleanOperation::Impl::Build(S2Error* error) { [this](const S2Builder::Graph& g, S2Error* error) { return IsFullPolygonResult(g, error); }); - (void) BuildOpType(op_->op_type()); - return builder_->Build(error); + (void) BuildOpType(op_->op_type_); + + // Release memory that is no longer needed. + if (!tracker_.Clear(&index_crossings_)) return; + builder_->Build(error); +} + +bool S2BooleanOperation::Impl::Build(S2Error* error) { + // This wrapper ensures that memory tracking errors are reported. + error->Clear(); + DoBuild(error); + if (!tracker_.ok()) *error = tracker_.error(); + return error->ok(); } S2BooleanOperation::Options::Options() @@ -2270,13 +2632,16 @@ S2BooleanOperation::Options::Options(const SnapFunction& snap_function) } S2BooleanOperation::Options::Options(const Options& options) - : snap_function_(options.snap_function_->Clone()), - polygon_model_(options.polygon_model_), - polyline_model_(options.polyline_model_), - polyline_loops_have_boundaries_(options.polyline_loops_have_boundaries_), - precision_(options.precision_), - conservative_output_(options.conservative_output_), - source_id_lexicon_(options.source_id_lexicon_) { + : snap_function_(options.snap_function_->Clone()), + polygon_model_(options.polygon_model_), + polyline_model_(options.polyline_model_), + polyline_loops_have_boundaries_(options.polyline_loops_have_boundaries_), + split_all_crossing_polyline_edges_( + options.split_all_crossing_polyline_edges_), + precision_(options.precision_), + conservative_output_(options.conservative_output_), + source_id_lexicon_(options.source_id_lexicon_), + memory_tracker_(options.memory_tracker_) { } S2BooleanOperation::Options& S2BooleanOperation::Options::operator=( @@ -2285,9 +2650,12 @@ S2BooleanOperation::Options& S2BooleanOperation::Options::operator=( polygon_model_ = options.polygon_model_; polyline_model_ = options.polyline_model_; polyline_loops_have_boundaries_ = options.polyline_loops_have_boundaries_; + split_all_crossing_polyline_edges_ = + options.split_all_crossing_polyline_edges_; precision_ = options.precision_; conservative_output_ = options.conservative_output_; source_id_lexicon_ = options.source_id_lexicon_; + memory_tracker_ = options.memory_tracker_; return *this; } @@ -2325,6 +2693,15 @@ void S2BooleanOperation::Options::set_polyline_loops_have_boundaries( polyline_loops_have_boundaries_ = value; } +bool S2BooleanOperation::Options::split_all_crossing_polyline_edges() const { + return split_all_crossing_polyline_edges_; +} + +void S2BooleanOperation::Options::set_split_all_crossing_polyline_edges( + bool value) { + split_all_crossing_polyline_edges_ = value; +} + Precision S2BooleanOperation::Options::precision() const { return precision_; } @@ -2338,7 +2715,15 @@ S2BooleanOperation::Options::source_id_lexicon() const { return source_id_lexicon_; } -const char* S2BooleanOperation::OpTypeToString(OpType op_type) { +S2MemoryTracker* S2BooleanOperation::Options::memory_tracker() const { + return memory_tracker_; +} + +void S2BooleanOperation::Options::set_memory_tracker(S2MemoryTracker* tracker) { + memory_tracker_ = tracker; +} + +string_view S2BooleanOperation::OpTypeToString(OpType op_type) { switch (op_type) { case OpType::UNION: return "UNION"; case OpType::INTERSECTION: return "INTERSECTION"; @@ -2348,27 +2733,44 @@ const char* S2BooleanOperation::OpTypeToString(OpType op_type) { } } +string_view S2BooleanOperation::PolygonModelToString(PolygonModel model) { + switch (model) { + case PolygonModel::OPEN: return "OPEN"; + case PolygonModel::SEMI_OPEN: return "SEMI_OPEN"; + case PolygonModel::CLOSED: return "CLOSED"; + default: return "Unknown PolygonModel"; + } +} + +string_view S2BooleanOperation::PolylineModelToString(PolylineModel model) { + switch (model) { + case PolylineModel::OPEN: return "OPEN"; + case PolylineModel::SEMI_OPEN: return "SEMI_OPEN"; + case PolylineModel::CLOSED: return "CLOSED"; + default: return "Unknown PolylineModel"; + } +} + S2BooleanOperation::S2BooleanOperation(OpType op_type, const Options& options) - : op_type_(op_type), options_(options), result_empty_(nullptr) { + : options_(options), op_type_(op_type), result_empty_(nullptr) { } S2BooleanOperation::S2BooleanOperation(OpType op_type, bool* result_empty, const Options& options) - : op_type_(op_type), options_(options), - result_empty_(result_empty) { + : options_(options), op_type_(op_type), result_empty_(result_empty) { } S2BooleanOperation::S2BooleanOperation( OpType op_type, unique_ptr layer, const Options& options) - : op_type_(op_type), options_(options), result_empty_(nullptr) { + : options_(options), op_type_(op_type), result_empty_(nullptr) { layers_.push_back(std::move(layer)); } S2BooleanOperation::S2BooleanOperation( OpType op_type, vector> layers, const Options& options) - : op_type_(op_type), options_(options), layers_(std::move(layers)), + : options_(options), op_type_(op_type), layers_(std::move(layers)), result_empty_(nullptr) { } diff --git a/src/s2/s2boolean_operation.h b/src/s2/s2boolean_operation.h index acceb06d..d40c9e65 100644 --- a/src/s2/s2boolean_operation.h +++ b/src/s2/s2boolean_operation.h @@ -21,9 +21,14 @@ #include #include #include + +#include "s2/base/integral_types.h" #include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" +#include "s2/s2error.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2shape_index.h" #include "s2/value_lexicon.h" // This class implements boolean operations (intersection, union, difference, @@ -36,15 +41,40 @@ // these objects, except that polygon interiors must be disjoint from all // other geometry (including other polygon interiors). If the input geometry // for a region does not meet this condition, it can be normalized by -// computing its union first. Note that points or polylines are allowed to -// coincide with the boundaries of polygons. -// -// Degeneracies are supported. A polygon loop or polyline may consist of a -// single edge from a vertex to itself, and polygons may contain "sibling -// pairs" consisting of an edge and its corresponding reverse edge. Polygons -// must not have any duplicate edges (due to the requirement that polygon -// interiors are disjoint), but polylines may have duplicate edges or can even -// be self-intersecting. +// computing its union first. Duplicate polygon edges are not allowed (even +// among different polygons), however polylines may have duplicate edges and +// may even be self-intersecting. Note that points or polylines are allowed +// to coincide with the boundaries of polygons. +// +// Degeneracies are fully supported. Supported degeneracy types include the +// following: +// +// - Point polylines consisting of a single degenerate edge AA. +// +// - Point loops consisting of a single vertex A. Such loops may represent +// either shells or holes according to whether the loop adds to or +// subtracts from the surrounding region of the polygon. +// +// - Sibling edge pairs of the form {AB, BA}. Such sibling pairs may +// represent either shells or holes according to whether they add to or +// subtract from the surrounding region. The edges of a sibling pair may +// belong to the same polygon loop (e.g. a loop AB) or to different polygon +// loops or polygons (e.g. the polygons {ABC, CBD}). +// +// A big advantage of degeneracy support is that geometry may be simplified +// without completely losing small details. For example, if a polygon +// representing a land area with many lakes and rivers is simplified using a +// tolerance of 1 kilometer, every water feature in the input is guaranteed to +// be within 1 kilometer of some water feature in the input (even if some +// lakes and rivers are merged and/or reduced to degenerate point or sibling +// edge pair holes). Mathematically speaking, degeneracy support allows +// geometry to be simplified while guaranteeing that the Hausdorff distance +// betweeen the boundaries of the original and simplified geometries is at +// most the simplification tolerance. It also allows geometry to be +// simplified without changing its dimension, thus preserving boundary +// semantics. (Note that the boundary of a polyline ABCD is {A,D}, whereas +// the boundary of a degenerate shell ABCDCB is its entire set of vertices and +// edges.) // // Points and polyline edges are treated as multisets: if the same point or // polyline edge appears multiple times in the input, it will appear multiple @@ -53,8 +83,14 @@ // sets of points or polylines as a single region while maintaining their // distinct identities, even when the points or polylines intersect each // other. It is also useful for reconstructing polylines that loop back on -// themselves. If duplicate geometry is not desired, it can be merged by -// GraphOptions::DuplicateEdges::MERGE in the S2Builder output layer. +// themselves (e.g., time series such as GPS tracks). If duplicate geometry +// is not desired, it can easily be removed by choosing the appropriate +// S2Builder output layer options. +// +// Self-intersecting polylines can be manipulated without materializing new +// vertices at the self-intersection points. This feature is important when +// processing polylines with large numbers of self-intersections such as GPS +// tracks (e.g., consider the path of a race car in the Indy 500). // // Polylines are always considered to be directed. Polyline edges between the // same pair of vertices are defined to intersect even if the two edges are in @@ -167,7 +203,7 @@ // S2BooleanOperation::Options options; // options.set_snap_function(snap_function); // S2BooleanOperation op(S2BooleanOperation::OpType::INTERSECTION, -// absl::make_unique(&polygon), +// std::make_unique(&polygon), // options); // S2Error error; // if (!op.Build(a, b, &error)) { @@ -183,29 +219,35 @@ // S2Polygon polygon; // S2BooleanOperation op( // S2BooleanOperation::OpType::UNION, -// absl::make_unique(&points), -// absl::make_unique(&polylines), -// absl::make_unique(&polygon)); +// std::make_unique(&points), +// std::make_unique(&polylines), +// std::make_unique(&polygon)); class S2BooleanOperation { public: // The supported operation types. - enum class OpType { + enum class OpType : uint8 { UNION, // Contained by either region. INTERSECTION, // Contained by both regions. DIFFERENCE, // Contained by the first region but not the second. SYMMETRIC_DIFFERENCE // Contained by one region but not the other. }; // Translates OpType to one of the strings above. - static const char* OpTypeToString(OpType op_type); + static absl::string_view OpTypeToString(OpType op_type); // Defines whether polygons are considered to contain their vertices and/or // edges (see definitions above). - enum class PolygonModel { OPEN, SEMI_OPEN, CLOSED }; + enum class PolygonModel : uint8 { OPEN, SEMI_OPEN, CLOSED }; + + // Translates PolygonModel to one of the strings above. + static absl::string_view PolygonModelToString(PolygonModel model); // Defines whether polylines are considered to contain their endpoints // (see definitions above). - enum class PolylineModel { OPEN, SEMI_OPEN, CLOSED }; + enum class PolylineModel : uint8 { OPEN, SEMI_OPEN, CLOSED }; + + // Translates PolylineModel to one of the strings above. + static absl::string_view PolylineModelToString(PolylineModel model); // With Precision::EXACT, the operation is evaluated using the exact input // geometry. Predicates that use this option will produce exact results; @@ -228,7 +270,7 @@ class S2BooleanOperation { // Conceptually, the difference between these two options is that with // Precision::SNAPPED, the inputs are snap rounded (together), whereas with // Precision::EXACT only the result is snap rounded. - enum class Precision { EXACT, SNAPPED }; + enum class Precision : uint8 { EXACT, SNAPPED }; // SourceId identifies an edge from one of the two input S2ShapeIndexes. // It consists of a region id (0 or 1), a shape id within that region's @@ -308,6 +350,18 @@ class S2BooleanOperation { bool polyline_loops_have_boundaries() const; void set_polyline_loops_have_boundaries(bool value); + // Specifies that a new vertex should be added whenever a polyline edge + // crosses another polyline edge. Note that this can cause the size of + // polylines with many self-intersections to increase quadratically. + // + // If false, new vertices are added only when a polyline from one input + // region cross a polyline from the other input region. This allows + // self-intersecting input polylines to be modified as little as possible. + // + // DEFAULT: false + bool split_all_crossing_polyline_edges() const; + void set_split_all_crossing_polyline_edges(bool value); + // Specifies whether the operation should use the exact input geometry // (Precision::EXACT), or whether the two input regions should be snapped // together first (Precision::SNAPPED). @@ -367,6 +421,36 @@ class S2BooleanOperation { ValueLexicon* source_id_lexicon() const; // void set_source_id_lexicon(ValueLexicon* source_id_lexicon); + // Specifies that internal memory usage should be tracked using the given + // S2MemoryTracker. If a memory limit is specified and more more memory + // than this is required then an error will be returned. Example usage: + // + // S2MemoryTracker tracker; + // tracker.set_limit(500 << 20); // 500 MB + // S2BooleanOperation::Options options; + // options.set_memory_tracker(&tracker); + // S2BooleanOperation op(..., options); + // ... + // S2Error error; + // if (!op.Build(..., &error)) { + // if (error.code() == S2Error::RESOURCE_EXHAUSTED) { + // S2_LOG(ERROR) << error; // Memory limit exceeded + // } + // } + // + // CAVEATS: + // + // - Memory used by the input S2ShapeIndexes and the output S2Builder + // layers is not counted towards the total. + // + // - While memory tracking is reasonably complete and accurate, it does + // not account for every last byte. It is intended only for the + // purpose of preventing clients from running out of memory. + // + // DEFAULT: nullptr (memory tracking disabled) + S2MemoryTracker* memory_tracker() const; + void set_memory_tracker(S2MemoryTracker* tracker); + // Options may be assigned and copied. Options(const Options& options); Options& operator=(const Options& options); @@ -376,11 +460,18 @@ class S2BooleanOperation { PolygonModel polygon_model_ = PolygonModel::SEMI_OPEN; PolylineModel polyline_model_ = PolylineModel::CLOSED; bool polyline_loops_have_boundaries_ = true; + bool split_all_crossing_polyline_edges_ = false; Precision precision_ = Precision::EXACT; bool conservative_output_ = false; ValueLexicon* source_id_lexicon_ = nullptr; + S2MemoryTracker* memory_tracker_ = nullptr; }; +#ifndef SWIG + // Specifies that the output boundary edges should be sent to a single + // S2Builder layer. This version can be used when the dimension of the + // output geometry is known (e.g., intersecting two polygons to yield a + // third polygon). S2BooleanOperation(OpType op_type, std::unique_ptr layer, const Options& options = Options()); @@ -406,8 +497,10 @@ class S2BooleanOperation { S2BooleanOperation(OpType op_type, std::vector> layers, const Options& options = Options()); +#endif OpType op_type() const { return op_type_; } + const Options& options() const { return options_; } // Executes the given operation. Returns true on success, and otherwise // sets "error" appropriately. (This class does not generate any errors @@ -454,8 +547,8 @@ class S2BooleanOperation { S2BooleanOperation(OpType op_type, bool* result_empty, const Options& options = Options()); - OpType op_type_; Options options_; + OpType op_type_; // The input regions. const S2ShapeIndex* regions_[2]; diff --git a/src/s2/s2buffer_operation.cc b/src/s2/s2buffer_operation.cc new file mode 100644 index 00000000..b4d50a09 --- /dev/null +++ b/src/s2/s2buffer_operation.cc @@ -0,0 +1,770 @@ +// Copyright 2020 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) +// +// The algorithm below essentially computes the offset curve of the original +// boundary, and uses this curve to divide the sphere into regions of constant +// winding number. Since winding numbers on the sphere are relative rather +// than absolute (see s2winding_operation.h), we also need to keep track of +// the desired winding number at a fixed reference point. The initial winding +// number for this point is the number of input shapes that contain it. We +// then update it during the buffering process by imagining a "sweep edge" +// that extends from the current point A on the input boundary to the +// corresponding point B on the offset curve. As we process an input loop and +// generate the corresponding offset curve, the sweep edge moves continuously +// and covers the entire buffer region (i.e., the region added to or +// subtracted from the input geometry). We increase the winding number of the +// reference point by one whenever it crosses the sweep edge from left to +// right, and we decrease the winding number by one whenever it crosses the +// sweep edge from right to left. +// +// Concave vertices require special handling, because the corresponding offset +// curve can leave behind regions whose winding number is zero or negative. +// We handle this by splicing the concave vertex into the offset curve itself; +// this effectively terminates the current buffer region and starts a new one, +// such that the region of overlap is counted twice (i.e., its winding number +// increases by two). The end result is the same as though we had computed +// the union of a sequence of buffered convex boundary segments. This trick +// is described in the following paper: "Polygon Offsetting by Computing +// Winding Numbers" (Chen and McMains, Proceedings of IDETC/CIE 2005). +// +// TODO(ericv): The algorithm below is much faster than, say, computing the +// union of many buffered edges. However further improvements are possible. +// In particular, there is an unimplemented optimization that would make it +// much faster to buffer concave boundaries when the buffer radius is large. + +#include "s2/s2buffer_operation.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/types/span.h" +#include "s2/s1angle.h" +#include "s2/s1chord_angle.h" +#include "s2/s2builder.h" +#include "s2/s2builder_layer.h" +#include "s2/s2builderutil_snap_functions.h" +#include "s2/s2contains_point_query.h" +#include "s2/s2edge_crosser.h" +#include "s2/s2edge_crossings.h" +#include "s2/s2edge_distances.h" +#include "s2/s2error.h" +#include "s2/s2lax_loop_shape.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" +#include "s2/s2point_span.h" +#include "s2/s2pointutil.h" +#include "s2/s2predicates.h" +#include "s2/s2predicates_internal.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" +#include "s2/s2shape_measures.h" +#include "s2/s2shapeutil_contains_brute_force.h" +#include "s2/s2winding_operation.h" +#include "s2/util/math/mathutil.h" + +using s2pred::DBL_ERR; +using std::ceil; +using std::make_unique; +using std::max; +using std::min; +using std::unique_ptr; +using std::vector; + +// The errors due to buffering can be categorized as follows: +// +// 1. Requested error. This represents the error due to approximating the +// buffered boundary as a sequence of line segments rather than a sequence +// of circular arcs. It is largely controlled by options.error_fraction(), +// and can be bounded as +// +// max(kMinRequestedError, error_fraction * buffer_radius) +// +// where kMinRequestedError reflects the fact that S2Points do not have +// infinite precision. (For example, it makes no sense to attempt to +// buffer geometry by 1e-100 radians because the spacing between +// representable S2Points is only about 2e-16 radians in general.) +// +// 2. Relative interpolation errors. These are numerical errors whose +// magnitude is proportional to the buffer radius. For such errors the +// worst-case coefficient of proportionality turns out to be so tiny +// compared to the smallest allowable error fraction (kMinErrorFraction) +// that we simply ignore such errors. +// +// 3. Absolute interpolation errors. These are numerical errors that are not +// proportional to the buffer radius. The main sources of such errors are +// (1) calling S2::RobustCrossProd() to compute edge normals, and (2) calls +// to S2::GetPointOnRay() to interpolate points along the buffered +// boundary. It is possible to show that this error is at most +// kMaxAbsoluteInterpolationError as defined below. +// +// Putting these together, the final error bound looks like this: +// +// max_error = kMaxAbsoluteInterpolationError + +// max(kMinRequestedError, +// max(kMinErrorFraction, options.error_fraction()) * +// options.buffer_radius()) + +// The maximum angular spacing between representable S2Points on the unit +// sphere is roughly 2 * DBL_ERR. We require the requested absolute error to +// be at least this large because attempting to achieve a smaller error does +// not increase the precision of the result and can increase the running time +// and space requirements considerably. +static constexpr S1Angle kMinRequestedError = S1Angle::Radians(2 * DBL_ERR); + +// The maximum absolute error due to interpolating points on the buffered +// boundary. The following constant bounds the maximum additional error +// perpendicular to the buffered boundary due to all steps of the calculation +// (S2::RobustCrossProd, the two calls to GetPointOnRay, etc). +// +// This distance represents about 10 nanometers on the Earth's surface. Note +// that this is a conservative upper bound and that it is difficult to +// construct inputs where the error is anywhere close to this large. +static constexpr S1Angle kMaxAbsoluteInterpolationError = + S2::kGetPointOnLineError + S2::kGetPointOnRayPerpendicularError; + +// TODO(user, b/210097200): Remove when we require c++17 for opensource. +constexpr double S2BufferOperation::Options::kMinErrorFraction; +constexpr double S2BufferOperation::Options::kMaxCircleSegments; + +S2BufferOperation::Options::Options() + : snap_function_( + make_unique(S1Angle::Zero())) { +} + +S2BufferOperation::Options::Options(S1Angle buffer_radius) : Options() { + buffer_radius_ = buffer_radius; +} + +S2BufferOperation::Options::Options(const Options& options) + : buffer_radius_(options.buffer_radius_), + error_fraction_(options.error_fraction_), + end_cap_style_(options.end_cap_style_), + polyline_side_(options.polyline_side_), + snap_function_(options.snap_function_->Clone()), + memory_tracker_(options.memory_tracker_) { +} + +S2BufferOperation::Options& S2BufferOperation::Options::operator=( + const Options& options) { + buffer_radius_ = options.buffer_radius_; + error_fraction_ = options.error_fraction_; + end_cap_style_ = options.end_cap_style_; + polyline_side_ = options.polyline_side_; + snap_function_ = options.snap_function_->Clone(); + memory_tracker_ = options.memory_tracker_; + return *this; +} + +S1Angle S2BufferOperation::Options::buffer_radius() const { + return buffer_radius_; +} + +void S2BufferOperation::Options::set_buffer_radius(S1Angle buffer_radius) { + buffer_radius_ = buffer_radius; +} + +double S2BufferOperation::Options::error_fraction() const { + return error_fraction_; +} + +void S2BufferOperation::Options::set_error_fraction(double error_fraction) { + S2_DCHECK_GE(error_fraction, kMinErrorFraction); + S2_DCHECK_LE(error_fraction, 1.0); + error_fraction_ = max(kMinErrorFraction, min(1.0, error_fraction)); +} + +const S1Angle S2BufferOperation::Options::max_error() const { + // See comments for kMinRequestedError above. + S2Builder::Options builder_options(*snap_function_); + builder_options.set_split_crossing_edges(true); + return max(kMinRequestedError, error_fraction_ * abs(buffer_radius_)) + + kMaxAbsoluteInterpolationError + builder_options.max_edge_deviation(); +} + +double S2BufferOperation::Options::circle_segments() const { +#if 0 + // This formula assumes that vertices can be placed anywhere. TODO(ericv). + return M_PI / acos((1 - error_fraction_) / (1 + error_fraction_)); +#else + // This formula assumes that all vertices are placed on the midline. + return M_PI / acos(1 - error_fraction_); +#endif +} + +void S2BufferOperation::Options::set_circle_segments(double circle_segments) { + S2_DCHECK_GE(circle_segments, 2.0); + S2_DCHECK_LE(circle_segments, kMaxCircleSegments); + circle_segments = max(2.0, min(kMaxCircleSegments, circle_segments)); + + // We convert circle_segments to error_fraction using planar geometry, + // because the number of segments required to approximate a circle on the + // sphere to within a given tolerance is not constant. Unlike in the plane, + // the total curvature of a circle on the sphere decreases as the area + // enclosed by the circle increases; great circles have no curvature at all. + // We round up when converting to ensure that we won't generate any tiny + // extra edges. + // +#if 0 + // Note that we take advantage of both positive and negative errors when + // approximating circles (i.e., vertices are not necessarily on the midline) + // and thus the relationships between circle_segments and error_fraction are + // e = (1 - cos(Pi/n)) / (1 + cos(Pi/n)) + // n = Pi / acos((1 - e) / (1 + e)) + double r = cos(M_PI / circle_segments); + set_error_fraction((1 - r) / (1 + r) + 1e-15); +#else + // When all vertices are on the midline, the relationships are + // e = 1 - cos(Pi/n) + // n = Pi / acos(1 - e) + set_error_fraction(1 - cos(M_PI / circle_segments) + 1e-15); +#endif +} + +S2BufferOperation::EndCapStyle S2BufferOperation::Options::end_cap_style() + const { + return end_cap_style_; +} + +void S2BufferOperation::Options::set_end_cap_style(EndCapStyle end_cap_style) { + end_cap_style_ = end_cap_style; +} + +S2BufferOperation::PolylineSide S2BufferOperation::Options::polyline_side() + const { + return polyline_side_; +} + +void S2BufferOperation::Options::set_polyline_side( + PolylineSide polyline_side) { + polyline_side_ = polyline_side; +} + +const S2Builder::SnapFunction& S2BufferOperation::Options::snap_function() + const { + return *snap_function_; +} + +void S2BufferOperation::Options::set_snap_function( + const S2Builder::SnapFunction& snap_function) { + snap_function_ = snap_function.Clone(); +} + +S2MemoryTracker* S2BufferOperation::Options::memory_tracker() const { + return memory_tracker_; +} + +void S2BufferOperation::Options::set_memory_tracker(S2MemoryTracker* tracker) { + memory_tracker_ = tracker; +} + +S2BufferOperation::S2BufferOperation() = default; + +S2BufferOperation::S2BufferOperation(unique_ptr result_layer, + const Options& options) { + Init(std::move(result_layer), options); +} + +void S2BufferOperation::Init(unique_ptr result_layer, + const Options& options) { + options_ = options; + ref_point_ = S2::Origin(); + ref_winding_ = 0; + have_input_start_ = false; + have_offset_start_ = false; + buffer_sign_ = sgn(options_.buffer_radius().radians()); + S1Angle abs_radius = abs(options_.buffer_radius()); + S1Angle requested_error = max(kMinRequestedError, + options_.error_fraction() * abs_radius); + S1Angle max_error = kMaxAbsoluteInterpolationError + requested_error; + if (abs_radius <= max_error) { + // If the requested radius is smaller than the maximum error, buffering + // could yield points on the wrong side of the original input boundary + // (e.g., shrinking geometry slightly rather than expanding it). Rather + // than taking that risk, we set the buffer radius to zero when this + // happens (which causes the original geometry to be returned). + abs_radius_ = S1ChordAngle::Zero(); + buffer_sign_ = 0; + } else if (abs_radius + max_error >= S1Angle::Radians(M_PI)) { + // If the permissible range of buffer angles includes Pi then we might + // as well take advantage of that. + abs_radius_ = S1ChordAngle::Straight(); + } else { + abs_radius_ = S1ChordAngle(abs_radius); + S1Angle vertex_step = GetMaxEdgeSpan(abs_radius, requested_error); + vertex_step_ = S1ChordAngle(vertex_step); + + // We take extra care to ensure that points are buffered as regular + // polygons. The step angle is adjusted up slightly to ensure that we + // don't wind up with a tiny extra edge. + point_step_ = S1ChordAngle::Radians( + 2 * M_PI / ceil(2 * M_PI / vertex_step.radians()) + 1e-15); + + // Edges are buffered only if the buffer radius (including permissible + // error) is less than 90 degrees. + S1Angle edge_radius = S1Angle::Radians(M_PI_2) - abs_radius; + if (edge_radius > max_error) { + edge_step_ = S1ChordAngle(GetMaxEdgeSpan(edge_radius, requested_error)); + } + } + + // The buffered output should include degeneracies (i.e., isolated points + // and/or sibling edge pairs) only if (1) the user specified a non-negative + // buffer radius, and (2) the adjusted buffer radius is zero. The only + // purpose of keeping degeneracies is to allow points/polylines in the input + // geometry to be converted back to points/polylines in the output if the + // client so desires. + S2WindingOperation::Options winding_options{options.snap_function()}; + winding_options.set_include_degeneracies( + buffer_sign_ == 0 && options_.buffer_radius() >= S1Angle::Zero()); + winding_options.set_memory_tracker(options.memory_tracker()); + op_.Init(std::move(result_layer), winding_options); + tracker_.Init(options.memory_tracker()); +} + +const S2BufferOperation::Options& S2BufferOperation::options() const { + return options_; +} + +S1Angle S2BufferOperation::GetMaxEdgeSpan(S1Angle radius, + S1Angle requested_error) const { + // If the allowable radius range spans Pi/2 then we can use edges as long as + // we like, however we always use at least 3 edges to approximate a circle. + S1Angle step = S1Angle::Radians(2 * M_PI / 3 + 1e-15); + S1Angle min_radius = radius - requested_error; + S2_DCHECK_GE(min_radius, S1Angle::Zero()); + if (radius.radians() < M_PI_2) { + step = min(step, S1Angle::Radians(2 * acos(tan(min_radius) / tan(radius)))); + } else if (min_radius.radians() > M_PI_2) { + step = min(step, S1Angle::Radians(2 * acos(tan(radius) / tan(min_radius)))); + } + return step; +} + +// The sweep edge AB (see introduction) consists of one point on the input +// boundary (A) and one point on the offset curve (B). This function advances +// the sweep edge by moving its first vertex A to "new_a" and updating the +// winding number of the reference point if necessary. +void S2BufferOperation::SetInputVertex(const S2Point& new_a) { + if (have_input_start_) { + S2_DCHECK(have_offset_start_); + UpdateRefWinding(sweep_a_, sweep_b_, new_a); + } else { + input_start_ = new_a; + have_input_start_ = true; + } + sweep_a_ = new_a; +} + +// Adds the point "new_b" to the offset path. Also advances the sweep edge AB +// by moving its second vertex B to "new_b" and updating the winding number of +// the reference point if necessary (see introduction). +void S2BufferOperation::AddOffsetVertex(const S2Point& new_b) { + if (!tracker_.AddSpace(&path_, 1)) return; + path_.push_back(new_b); + if (have_offset_start_) { + S2_DCHECK(have_input_start_); + UpdateRefWinding(sweep_a_, sweep_b_, new_b); + } else { + offset_start_ = new_b; + have_offset_start_ = true; + } + sweep_b_ = new_b; +} + +// Finishes buffering the current loop by advancing the sweep edge back to its +// starting location, updating the winding number of the reference point if +// necessary. +void S2BufferOperation::CloseBufferRegion() { + if (have_offset_start_ && have_input_start_) { + UpdateRefWinding(sweep_a_, sweep_b_, input_start_); + UpdateRefWinding(input_start_, sweep_b_, offset_start_); + } +} + +// Outputs the current buffered path (which is assumed to be a loop), and +// resets the state to prepare for buffering a new loop. +void S2BufferOperation::OutputPath() { + op_.AddLoop(path_); + path_.clear(); // Does not change capacity. + have_input_start_ = false; + have_offset_start_ = false; +} + +// Given a triangle ABC that has just been covered by the sweep edge AB, +// updates the winding number of the reference point if necessary. +void S2BufferOperation::UpdateRefWinding( + const S2Point& a, const S2Point& b, const S2Point& c) { + // TODO(ericv): This code could be made much faster by maintaining a + // bounding plane that separates the current sweep edge from the reference + // point. Whenever the sweep_a_ or sweep_b_ is updated we would just need + // to check that the new vertex is still on the opposite side of the + // bounding plane (i.e., one dot product). If not, we test the current + // triangle using the code below and then compute a new bounding plane. + // + // Another optimization would be to choose the reference point to be 90 + // degrees away from the first input vertex, since then triangle tests would + // not be needed unless the input geometry spans more than 90 degrees. This + // would involve adding a new flag have_ref_point_ rather than always + // choosing the reference point to be S2::Origin(). + // + // According to profiling these optimizations are not currently worthwhile, + // but this is worth revisiting if and when other improvements are made. + int sign = s2pred::Sign(a, b, c); + if (sign == 0) return; + bool inside = S2::AngleContainsVertex(a, b, c) == (sign > 0); + S2EdgeCrosser crosser(&b, &ref_point_); + inside ^= crosser.EdgeOrVertexCrossing(&a, &b); + inside ^= crosser.EdgeOrVertexCrossing(&b, &c); + inside ^= crosser.EdgeOrVertexCrossing(&c, &a); + if (inside) ref_winding_ += sign; +} + +// Ensures that the output will be the full polygon. +void S2BufferOperation::AddFullPolygon() { + ref_winding_ += 1; +} + +void S2BufferOperation::AddPoint(const S2Point& point) { + // If buffer_radius < 0, points are discarded. + if (buffer_sign_ < 0) return; + + // Buffering by 180 degrees or more always yields the full polygon. + // (We don't need to worry about buffering by 180 degrees yielding + // a degenerate hole because error_fraction_ is always positive. + if (abs_radius_ >= S1ChordAngle::Straight()) { + return AddFullPolygon(); + } + + // If buffer_radius == 0, points are converted into degenerate loops. + if (buffer_sign_ == 0) { + if (!tracker_.AddSpace(&path_, 1)) return; + path_.push_back(point); + } else { + // Since S1ChordAngle can only represent angles between 0 and 180 degrees, + // we generate the circle in four 90 degree increments. + SetInputVertex(point); + S2Point start = S2::Ortho(point); + S1ChordAngle angle = S1ChordAngle::Zero(); + for (int quadrant = 0; quadrant < 4; ++quadrant) { + // Generate 90 degrees of the circular arc. Normalize "rotate_dir" at + // each iteration to avoid magnifying normalization errors in "point". + S2Point rotate_dir = point.CrossProd(start).Normalize(); + for (; angle < S1ChordAngle::Right(); angle += point_step_) { + S2Point dir = S2::GetPointOnRay(start, rotate_dir, angle); + AddOffsetVertex(S2::GetPointOnRay(point, dir, abs_radius_)); + } + angle -= S1ChordAngle::Right(); + start = rotate_dir; + } + CloseBufferRegion(); + } + OutputPath(); +} + +// Returns the edge normal for the given edge AB. The sign is chosen such +// that the normal is on the right of AB if buffer_sign_ > 0, and on the left +// of AB if buffer_sign_ < 0. +inline S2Point S2BufferOperation::GetEdgeAxis(const S2Point& a, + const S2Point& b) const { + S2_DCHECK_NE(buffer_sign_, 0); + return buffer_sign_ * S2::RobustCrossProd(b, a).Normalize(); +} + +// Adds a semi-open offset arc around vertex V. The arc proceeds CCW from +// "start" to "end" (both of which must be perpendicular to V). +void S2BufferOperation::AddVertexArc(const S2Point& v, const S2Point& start, + const S2Point& end) { + // Make sure that we output at least one point even when span == 0. + S2Point rotate_dir = buffer_sign_ * v.CrossProd(start).Normalize(); + S1ChordAngle angle, span(start, end); + do { + S2Point dir = S2::GetPointOnRay(start, rotate_dir, angle); + AddOffsetVertex(S2::GetPointOnRay(v, dir, abs_radius_)); + } while ((angle += vertex_step_) < span); +} + +// Closes the semi-open arc generated by AddVertexArc(). +void S2BufferOperation::CloseVertexArc(const S2Point& v, const S2Point& end) { + AddOffsetVertex(S2::GetPointOnRay(v, end, abs_radius_)); +} + +// Adds a semi-open offset arc for the given edge AB. +void S2BufferOperation::AddEdgeArc(const S2Point& a, const S2Point& b) { + S2Point ab_axis = GetEdgeAxis(a, b); + if (edge_step_ == S1ChordAngle::Zero()) { + // If the buffer radius is more than 90 degrees, edges do not contribute to + // the buffered boundary. Instead we force the offset path to pass + // through a vertex located at the edge normal. This is similar to the + // case of concave vertices (below) where it is necessary to route the + // offset path through the concave vertex to ensure that the winding + // numbers in all output regions have the correct sign. + AddOffsetVertex(ab_axis); + } else { + // Make sure that we output at least one point even when span == 0. + S2Point rotate_dir = buffer_sign_ * a.CrossProd(ab_axis).Normalize(); + S1ChordAngle angle, span(a, b); + do { + S2Point p = S2::GetPointOnRay(a, rotate_dir, angle); + AddOffsetVertex(S2::GetPointOnRay(p, ab_axis, abs_radius_)); + } while ((angle += edge_step_) < span); + } + SetInputVertex(b); +} + +// Closes the semi-open arc generated by AddEdgeArc(). +void S2BufferOperation::CloseEdgeArc(const S2Point& a, const S2Point& b) { + if (edge_step_ != S1ChordAngle::Zero()) { + AddOffsetVertex(S2::GetPointOnRay(b, GetEdgeAxis(a, b), abs_radius_)); + } +} + +// Buffers the edge AB and the vertex B. (The vertex C is used to determine +// the range of angles that should be buffered at B.) +// +// TODO(ericv): Let A* denote the possible offset points of A with respect to +// the edge AB for buffer radii in the range specified by "radius" and +// "error_fraction". Rather than requiring that the path so far terminates at +// a point in A*, as you might expect, instead we only require that the path +// terminates at a point X such that for any point Y in A*, the edge XY does +// not leave the valid buffer zone of the previous edge and vertex. +void S2BufferOperation::BufferEdgeAndVertex(const S2Point& a, const S2Point& b, + const S2Point& c) { + S2_DCHECK_NE(a, b); + S2_DCHECK_NE(b, c); + S2_DCHECK_NE(buffer_sign_, 0); + if (!tracker_.ok()) return; + + // For left (convex) turns we need to add an offset arc. For right + // (concave) turns we connect the end of the current offset path to the + // vertex itself and then to the start of the offset path for the next edge. + // Note that A == C is considered to represent a convex (left) turn. + AddEdgeArc(a, b); + if (buffer_sign_ * s2pred::Sign(a, b, c) >= 0) { + // The boundary makes a convex turn. If there is no following edge arc + // then we need to generate a closed vertex arc. + S2Point start = GetEdgeAxis(a, b); + S2Point end = GetEdgeAxis(b, c); + AddVertexArc(b, start, end); + if (edge_step_ == S1ChordAngle::Zero()) CloseVertexArc(b, end); + } else { + // The boundary makes a concave turn. It is tempting to simply connect + // the end of the current offset path to the start of the offset path for + // the next edge, however this can create output regions where the winding + // number is incorrect. A solution that always works is to terminate the + // current offset path and start a new one by connecting the two offset + // paths through the input vertex whenever it is concave. We first need + // to close the previous semi-open edge arc if necessary. + CloseEdgeArc(a, b); + AddOffsetVertex(b); // Connect through the input vertex. + } +} + +// Given a polyline that starts with the edge AB, adds an end cap (as +// specified by end_cap_style() and polyline_side()) for the vertex A. +void S2BufferOperation::AddStartCap(const S2Point& a, const S2Point& b) { + S2Point axis = GetEdgeAxis(a, b); + if (options_.end_cap_style() == EndCapStyle::FLAT) { + // One-sided flat end caps require no additional vertices since the + // "offset curve" for the opposite side is simply the reversed polyline. + if (options_.polyline_side() == PolylineSide::BOTH) { + AddOffsetVertex(S2::GetPointOnRay(a, -axis, abs_radius_)); + } + } else { + S2_DCHECK(options_.end_cap_style() == EndCapStyle::ROUND); + if (options_.polyline_side() == PolylineSide::BOTH) { + // The end cap consists of a semicircle. + AddVertexArc(a, -axis, axis); + } else { + // The end cap consists of a quarter circle. Note that for + // PolylineSide::LEFT, the polyline direction has been reversed. + AddVertexArc(a, axis.CrossProd(a).Normalize(), axis); + } + } +} + +// Given a polyline that ends with the edge AB, adds an end cap (as specified +// by end_cap_style() and polyline_side()) for the vertex B. +void S2BufferOperation::AddEndCap(const S2Point& a, const S2Point& b) { + S2Point axis = GetEdgeAxis(a, b); + if (options_.end_cap_style() == EndCapStyle::FLAT) { + CloseEdgeArc(a, b); // Close the previous semi-open edge arc if necessary. + } else { + S2_DCHECK(options_.end_cap_style() == EndCapStyle::ROUND); + if (options_.polyline_side() == PolylineSide::BOTH) { + // The end cap consists of a semicircle. + AddVertexArc(b, axis, -axis); + } else { + // The end cap consists of a quarter circle. We close the arc since it + // will be followed by the reversed polyline vertices. Note that for + // PolylineSide::LEFT, the polyline direction has been reversed. + S2Point end = b.CrossProd(axis).Normalize(); + AddVertexArc(b, axis, end); + CloseVertexArc(b, end); + } + } +} + +// Helper function that buffers the given loop. +void S2BufferOperation::BufferLoop(S2PointLoopSpan loop) { + // Empty loops always yield an empty path. + if (loop.empty() || !tracker_.ok()) return; + + // Loops with one degenerate edge are treated as points. + if (loop.size() == 1) return AddPoint(loop[0]); + + // Buffering by 180 degrees or more always yields the full polygon. + // Buffering by -180 degrees or more always yields the empty polygon. + if (abs_radius_ >= S1ChordAngle::Straight()) { + if (buffer_sign_ > 0) AddFullPolygon(); + return; + } + + // If buffer_radius == 0, the loop is passed through unchanged. + if (buffer_sign_ == 0) { + if (!tracker_.AddSpace(&path_, loop.size())) return; + path_.assign(loop.begin(), loop.end()); + } else { + SetInputVertex(loop[0]); + for (size_t i = 0; i < loop.size(); ++i) { + BufferEdgeAndVertex(loop[i], loop[i + 1], loop[i + 2]); + } + CloseBufferRegion(); + } + OutputPath(); +} + +void S2BufferOperation::AddPolyline(S2PointSpan polyline) { + // Left-sided buffering is supported by reversing the polyline and then + // buffering on the right. + vector reversed; + if (options_.polyline_side() == PolylineSide::LEFT) { + reversed.reserve(polyline.size()); + std::reverse_copy(polyline.begin(), polyline.end(), + std::back_inserter(reversed)); + polyline = reversed; + } + + // If buffer_radius < 0, polylines are discarded. + if (buffer_sign_ < 0 || !tracker_.ok()) return; + + // Polylines with 0 or 1 vertices are defined to have no edges. + int n = polyline.size(); + if (n <= 1) return; + + // Polylines with one degenerate edge are treated as points. + if (n == 2 && polyline[0] == polyline[1]) { + return AddPoint(polyline[0]); + } + + // Buffering by 180 degrees or more always yields the full polygon. + if (abs_radius_ >= S1ChordAngle::Straight()) { + return AddFullPolygon(); + } + + // If buffer_radius == 0, polylines are converted into degenerate loops. + if (buffer_sign_ == 0) { + if (!tracker_.AddSpace(&path_, 2 * (n - 1))) return; + path_.assign(polyline.begin(), polyline.end() - 1); + path_.insert(path_.end(), polyline.rbegin(), polyline.rend() - 1); + } else { + // Otherwise we buffer each side of the polyline separately. + SetInputVertex(polyline[0]); + AddStartCap(polyline[0], polyline[1]); + for (int i = 0; i < n - 2; ++i) { + BufferEdgeAndVertex(polyline[i], polyline[i + 1], polyline[i + 2]); + } + AddEdgeArc(polyline[n - 2], polyline[n - 1]); + AddEndCap(polyline[n - 2], polyline[n - 1]); + + if (options_.polyline_side() == PolylineSide::BOTH) { + for (int i = n - 3; i >= 0; --i) { + BufferEdgeAndVertex(polyline[i + 2], polyline[i + 1], polyline[i]); + } + AddEdgeArc(polyline[1], polyline[0]); + CloseBufferRegion(); + } else { + // The other side of the polyline is not buffered. Note that for + // PolylineSide::LEFT, the polyline direction has been reversed. + if (!tracker_.AddSpace(&path_, n)) return; + path_.insert(path_.end(), polyline.rbegin(), polyline.rend()); + // Don't call CloseBufferRegion() since the path has already been closed. + } + } + OutputPath(); +} + +void S2BufferOperation::AddLoop(S2PointLoopSpan loop) { + if (loop.empty()) return; + BufferLoop(loop); + + // The vertex copying below could be avoided by adding a version of + // S2LaxLoopShape that doesn't own its vertices. + if (!tracker_.ok()) return; + ref_winding_ += s2shapeutil::ContainsBruteForce(S2LaxLoopShape(loop), + ref_point_); + num_polygon_layers_ += 1; +} + +void S2BufferOperation::BufferShape(const S2Shape& shape) { + int dimension = shape.dimension(); + int num_chains = shape.num_chains(); + for (int c = 0; c < num_chains; ++c) { + S2Shape::Chain chain = shape.chain(c); + if (chain.length == 0) continue; + if (dimension == 0) { + AddPoint(shape.edge(c).v0); + } else { + S2::GetChainVertices(shape, c, &tmp_vertices_); + if (dimension == 1) { + AddPolyline(S2PointSpan(tmp_vertices_)); + } else { + BufferLoop(S2PointLoopSpan(tmp_vertices_)); + } + } + } +} + +void S2BufferOperation::AddShape(const S2Shape& shape) { + BufferShape(shape); + ref_winding_ += s2shapeutil::ContainsBruteForce(shape, ref_point_); + num_polygon_layers_ += (shape.dimension() == 2); +} + +void S2BufferOperation::AddShapeIndex(const S2ShapeIndex& index) { + int max_dimension = -1; + for (const S2Shape* shape : index) { + if (shape == nullptr) continue; + max_dimension = max(max_dimension, shape->dimension()); + BufferShape(*shape); + } + ref_winding_ += MakeS2ContainsPointQuery(&index).Contains(ref_point_); + num_polygon_layers_ += (max_dimension == 2); +} + +bool S2BufferOperation::Build(S2Error* error) { + if (buffer_sign_ < 0 && num_polygon_layers_ > 1) { + error->Init(S2Error::FAILED_PRECONDITION, + "Negative buffer radius requires at most one polygon layer"); + return false; + } + return op_.Build(ref_point_, ref_winding_, + S2WindingOperation::WindingRule::POSITIVE, error); +} diff --git a/src/s2/s2buffer_operation.h b/src/s2/s2buffer_operation.h new file mode 100644 index 00000000..3ba4a3ff --- /dev/null +++ b/src/s2/s2buffer_operation.h @@ -0,0 +1,359 @@ +// Copyright 2020 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_S2BUFFER_OPERATION_H_ +#define S2_S2BUFFER_OPERATION_H_ + +#include +#include + +#include "s2/base/integral_types.h" +#include "s2/s1angle.h" +#include "s2/s1chord_angle.h" +#include "s2/s2builder.h" +#include "s2/s2error.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" +#include "s2/s2point_span.h" +#include "s2/s2shape.h" +#include "s2/s2shape_index.h" +#include "s2/s2winding_operation.h" + +// This class provides a way to expand an arbitrary collection of geometry by +// a fixed radius (an operation variously known as "buffering", "offsetting", +// or "Minkowski sum with a disc"). The output consists of a polygon +// (possibly with multiple shells) that contains all points within the given +// radius of the original geometry. +// +// The radius can also be negative, in which case the geometry is contracted. +// This causes the boundaries of polygons to shrink or disappear, and removes +// all points and polylines. +// +// The input consists of a sequence of layers. Each layer may consist of any +// combination of points, polylines, and polygons, with the restriction that +// polygon interiors within each layer may not intersect any other geometry +// (including other polygon interiors). The output is the union of the +// buffered input layers. Note that only a single layer is allowed if the +// buffer radius is negative. +// +// This class may be used to compute polygon unions by setting the buffer +// radius to zero. The union is computed using a single snapping operation. +// +// Note that if you only want to compute an S2CellId covering of the buffered +// geometry, it is much faster to use S2ShapeIndexBufferedRegion instead. +// +// Keywords: buffer, buffering, expand, expanding, offset, offsetting, +// widen, contract, shrink, Minkowski sum +class S2BufferOperation { + public: + // For polylines, specifies whether the end caps should be round or flat. + // See Options::set_end_cap_style() below. + enum class EndCapStyle : uint8 { ROUND, FLAT }; + + // Specifies whether polylines should be buffered only on the left, only on + // the right, or on both sides. + enum class PolylineSide : uint8 { LEFT, RIGHT, BOTH }; + + class Options { + public: + Options(); + + // Convenience constructor that calls set_buffer_radius(). + explicit Options(S1Angle buffer_radius); + + // If positive, specifies that all points within the given radius of the + // input geometry should be added to the output. If negative, specifies + // that all points within the given radius of complement of the input + // geometry should be subtracted from the output. If the buffer radius + // is zero then the input geometry is passed through to the output layer + // after first converting points and polylines into degenerate loops. + // + // DEFAULT: S1Angle::Zero() + S1Angle buffer_radius() const; + void set_buffer_radius(S1Angle buffer_radius); + + // Specifies the allowable error when buffering, expressed as a fraction + // of buffer_radius(). The actual buffer distance will be in the range + // [(1-f) * r - C, (1 + f) * r + C] where "f" is the error fraction, "r" + // is the buffer radius, and "C" is S2BufferOperation::kAbsError. + // + // Be aware that the number of output edges increases proportionally to + // (1 / sqrt(error_fraction)), so setting a small value may increase the + // size of the output considerably. + // + // REQUIRES: error_fraction() >= kMinErrorFraction + // REQUIRES: error_fraction() <= 1.0 + // + // DEFAULT: 0.01 (i.e., maximum error of 1%) + static constexpr double kMinErrorFraction = 1e-6; + double error_fraction() const; + void set_error_fraction(double error_fraction); + + // Returns the maximum error in the buffered result for the current + // buffer_radius(), error_fraction(), and snap_function(). Note that the + // error due to buffering consists of both relative errors (those + // proportional to the buffer radius) and absolute errors. The maximum + // relative error is controlled by error_fraction(), while the maximum + // absolute error is about 10 nanometers on the Earth's surface and is + // defined internally. The error due to snapping is defined by the + // specified snap_function(). + const S1Angle max_error() const; + + // Alternatively, error_fraction() may be specified as the number of + // polyline segments used to approximate a planar circle. These two + // values are related according to the formula + // + // error_fraction = (1 - cos(theta)) / (1 + cos(theta)) + // ~= 0.25 * (theta ** 2) + // + // where (theta == Pi / circle_segments), i.e. error decreases + // quadratically with the number of circle segments. + // + // REQUIRES: circle_segments() >= 2.0 + // REQUIRES: circle_segments() <= kMaxCircleSegments + // (about 1570; corresponds to kMinErrorFraction) + // + // DEFAULT: about 15.76 (corresponding to error_fraction() default value) + static constexpr double kMaxCircleSegments = 1570.7968503979573; + double circle_segments() const; + void set_circle_segments(double circle_segments); + + // For polylines, specifies whether the end caps should be round or flat. + // + // Note that with flat end caps, there is no buffering beyond the polyline + // endpoints (unlike "square" end caps, which are not implemented). + // + // DEFAULT: EndCapStyle::ROUND + EndCapStyle end_cap_style() const; + void set_end_cap_style(EndCapStyle end_cap_style); + + // Specifies whether polylines should be buffered only on the left, only + // on the right, or on both sides. For one-sided buffering please note + // the following: + // + // - EndCapStyle::ROUND yields two quarter-circles, one at each end. + // + // - To buffer by a different radius on each side of the polyline, you + // can use two S2BufferOperations and compute their union. (Note that + // round end caps will yield two quarter-circles at each end of the + // polyline with different radii.) + // + // - Polylines consisting of a single degenerate edge are always buffered + // identically to points, i.e. this option has no effect. + // + // - When the polyline turns right by more than 90 degrees, buffering may + // or may not extend to the non-buffered side of the polyline. For + // example if ABC makes a 170 degree right turn at B, it is unspecified + // whether the buffering of AB extends across edge BC and vice versa. + // Similarly if ABCD represents two right turns of 90 degrees where AB + // and CD are separated by less than the buffer radius, it is + // unspecified whether buffering of AB extends across CD and vice versa. + // + // DEFAULT: PolylineSide::BOTH + PolylineSide polyline_side() const; + void set_polyline_side(PolylineSide polyline_side); + + // Specifies the function used for snap rounding the output during the + // call to Build(). Note that any errors due to snapping are in addition + // to those specified by error_fraction(). + // + // DEFAULT: s2builderutil::IdentitySnapFunction(S1Angle::Zero()) + const S2Builder::SnapFunction& snap_function() const; + void set_snap_function(const S2Builder::SnapFunction& snap_function); + + // Specifies that internal memory usage should be tracked using the given + // S2MemoryTracker. If a memory limit is specified and more more memory + // than this is required then an error will be returned. Example usage: + // + // S2MemoryTracker tracker; + // tracker.set_limit(500 << 20); // 500 MB + // S2BufferOperation::Options options; + // options.set_buffer_radius(S1Angle::Degrees(1e-5)); + // options.set_memory_tracker(&tracker); + // S2BufferOperation op{options}; + // ... + // S2Error error; + // if (!op.Build(&error)) { + // if (error.code() == S2Error::RESOURCE_EXHAUSTED) { + // S2_LOG(ERROR) << error; // Memory limit exceeded + // } + // } + // + // CAVEATS: + // + // - Memory allocated by the output S2Builder layer is not tracked. + // + // - While memory tracking is reasonably complete and accurate, it does + // not account for every last byte. It is intended only for the + // purpose of preventing clients from running out of memory. + // + // DEFAULT: nullptr (memory tracking disabled) + S2MemoryTracker* memory_tracker() const; + void set_memory_tracker(S2MemoryTracker* tracker); + + // Options may be assigned and copied. + Options(const Options& options); + Options& operator=(const Options& options); + + private: + S1Angle buffer_radius_ = S1Angle::Zero(); + // double error_fraction_ = 0.01; + double error_fraction_ = 0.02; + EndCapStyle end_cap_style_ = EndCapStyle::ROUND; + PolylineSide polyline_side_ = PolylineSide::BOTH; + std::unique_ptr snap_function_; + S2MemoryTracker* memory_tracker_ = nullptr; + }; + + // Default constructor; requires Init() to be called. + S2BufferOperation(); + +#ifndef SWIG + // Convenience constructor that calls Init(). + explicit S2BufferOperation(std::unique_ptr result_layer, + const Options& options = Options{}); +#endif + + // Starts a buffer operation that sends the output polygon to the given + // S2Builder layer. This method may be called more than once. + // + // Note that buffering always yields a polygon, even if the input includes + // polylines and points. If the buffer radius is zero, points and polylines + // will be converted into degenerate polygon loops; if the buffer radius is + // negative, points and polylines will be removed. + void Init(std::unique_ptr result_layer, + const Options& options = Options()); + + const Options& options() const; + + // Each call below represents a different input layer. Note that if the + // buffer radius is negative, then at most one input layer is allowed + // (ignoring any layers that contain only points and polylines). + + // Adds an input layer containing a single point. + void AddPoint(const S2Point& point); + + // Adds an input layer containing a polyline. Note the following: + // + // - Polylines with 0 or 1 vertices are considered to be empty. + // - A polyline with 2 identical vertices is equivalent to a point. + // - Polylines have end caps (see Options::end_cap_style). + // - One-sided polyline buffering is supported (see Options::polyline_side). + void AddPolyline(S2PointSpan polyline); + + // Adds an input layer containing a loop. Note the following: + // + // - A loop with no vertices is empty. + // - A loop with 1 vertex is equivalent to a point. + // - The interior of the loop is on its left. + // - Buffering a self-intersecting loop produces undefined results. + void AddLoop(S2PointLoopSpan loop); + + // Adds an input layer containing the given shape. Shapes are handled as + // points, polylines, or polygons according to the rules above. In addition + // note the following: + // + // - Polygons holes may be degenerate (e.g., consisting of a + // single vertex or entirely of sibling pairs such as ABCBDB). + // - Full polygons are supported. Note that since full polygons do + // not have a boundary, they are not affected by buffering. + void AddShape(const S2Shape& shape); + + // Adds an input layer containing all of the shapes in the given index. + // + // REQUIRES: The interiors of polygons must be disjoint from all other + // indexed geometry, including other polygon interiors. + // (S2BooleanOperation also requires this.) + void AddShapeIndex(const S2ShapeIndex& index); + + // Computes the union of the buffered input shapes and sends the output + // polygon to the S2Builder layer specified in the constructor. Returns + // true on success and otherwise sets "error" appropriately. + // + // Note that if the buffer radius is negative, only a single input layer is + // allowed (ignoring any layers that contain only points and polylines). + bool Build(S2Error* error); + + private: + S1Angle GetMaxEdgeSpan(S1Angle radius, S1Angle requested_error) const; + void SetInputVertex(const S2Point& new_a); + void AddOffsetVertex(const S2Point& new_b); + void CloseBufferRegion(); + void OutputPath(); + void UpdateRefWinding(const S2Point& a, const S2Point& b, const S2Point& c); + void AddFullPolygon(); + S2Point GetEdgeAxis(const S2Point& a, const S2Point& b) const; + void AddVertexArc(const S2Point& v, const S2Point& start, const S2Point& end); + void CloseVertexArc(const S2Point& v, const S2Point& end); + void AddEdgeArc(const S2Point& a, const S2Point& b); + void CloseEdgeArc(const S2Point& a, const S2Point& b); + void BufferEdgeAndVertex(const S2Point& a, const S2Point& b, + const S2Point& c); + void AddStartCap(const S2Point& a, const S2Point& b); + void AddEndCap(const S2Point& a, const S2Point& b); + void BufferLoop(S2PointLoopSpan loop); + void BufferShape(const S2Shape& shape); + + Options options_; + + // The number of layers containing two-dimension geometry that have been + // added so far. This is used to enforce the requirement that negative + // buffer radii allow only a single such layer. + int num_polygon_layers_ = 0; + + // Parameters for buffering vertices and edges. + int buffer_sign_; // The sign of buffer_radius (-1, 0, or +1). + S1ChordAngle abs_radius_; + S1ChordAngle vertex_step_, edge_step_; + + // We go to extra effort to ensure that points are transformed into regular + // polygons. (We don't do this for arcs in general because we would rather + // use the allowable error to reduce the complexity of the output rather + // than increase its symmetry.) + S1ChordAngle point_step_; + + // Contains the buffered loops that have been accumulated so far. + S2WindingOperation op_; + + // The current offset path. When each path is completed into a loop it is + // added to op_ (the S2WindingOperation). + std::vector path_; + + // As buffered loops are added we keep track of the winding number of a + // fixed reference point. This is used to derive the winding numbers of + // every region in the spherical partition induced by the buffered loops. + S2Point ref_point_; + + // The winding number associated with ref_point_. + int ref_winding_; + + // The endpoints of the current sweep edge. sweep_a_ is a vertex of the + // original geometry and sweep_b_ is a vertex of the current offset path. + S2Point sweep_a_, sweep_b_; + + // The starting vertices of the current input loop and offset curve. These + // are used to close the buffer region when a loop is completed. + S2Point input_start_, offset_start_; + bool have_input_start_, have_offset_start_; + + // Used internally as a temporary to avoid excessive memory allocation. + std::vector tmp_vertices_; + + S2MemoryTracker::Client tracker_; +}; + +#endif // S2_S2BUFFER_OPERATION_H_ diff --git a/src/s2/s2builder.cc b/src/s2/s2builder.cc index 7781e7bc..68aae5fc 100644 --- a/src/s2/s2builder.cc +++ b/src/s2/s2builder.cc @@ -1,4 +1,3 @@ -#include "cpp-compat.h" // Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -69,19 +68,26 @@ #include "s2/s2builder.h" +#include + #include #include #include #include #include -#include -#include +#include +#include #include +#include "absl/cleanup/cleanup.h" +#include "absl/container/btree_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + #include "s2/base/casts.h" +#include "s2/base/integral_types.h" #include "s2/base/logging.h" -#include "absl/memory/memory.h" -#include "s2/util/bits/bits.h" +#include "s2/base/log_severity.h" #include "s2/id_set_lexicon.h" #include "s2/mutable_s2shape_index.h" #include "s2/s1angle.h" @@ -89,23 +95,33 @@ #include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" #include "s2/s2builderutil_snap_functions.h" +#include "s2/s2cell_id.h" #include "s2/s2closest_edge_query.h" +#include "s2/s2closest_edge_query_base.h" #include "s2/s2closest_point_query.h" +#include "s2/s2closest_point_query_base.h" +#include "s2/s2crossing_edge_query.h" #include "s2/s2edge_crossings.h" #include "s2/s2edge_distances.h" #include "s2/s2error.h" #include "s2/s2loop.h" +#include "s2/s2point.h" #include "s2/s2point_index.h" -#include "s2/s2pointutil.h" +#include "s2/s2point_span.h" #include "s2/s2polygon.h" #include "s2/s2polyline.h" #include "s2/s2polyline_simplifier.h" #include "s2/s2predicates.h" +#include "s2/s2shape.h" +#include "s2/s2shapeutil_shape_edge.h" #include "s2/s2shapeutil_visit_crossing_edge_pairs.h" #include "s2/s2text_format.h" +#include "s2/util/bits/bits.h" +#include "s2/util/gtl/compact_array.h" -using absl::make_unique; +using absl::flat_hash_set; using gtl::compact_array; +using std::make_unique; using std::max; using std::pair; using std::unique_ptr; @@ -114,28 +130,6 @@ using std::vector; // Internal flag intended to be set from within a debugger. bool s2builder_verbose = false; -S1Angle S2Builder::SnapFunction::max_edge_deviation() const { - // We want max_edge_deviation() to be large enough compared to snap_radius() - // such that edge splitting is rare. - // - // Using spherical trigonometry, if the endpoints of an edge of length L - // move by at most a distance R, the center of the edge moves by at most - // asin(sin(R) / cos(L / 2)). Thus the (max_edge_deviation / snap_radius) - // ratio increases with both the snap radius R and the edge length L. - // - // We arbitrarily limit the edge deviation to be at most 10% more than the - // snap radius. With the maximum allowed snap radius of 70 degrees, this - // means that edges up to 30.6 degrees long are never split. For smaller - // snap radii, edges up to 49 degrees long are never split. (Edges of any - // length are not split unless their endpoints move far enough so that the - // actual edge deviation exceeds the limit; in practice, splitting is rare - // even with long edges.) Note that it is always possible to split edges - // when max_edge_deviation() is exceeded; see MaybeAddExtraSites(). - S2_DCHECK_LE(snap_radius(), kMaxSnapRadius()); - const double kMaxEdgeDeviationRatio = 1.1; - return kMaxEdgeDeviationRatio * snap_radius(); -} - S2Builder::Options::Options() : snap_function_( make_unique(S1Angle::Zero())) { @@ -146,20 +140,50 @@ S2Builder::Options::Options(const SnapFunction& snap_function) } S2Builder::Options::Options(const Options& options) - : snap_function_(options.snap_function_->Clone()), - split_crossing_edges_(options.split_crossing_edges_), - simplify_edge_chains_(options.simplify_edge_chains_), - idempotent_(options.idempotent_) { + : snap_function_(options.snap_function_->Clone()), + split_crossing_edges_(options.split_crossing_edges_), + intersection_tolerance_(options.intersection_tolerance_), + simplify_edge_chains_(options.simplify_edge_chains_), + idempotent_(options.idempotent_), + memory_tracker_(options.memory_tracker_) { } S2Builder::Options& S2Builder::Options::operator=(const Options& options) { snap_function_ = options.snap_function_->Clone(); split_crossing_edges_ = options.split_crossing_edges_; + intersection_tolerance_ = options.intersection_tolerance_; simplify_edge_chains_ = options.simplify_edge_chains_; idempotent_ = options.idempotent_; + memory_tracker_ = options.memory_tracker_; return *this; } +S1Angle S2Builder::Options::edge_snap_radius() const { + return snap_function().snap_radius() + intersection_tolerance(); +} + +S1Angle S2Builder::Options::max_edge_deviation() const { + // We want max_edge_deviation() to be large enough compared to snap_radius() + // such that edge splitting is rare. + // + // Using spherical trigonometry, if the endpoints of an edge of length L + // move by at most a distance R, the center of the edge moves by at most + // asin(sin(R) / cos(L / 2)). Thus the (max_edge_deviation / snap_radius) + // ratio increases with both the snap radius R and the edge length L. + // + // We arbitrarily limit the edge deviation to be at most 10% more than the + // snap radius. With the maximum allowed snap radius of 70 degrees, this + // means that edges up to 30.6 degrees long are never split. For smaller + // snap radii, edges up to 49 degrees long are never split. (Edges of any + // length are not split unless their endpoints move far enough so that the + // actual edge deviation exceeds the limit; in practice, splitting is rare + // even with long edges.) Note that it is always possible to split edges + // when max_edge_deviation() is exceeded; see MaybeAddExtraSites(). + S2_DCHECK_LE(snap_function().snap_radius(), SnapFunction::kMaxSnapRadius()); + const double kMaxEdgeDeviationRatio = 1.1; + return kMaxEdgeDeviationRatio * edge_snap_radius(); +} + bool operator==(const S2Builder::GraphOptions& x, const S2Builder::GraphOptions& y) { return (x.edge_type() == y.edge_type() && @@ -184,8 +208,7 @@ static S1ChordAngle AddPointToEdgeError(S1ChordAngle ca) { return ca.PlusError(S2::GetUpdateMinDistanceMaxError(ca)); } -S2Builder::S2Builder() { -} +S2Builder::S2Builder() = default; S2Builder::S2Builder(const Options& options) { Init(options); @@ -201,49 +224,67 @@ void S2Builder::Init(const Options& options) { // radius" used when evaluating exact predicates (s2predicates.h). site_snap_radius_ca_ = S1ChordAngle(snap_radius); - // When split_crossing_edges() is true, we need to use a larger snap radius - // for edges than for vertices to ensure that both edges are snapped to the - // edge intersection location. This is because the computed intersection - // point is not exact; it may be up to kIntersectionError away from its true - // position. The computed intersection point might then be snapped to some - // other vertex up to snap_radius away. So to ensure that both edges are - // snapped to a common vertex, we need to increase the snap radius for edges - // to at least the sum of these two values (calculated conservatively). - S1Angle edge_snap_radius = snap_radius; - if (!options.split_crossing_edges()) { - edge_snap_radius_ca_ = site_snap_radius_ca_; - } else { - edge_snap_radius += S2::kIntersectionError; - edge_snap_radius_ca_ = RoundUp(edge_snap_radius); - } + // When intersection_tolerance() is non-zero we need to use a larger snap + // radius for edges than for vertices to ensure that both edges are snapped + // to the edge intersection location. This is because the computed + // intersection point is not exact; it may be up to intersection_tolerance() + // away from its true position. The computed intersection point might then + // be snapped to some other vertex up to snap_radius away. So to ensure + // that both edges are snapped to a common vertex, we need to increase the + // snap radius for edges to at least the sum of these two values (calculated + // conservatively). + S1Angle edge_snap_radius = options.edge_snap_radius(); + edge_snap_radius_ca_ = RoundUp(edge_snap_radius); snapping_requested_ = (edge_snap_radius > S1Angle::Zero()); // Compute the maximum distance that a vertex can be separated from an // edge while still affecting how that edge is snapped. - max_edge_deviation_ = snap_function.max_edge_deviation(); + max_edge_deviation_ = options.max_edge_deviation(); edge_site_query_radius_ca_ = S1ChordAngle( max_edge_deviation_ + snap_function.min_edge_vertex_separation()); // Compute the maximum edge length such that even if both endpoints move by - // the maximum distance allowed (i.e., snap_radius), the center of the edge - // will still move by less than max_edge_deviation(). This saves us a lot - // of work since then we don't need to check the actual deviation. - min_edge_length_to_split_ca_ = S1ChordAngle::Radians( - 2 * acos(sin(snap_radius) / sin(max_edge_deviation_))); - - // If the condition below is violated, then AddExtraSites() needs to be - // modified to check that snapped edges pass on the same side of each "site - // to avoid" as the input edge. Currently it doesn't need to do this - // because the condition below guarantees that if the snapped edge passes on - // the wrong side of the site then it is also too close, which will cause a - // separation site to be added. + // the maximum distance allowed (i.e., edge_snap_radius), the center of the + // edge will still move by less than max_edge_deviation(). This saves us a + // lot of work since then we don't need to check the actual deviation. + if (!snapping_requested_) { + min_edge_length_to_split_ca_ = S1ChordAngle::Infinity(); + } else { + // This value varies between 30 and 50 degrees depending on the snap radius. + min_edge_length_to_split_ca_ = S1ChordAngle::Radians( + 2 * acos(sin(edge_snap_radius) / sin(max_edge_deviation_))); + } + + // In rare cases we may need to explicitly check that the input topology is + // preserved, i.e. that edges do not cross vertices when snapped. This is + // only necessary (1) for vertices added using ForceVertex(), and (2) when the + // snap radius is smaller than intersection_tolerance() (which is typically + // either zero or S2::kIntersectionError, about 9e-16 radians). This + // condition arises because when a geodesic edge is snapped, the edge center + // can move further than its endpoints. This can cause an edge to pass on the + // wrong side of an input vertex. (Note that this could not happen in a + // planar version of this algorithm.) Usually we don't need to consider this + // possibility explicitly, because if the snapped edge passes on the wrong + // side of a vertex then it is also closer than min_edge_vertex_separation() + // to that vertex, which will cause a separation site to be added. + // + // If the condition below is true then we need to check all sites (i.e., + // snapped input vertices) for topology changes. However this is almost never + // the case because + // + // max_edge_deviation() == 1.1 * edge_snap_radius() + // and min_edge_vertex_separation() >= 0.219 * snap_radius() // - // Currently max_edge_deviation() is at most 1.1 * snap_radius(), whereas - // min_edge_vertex_separation() is at least 0.219 * snap_radius() (based on - // S2CellIdSnapFunction, which is currently the worst case). - S2_DCHECK_LE(snap_function.max_edge_deviation(), - snap_function.snap_radius() + - snap_function.min_edge_vertex_separation()); + // for all currently implemented snap functions. The condition below is + // only true when intersection_tolerance() is non-zero (which causes + // edge_snap_radius() to exceed snap_radius() by S2::kIntersectionError) and + // snap_radius() is very small (at most S2::kIntersectionError / 1.19). + check_all_site_crossings_ = (options.max_edge_deviation() > + options.edge_snap_radius() + + snap_function.min_edge_vertex_separation()); + if (options.intersection_tolerance() <= S1Angle::Zero()) { + S2_DCHECK(!check_all_site_crossings_); + } // To implement idempotency, we check whether the input geometry could // possibly be the output of a previous S2Builder invocation. This involves @@ -272,7 +313,7 @@ void S2Builder::Init(const Options& options) { // error in the calculation to compare this distance against the bound. double d = sin(edge_snap_radius); edge_snap_radius_sin2_ = d * d; - edge_snap_radius_sin2_ += ((9.5 * d + 2.5 + 2 * sqrt(3.0)) * d + + edge_snap_radius_sin2_ += ((9.5 * d + 2.5 + 2 * sqrt(3)) * d + 9 * DBL_EPSILON) * DBL_EPSILON; // Initialize the current label set. @@ -284,6 +325,8 @@ void S2Builder::Init(const Options& options) { // idempotency, and can also save work. If we discover any reason that the // input geometry needs to be modified, snapping_needed_ is set to true. snapping_needed_ = false; + + tracker_.Init(options.memory_tracker()); } void S2Builder::clear_labels() { @@ -341,20 +384,32 @@ S2Builder::InputVertexId S2Builder::AddVertex(const S2Point& v) { // vertices once they have all been added, remove duplicates, and update the // edges. if (input_vertices_.empty() || v != input_vertices_.back()) { + if (!tracker_.AddSpace(&input_vertices_, 1)) return -1; input_vertices_.push_back(v); } return input_vertices_.size() - 1; } +void S2Builder::AddIntersection(const S2Point& vertex) { + // It is an error to call this method without first setting + // intersection_tolerance() to a non-zero value. + S2_DCHECK_GT(options_.intersection_tolerance(), S1Angle::Zero()); + + // Calling this method also overrides the idempotent() option. + snapping_needed_ = true; + + AddVertex(vertex); +} + void S2Builder::AddEdge(const S2Point& v0, const S2Point& v1) { S2_DCHECK(!layers_.empty()) << "Call StartLayer before adding any edges"; - if (v0 == v1 && (layer_options_.back().degenerate_edges() == GraphOptions::DegenerateEdges::DISCARD)) { return; } InputVertexId j0 = AddVertex(v0); InputVertexId j1 = AddVertex(v1); + if (!tracker_.AddSpace(&input_edges_, 1)) return; input_edges_.push_back(InputEdge(j0, j1)); // If there are any labels, then attach them to this input edge. @@ -371,6 +426,12 @@ void S2Builder::AddEdge(const S2Point& v0, const S2Point& v1) { } } +void S2Builder::AddPolyline(S2PointSpan polyline) { + for (size_t i = 1; i < polyline.size(); ++i) { + AddEdge(polyline[i - 1], polyline[i]); + } +} + void S2Builder::AddPolyline(const S2Polyline& polyline) { const int n = polyline.num_vertices(); for (int i = 1; i < n; ++i) { @@ -378,6 +439,12 @@ void S2Builder::AddPolyline(const S2Polyline& polyline) { } } +void S2Builder::AddLoop(S2PointLoopSpan loop) { + for (size_t i = 0; i < loop.size(); ++i) { + AddEdge(loop[i], loop[i + 1]); + } +} + void S2Builder::AddLoop(const S2Loop& loop) { // Ignore loops that do not have a boundary. if (loop.is_empty_or_full()) return; @@ -414,6 +481,7 @@ void S2Builder::AddIsFullPolygonPredicate(IsFullPolygonPredicate predicate) { } void S2Builder::ForceVertex(const S2Point& vertex) { + if (!tracker_.AddSpace(&sites_, 1)) return; sites_.push_back(vertex); } @@ -425,8 +493,7 @@ class VertexIdEdgeVectorShape final : public S2Shape { // Requires that "edges" is constant for the lifetime of this object. VertexIdEdgeVectorShape(const vector>& edges, const vector& vertices) - : edges_(edges), vertices_(vertices) { - } + : edges_(edges), vertices_(vertices) {} const S2Point& vertex0(int e) const { return vertex(edges_[e].first); } const S2Point& vertex1(int e) const { return vertex(edges_[e].second); } @@ -457,12 +524,12 @@ class VertexIdEdgeVectorShape final : public S2Shape { bool S2Builder::Build(S2Error* error) { // S2_CHECK rather than S2_DCHECK because this is friendlier than crashing on the - // "error->ok()" call below. It would be easy to allow (error == nullptr) + // "error->Clear()" call below. It would be easy to allow (error == nullptr) // by declaring a local "tmp_error", but it seems better to make clients // think about error handling. S2_CHECK(error != nullptr); - error->Clear(); error_ = error; + error_->Clear(); // Mark the end of the last layer. layer_begins_.push_back(input_edges_.size()); @@ -474,10 +541,12 @@ bool S2Builder::Build(S2Error* error) { ChooseSites(); BuildLayers(); Reset(); - return error->ok(); + if (!tracker_.ok()) *error_ = tracker_.error(); + return error_->ok(); } void S2Builder::Reset() { + // Note that these calls do not change vector capacities. input_vertices_.clear(); input_edges_.clear(); layers_.clear(); @@ -494,36 +563,48 @@ void S2Builder::Reset() { } void S2Builder::ChooseSites() { - if (input_vertices_.empty()) return; + if (!tracker_.ok() || input_vertices_.empty()) return; + // Note that although we always create an S2ShapeIndex, often it is not + // actually built (because this happens lazily). Therefore we only test + // its memory usage at the places where it is used. MutableS2ShapeIndex input_edge_index; - input_edge_index.Add(make_unique( - input_edges_, input_vertices_)); + input_edge_index.set_memory_tracker(tracker_.tracker()); + input_edge_index.Add(make_unique(input_edges_, + input_vertices_)); if (options_.split_crossing_edges()) { AddEdgeCrossings(input_edge_index); } + if (snapping_requested_) { S2PointIndex site_index; + auto _ = absl::MakeCleanup([&]() { tracker_.DoneSiteIndex(site_index); }); AddForcedSites(&site_index); ChooseInitialSites(&site_index); + if (!tracker_.FixSiteIndexTally(site_index)) return; CollectSiteEdges(site_index); } if (snapping_needed_) { AddExtraSites(input_edge_index); } else { - CopyInputEdges(); + ChooseAllVerticesAsSites(); } } -void S2Builder::CopyInputEdges() { - // Sort the input vertices, discard duplicates, and update the input edges - // to refer to the pruned vertex list. (We sort in the same order used by - // ChooseInitialSites() to avoid inconsistencies in tests.) +void S2Builder::ChooseAllVerticesAsSites() { + // Sort the input vertices, discard duplicates, and use the result as the + // list of sites. (We sort in the same order used by ChooseInitialSites() + // to avoid inconsistencies in tests.) We also copy the result back to + // input_vertices_ and update the input edges to use the new vertex + // numbering (so that InputVertexId == SiteId). This simplifies the + // implementation of SnapEdge() for this case. + sites_.clear(); + if (!tracker_.AddSpaceExact(&sites_, input_vertices_.size())) return; + const int64 kTempPerVertex = sizeof(InputVertexKey) + sizeof(InputVertexId); + if (!tracker_.TallyTemp(input_vertices_.size() * kTempPerVertex)) return; vector sorted = SortInputVertices(); vector vmap(input_vertices_.size()); - sites_.clear(); - sites_.reserve(input_vertices_.size()); - for (int in = 0; in < sorted.size(); ) { + for (size_t in = 0; in < sorted.size();) { const S2Point& site = input_vertices_[sorted[in].second]; vmap[sorted[in].second] = sites_.size(); while (++in < sorted.size() && input_vertices_[sorted[in].second] == site) { @@ -531,7 +612,7 @@ void S2Builder::CopyInputEdges() { } sites_.push_back(site); } - input_vertices_ = sites_; + input_vertices_ = sites_; // Does not change allocated size. for (InputEdge& e : input_edges_) { e.first = vmap[e.first]; e.second = vmap[e.second]; @@ -593,7 +674,8 @@ vector S2Builder::SortInputVertices() { vector keys; keys.reserve(input_vertices_.size()); - for (InputVertexId i = 0; i < input_vertices_.size(); ++i) { + for (InputVertexId i = 0; static_cast(i) < input_vertices_.size(); + ++i) { keys.push_back(InputVertexKey(S2CellId(input_vertices_[i]), i)); } std::sort(keys.begin(), keys.end(), @@ -609,21 +691,28 @@ vector S2Builder::SortInputVertices() { // points to input_vertices_. (The intersection points will be snapped and // merged with the other vertices during site selection.) void S2Builder::AddEdgeCrossings(const MutableS2ShapeIndex& input_edge_index) { + input_edge_index.ForceBuild(); + if (!tracker_.ok()) return; + // We need to build a list of intersections and add them afterwards so that // we don't reallocate vertices_ during the VisitCrossings() call. vector new_vertices; + auto _ = absl::MakeCleanup([&]() { tracker_.Untally(new_vertices); }); s2shapeutil::VisitCrossingEdgePairs( input_edge_index, s2shapeutil::CrossingType::INTERIOR, - [&new_vertices](const s2shapeutil::ShapeEdge& a, - const s2shapeutil::ShapeEdge& b, bool) { + [this, &new_vertices](const s2shapeutil::ShapeEdge& a, + const s2shapeutil::ShapeEdge& b, bool) { + if (!tracker_.AddSpace(&new_vertices, 1)) return false; new_vertices.push_back( S2::GetIntersection(a.v0(), a.v1(), b.v0(), b.v1())); - return true; // Continue visiting. + return true; }); - if (!new_vertices.empty()) { - snapping_needed_ = true; - for (const auto& vertex : new_vertices) AddVertex(vertex); - } + if (new_vertices.empty()) return; + + snapping_needed_ = true; + if (!tracker_.AddSpaceExact(&input_vertices_, new_vertices.size())) return; + input_vertices_.insert(input_vertices_.end(), + new_vertices.begin(), new_vertices.end()); } void S2Builder::AddForcedSites(S2PointIndex* site_index) { @@ -631,14 +720,15 @@ void S2Builder::AddForcedSites(S2PointIndex* site_index) { std::sort(sites_.begin(), sites_.end()); sites_.erase(std::unique(sites_.begin(), sites_.end()), sites_.end()); // Add the forced sites to the index. - for (SiteId id = 0; id < sites_.size(); ++id) { + for (SiteId id = 0; static_cast(id) < sites_.size(); ++id) { + if (!tracker_.TallyIndexedSite()) return; site_index->Add(sites_[id], id); } num_forced_sites_ = sites_.size(); } void S2Builder::ChooseInitialSites(S2PointIndex* site_index) { - // Find all points whose distance is <= min_site_separation_ca_. + // Prepare to find all points whose distance is <= min_site_separation_ca_. S2ClosestPointQueryOptions options; options.set_conservative_max_distance(min_site_separation_ca_); S2ClosestPointQuery site_query(site_index, options); @@ -658,32 +748,43 @@ void S2Builder::ChooseInitialSites(S2PointIndex* 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. - for (const InputVertexKey& key : SortInputVertices()) { + // + // Track the memory used by SortInputVertices() before calling it. + if (!tracker_.Tally(input_vertices_.size() * sizeof(InputVertexKey))) return; + vector sorted_keys = SortInputVertices(); + auto _ = absl::MakeCleanup([&]() { tracker_.Untally(sorted_keys); }); + for (const InputVertexKey& key : sorted_keys) { 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; - // FindClosestPoints() measures distances conservatively, so we need to - // recheck the distances using exact predicates. - // - // NOTE(ericv): When the snap radius is large compared to the average - // vertex spacing, we could possibly avoid the call the FindClosestPoints - // by checking whether sites_.back() is close enough. - S2ClosestPointQueryPointTarget target(site); - site_query.FindClosestPoints(&target, &results); bool add_site = true; - for (const auto& result : results) { - if (s2pred::CompareDistance(site, result.point(), - min_site_separation_ca_) <= 0) { - add_site = false; - // This pair of sites is too close. If the sites are distinct, then - // the output cannot be idempotent. - snapping_needed_ = snapping_needed_ || site != result.point(); + if (site_snap_radius_ca_ == S1ChordAngle::Zero()) { + add_site = sites_.empty() || site != sites_.back(); + } else { + // FindClosestPoints() measures distances conservatively, so we need to + // recheck the distances using exact predicates. + // + // NOTE(ericv): When the snap radius is large compared to the average + // vertex spacing, we could possibly avoid the call the FindClosestPoints + // by checking whether sites_.back() is close enough. + S2ClosestPointQueryPointTarget target(site); + site_query.FindClosestPoints(&target, &results); + for (const auto& result : results) { + if (s2pred::CompareDistance(site, result.point(), + min_site_separation_ca_) <= 0) { + add_site = false; + // This pair of sites is too close. If the sites are distinct, then + // the output cannot be idempotent. + snapping_needed_ = snapping_needed_ || site != result.point(); + } } } if (add_site) { + if (!tracker_.TallyIndexedSite()) return; site_index->Add(site, sites_.size()); + if (!tracker_.AddSpace(&sites_, 1)) return; sites_.push_back(site); site_query.ReInit(); } @@ -693,7 +794,7 @@ void S2Builder::ChooseInitialSites(S2PointIndex* site_index) { S2Point S2Builder::SnapSite(const S2Point& point) const { if (!snapping_requested_) return point; S2Point site = options_.snap_function().SnapPoint(point); -S1ChordAngle dist_moved(site, point); + S1ChordAngle dist_moved(site, point); if (dist_moved > site_snap_radius_ca_) { error_->Init(S2Error::BUILDER_SNAP_RADIUS_TOO_SMALL, "Snap function moved vertex (%.15g, %.15g, %.15g) " @@ -705,23 +806,27 @@ S1ChordAngle dist_moved(site, point); return site; } -// For each edge, find all sites within min_edge_site_query_radius_ca_ and +// For each edge, find all sites within edge_site_query_radius_ca_ and // store them in edge_sites_. Also, to implement idempotency this method also // checks whether the input vertices and edges may already satisfy the output // criteria. If any problems are found then snapping_needed_ is set to true. void S2Builder::CollectSiteEdges(const S2PointIndex& site_index) { // Find all points whose distance is <= edge_site_query_radius_ca_. + // + // Memory used by S2ClosestPointQuery is not tracked, but it is temporary, + // typically insignificant, and does not affect the high water mark. S2ClosestPointQueryOptions options; options.set_conservative_max_distance(edge_site_query_radius_ca_); S2ClosestPointQuery site_query(&site_index, options); vector::Result> results; - edge_sites_.resize(input_edges_.size()); - for (InputEdgeId e = 0; e < input_edges_.size(); ++e) { + if (!tracker_.AddSpaceExact(&edge_sites_, input_edges_.size())) return; + edge_sites_.resize(input_edges_.size()); // Construct all elements. + for (InputEdgeId e = 0; static_cast(e) < input_edges_.size(); ++e) { const InputEdge& edge = input_edges_[e]; const S2Point& v0 = input_vertices_[edge.first]; const S2Point& v1 = input_vertices_[edge.second]; if (s2builder_verbose) { - cpp_compat_cout << "S2Polyline: " << s2textformat::ToString(v0) + std::cout << "S2Polyline: " << s2textformat::ToString(v0) << ", " << s2textformat::ToString(v1) << "\n"; } S2ClosestPointQueryEdgeTarget target(v0, v1); @@ -739,20 +844,32 @@ void S2Builder::CollectSiteEdges(const S2PointIndex& site_index) { } } SortSitesByDistance(v0, sites); + if (!tracker_.TallyEdgeSites(*sites)) return; } } +// Sorts the sites in increasing order of distance to X. void S2Builder::SortSitesByDistance(const S2Point& x, compact_array* sites) const { - // Sort sites in increasing order of distance to X. std::sort(sites->begin(), sites->end(), [&x, this](SiteId i, SiteId j) { return s2pred::CompareDistances(x, sites_[i], sites_[j]) < 0; }); } -// There are two situatons where we need to add extra Voronoi sites in order -// to ensure that the snapped edges meet the output requirements: +// Like the above, but inserts "new_site_id" into an already-sorted list. +void S2Builder::InsertSiteByDistance(SiteId new_site_id, const S2Point& x, + compact_array* sites) { + if (!tracker_.ReserveEdgeSite(sites)) return; + sites->insert(std::lower_bound( + sites->begin(), sites->end(), new_site_id, + [&x, this](SiteId i, SiteId j) { + return s2pred::CompareDistances(x, sites_[i], sites_[j]) < 0; + }), new_site_id); +} + +// There are two situations where we need to add extra Voronoi sites in order to +// ensure that the snapped edges meet the output requirements: // // (1) If a snapped edge deviates from its input edge by more than // max_edge_deviation(), we add a new site on the input edge near the @@ -760,58 +877,99 @@ void S2Builder::SortSitesByDistance(const S2Point& x, // into two pieces, so that it follows the input edge more closely. // // (2) If a snapped edge is closer than min_edge_vertex_separation() to any -// nearby site (the "site to avoid"), then we add a new site (the -// "separation site") on the input edge near the site to avoid. This -// causes the snapped edge to follow the input edge more closely and is -// guaranteed to increase the separation to the required distance. +// nearby site (the "site to avoid") or passes on the wrong side of it +// relative to the input edge, then we add a new site (the "separation +// site") along the input edge near the site to avoid. This causes the +// snapped edge to follow the input edge more closely, so that it is +// guaranteed to pass on the correct side of the site to avoid with a +// separation of at least the required distance. // // We check these conditions by snapping all the input edges to a chain of // Voronoi sites and then testing each edge in the chain. If a site needs to // be added, we mark all nearby edges for re-snapping. void S2Builder::AddExtraSites(const MutableS2ShapeIndex& input_edge_index) { - // When options_.split_crossing_edges() is true, this function may be called - // even when site_snap_radius_ca_ == 0 (because edge_snap_radius_ca_ > 0). - // However neither of the conditions above apply in that case. - if (site_snap_radius_ca_ == S1ChordAngle::Zero()) return; - - vector chain; // Temporary - vector snap_queue; - for (InputEdgeId max_e = 0; max_e < input_edges_.size(); ++max_e) { - snap_queue.push_back(max_e); - while (!snap_queue.empty()) { - InputEdgeId e = snap_queue.back(); - snap_queue.pop_back(); + // Note that we could save some work in AddSnappedEdges() by saving the + // snapped edge chains in a vector, but currently this is not worthwhile + // since SnapEdge() accounts for less than 5% of the runtime. + + // Using 18 buckets is equivalent to `reserve(16)`, which was the + // `expected_max_elements` used by the old `dense_hash_set` version. + // This will actually get us 31 buckets since it gets rounded up. + // We could experiment with different values here. + flat_hash_set edges_to_resnap(/*bucket_count=*/18); + + vector chain; // Temporary storage. + int num_edges_after_snapping = 0; + + // CheckEdge() defines the body of the loops below. + const auto CheckEdge = [&](InputEdgeId e) -> bool { + if (!tracker_.ok()) return false; SnapEdge(e, &chain); - // We could save the snapped chain here in a snapped_chains_ vector, to - // avoid resnapping it in AddSnappedEdges() below, however currently - // SnapEdge only accounts for less than 5% of the runtime. - MaybeAddExtraSites(e, max_e, chain, input_edge_index, &snap_queue); + edges_to_resnap.erase(e); + num_edges_after_snapping += chain.size(); + MaybeAddExtraSites(e, chain, input_edge_index, &edges_to_resnap); + return true; + }; + + // The first pass is different because we snap every edge. In the following + // passes we only snap edges that are near the extra sites that were added. + S2_VLOG(1) << "Before pass 0: sites=" << sites_.size(); + for (InputEdgeId e = 0; static_cast(e) < input_edges_.size(); ++e) { + if (!CheckEdge(e)) return; + } + S2_VLOG(1) << "Pass 0: edges snapped=" << input_edges_.size() + << ", output edges=" << num_edges_after_snapping + << ", sites=" << sites_.size(); + + for (int num_passes = 1; !edges_to_resnap.empty(); ++num_passes) { + auto edges_to_snap = edges_to_resnap; + edges_to_resnap.clear(); + num_edges_after_snapping = 0; + for (InputEdgeId e : edges_to_snap) { + if (!CheckEdge(e)) return; } + S2_VLOG(1) << "Pass " << num_passes + << ": edges snapped=" << edges_to_snap.size() + << ", output edges=" << num_edges_after_snapping + << ", sites=" << sites_.size(); } } -void S2Builder::MaybeAddExtraSites(InputEdgeId edge_id, - InputEdgeId max_edge_id, - const vector& chain, - const MutableS2ShapeIndex& input_edge_index, - vector* snap_queue) { - // The snapped chain is always a *subsequence* of the nearby sites +void S2Builder::MaybeAddExtraSites( + InputEdgeId edge_id, const vector& chain, + const MutableS2ShapeIndex& input_edge_index, + flat_hash_set* edges_to_resnap) { + // If the memory tracker has a periodic callback function, tally an amount + // of memory proportional to the work being done so that the caller has an + // opportunity to cancel the operation if necessary. + if (!tracker_.TallyTemp(chain.size() * sizeof(chain[0]))) return; + + // If the input includes NaN vertices, snapping can produce an empty chain. + if (chain.empty()) return; + + // The snapped edge chain is always a subsequence of the nearby sites // (edge_sites_), so we walk through the two arrays in parallel looking for - // sites that weren't snapped. We also keep track of the current snapped - // edge, since it is the only edge that can be too close. - int i = 0; - for (SiteId id : edge_sites_[edge_id]) { + // sites that weren't snapped. These are the "sites to avoid". We also keep + // track of the current snapped edge, since it is the only edge that can be + // too close or pass on the wrong side of a site to avoid. Vertices beyond + // the chain endpoints in either direction can be ignored because only the + // interiors of chain edges can be too close to a site to avoid. + const InputEdge& edge = input_edges_[edge_id]; + const S2Point& a0 = input_vertices_[edge.first]; + const S2Point& a1 = input_vertices_[edge.second]; + const auto& nearby_sites = edge_sites_[edge_id]; + for (size_t i = 0, j = 0; j < nearby_sites.size(); ++j) { + SiteId id = nearby_sites[j]; if (id == chain[i]) { - if (++i == chain.size()) break; + // This site is a vertex of the snapped edge chain. + if (++i == chain.size()) { + break; // Sites beyond the end of the snapped chain can be ignored. + } // Check whether this snapped edge deviates too far from its original // position. If so, we split the edge by adding an extra site. const S2Point& v0 = sites_[chain[i - 1]]; const S2Point& v1 = sites_[chain[i]]; if (S1ChordAngle(v0, v1) < min_edge_length_to_split_ca_) continue; - - const InputEdge& edge = input_edges_[edge_id]; - const S2Point& a0 = input_vertices_[edge.first]; - const S2Point& a1 = input_vertices_[edge.second]; if (!S2::IsEdgeBNearEdgeA(a0, a1, v0, v1, max_edge_deviation_)) { // Add a new site on the input edge, positioned so that it splits the // snapped edge into two approximately equal pieces. Then we find all @@ -825,57 +983,109 @@ void S2Builder::MaybeAddExtraSites(InputEdgeId edge_id, S2Point mid = (S2::Project(v0, a0, a1) + S2::Project(v1, a0, a1)).Normalize(); S2Point new_site = GetSeparationSite(mid, v0, v1, edge_id); - AddExtraSite(new_site, max_edge_id, input_edge_index, snap_queue); + AddExtraSite(new_site, input_edge_index, edges_to_resnap); + + // In the case where the edge wrapped around the sphere the "wrong + // way", it is not safe to continue checking this edge. It will be + // marked for resnapping and we will come back to it in the next pass. return; } - } else if (i > 0 && id >= num_forced_sites_) { - // Check whether this "site to avoid" is closer to the snapped edge than - // min_edge_vertex_separation(). Note that this is the only edge of the - // chain that can be too close because its vertices must span the point - // where "site_to_avoid" projects onto the input edge XY (this claim - // relies on the fact that all sites are separated by at least the snap - // radius). We don't try to avoid sites added using ForceVertex() - // because we don't guarantee any minimum separation from such sites. + } else { + // This site is near the input edge but is not part of the snapped chain. + if (i == 0) { + continue; // Sites before the start of the chain can be ignored. + } + // We need to ensure that non-forced sites are separated by at least + // min_edge_vertex_separation() from the snapped chain. This happens + // automatically as part of the algorithm except where there are portions + // of the input edge that are not within edge_snap_radius() of any site. + // These portions of the original edge are called "coverage gaps". + // Therefore if we find that a site to avoid that is too close to the + // snapped edge chain, we can fix the problem by adding a new site (the + // "separation site") in the corresponding coverage gap located as closely + // as possible to the site to avoid. This technique is is guaranteed to + // produce the required minimum separation, and the entire process of + // adding separation sites is guaranteed to terminate. const S2Point& site_to_avoid = sites_[id]; const S2Point& v0 = sites_[chain[i - 1]]; const S2Point& v1 = sites_[chain[i]]; - if (s2pred::CompareEdgeDistance( + bool add_separation_site = false; + if (!is_forced(id) && + min_edge_site_separation_ca_ > S1ChordAngle::Zero() && + s2pred::CompareEdgeDistance( site_to_avoid, v0, v1, min_edge_site_separation_ca_) < 0) { - // A snapped edge can only approach a site too closely when there are - // no sites near the input edge near that point. We fix that by - // adding a new site along the input edge (a "separation site"), then - // we find all the edges near the new site (including this one) and + add_separation_site = true; + } + // Similarly, we also add a separation site whenever a snapped edge passes + // on the wrong side of a site to avoid. Normally we don't need to worry + // about this, since if an edge passes on the wrong side of a nearby site + // then it is also too close to it. However if the snap radius is very + // small and intersection_tolerance() is non-zero then we need to check + // this condition explicitly (see the "check_all_site_crossings_" flag for + // details). We also need to check this condition explicitly for forced + // vertices. Again, we can solve this problem by adding a "separation + // site" in the corresponding coverage gap located as closely as possible + // to the site to avoid. + // + // It is possible to show that when all points are projected onto the + // great circle through (a0, a1), no improper crossing occurs unless the + // the site to avoid is located between a0 and a1, and also between v0 + // and v1. TODO(ericv): Verify whether all these checks are necessary. + if (!add_separation_site && + (is_forced(id) || check_all_site_crossings_) && + (s2pred::Sign(a0, a1, site_to_avoid) != + s2pred::Sign(v0, v1, site_to_avoid))&& + s2pred::CompareEdgeDirections(a0, a1, a0, site_to_avoid) > 0 && + s2pred::CompareEdgeDirections(a0, a1, site_to_avoid, a1) > 0 && + s2pred::CompareEdgeDirections(a0, a1, v0, site_to_avoid) > 0 && + s2pred::CompareEdgeDirections(a0, a1, site_to_avoid, v1) > 0) { + add_separation_site = true; + } + if (add_separation_site) { + // We add a new site (the separation site) in the coverage gap along the + // input edge, located as closely as possible to the site to avoid. + // Then we find all the edges near the new site (including this one) and // add them to the snap queue. S2Point new_site = GetSeparationSite(site_to_avoid, v0, v1, edge_id); S2_DCHECK_NE(site_to_avoid, new_site); - AddExtraSite(new_site, max_edge_id, input_edge_index, snap_queue); - return; + AddExtraSite(new_site, input_edge_index, edges_to_resnap); + + // Skip the remaining sites near this chain edge, and then continue + // scanning this chain. Note that this is safe even though the call + // to AddExtraSite() above added a new site to "nearby_sites". + for (; nearby_sites[j + 1] != chain[i]; ++j) {} } } } } -// Adds a new site, then updates "edge_sites"_ for all edges near the new site -// and adds them to "snap_queue" for resnapping (unless their edge id exceeds -// "max_edge_id", since those edges have not been snapped the first time yet). +// Adds a new site, then updates "edge_sites_" for all edges near the new site +// and adds them to "edges_to_resnap" for resnapping. void S2Builder::AddExtraSite(const S2Point& new_site, - InputEdgeId max_edge_id, const MutableS2ShapeIndex& input_edge_index, - vector* snap_queue) { + flat_hash_set* edges_to_resnap) { + if (!sites_.empty()) S2_DCHECK_NE(new_site, sites_.back()); + if (!tracker_.AddSpace(&sites_, 1)) return; SiteId new_site_id = sites_.size(); sites_.push_back(new_site); + // Find all edges whose distance is <= edge_site_query_radius_ca_. S2ClosestEdgeQuery::Options options; options.set_conservative_max_distance(edge_site_query_radius_ca_); options.set_include_interiors(false); + + if (!input_edge_index.is_fresh()) input_edge_index.ForceBuild(); + if (!tracker_.ok()) return; + + // Memory used by S2ClosestEdgeQuery is not tracked, but it is temporary, + // typically insignificant, and does not affect the high water mark. S2ClosestEdgeQuery query(&input_edge_index, options); S2ClosestEdgeQuery::PointTarget target(new_site); for (const auto& result : query.FindClosestEdges(&target)) { 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); - if (e <= max_edge_id) snap_queue->push_back(e); + const S2Point& v0 = input_vertices_[input_edges_[e].first]; + InsertSiteByDistance(new_site_id, v0, &edge_sites_[e]); + edges_to_resnap->insert(e); } } @@ -904,8 +1114,8 @@ S2Point S2Builder::GetSeparationSite(const S2Point& site_to_avoid, Vector3_d xy_dir = y - x; S2Point n = S2::RobustCrossProd(x, y); S2Point new_site = S2::Project(site_to_avoid, x, y, n); - S2Point gap_min = GetCoverageEndpoint(v0, x, y, n); - S2Point gap_max = GetCoverageEndpoint(v1, y, x, -n); + S2Point gap_min = GetCoverageEndpoint(v0, n); + S2Point gap_max = GetCoverageEndpoint(v1, -n); if ((new_site - gap_min).DotProd(xy_dir) < 0) { new_site = gap_min; } else if ((gap_max - new_site).DotProd(xy_dir) < 0) { @@ -920,8 +1130,7 @@ S2Point S2Builder::GetSeparationSite(const S2Point& site_to_avoid, // Given a site P and an edge XY with normal N, intersect XY with the disc of // radius snap_radius() around P, and return the intersection point that is // further along the edge XY toward Y. -S2Point S2Builder::GetCoverageEndpoint(const S2Point& p, const S2Point& x, - const S2Point& y, const S2Point& n) +S2Point S2Builder::GetCoverageEndpoint(const S2Point& p, const S2Point& n) const { // Consider the plane perpendicular to P that cuts off a spherical cap of // radius snap_radius(). This plane intersects the plane through the edge @@ -956,6 +1165,8 @@ void S2Builder::SnapEdge(InputEdgeId e, vector* chain) const { chain->clear(); const InputEdge& edge = input_edges_[e]; if (!snapping_needed_) { + // Note that the input vertices have been renumbered such that + // InputVertexId and SiteId are the same (see ChooseAllVerticesAsSites). chain->push_back(edge.first); chain->push_back(edge.second); return; @@ -1041,53 +1252,66 @@ void S2Builder::SnapEdge(InputEdgeId e, vector* chain) const { } } if (s2builder_verbose) { - cpp_compat_cout << "(" << edge.first << "," << edge.second << "): "; - for (SiteId id : *chain) cpp_compat_cout << id << " "; - cpp_compat_cout << std::endl; + std::cout << "(" << edge.first << "," << edge.second << "): "; + for (SiteId id : *chain) std::cout << id << " "; + std::cout << std::endl; } } void S2Builder::BuildLayers() { + if (!tracker_.ok()) return; + // Each output edge has an "input edge id set id" (an int32) representing // the set of input edge ids that were snapped to this edge. The actual // InputEdgeIds can be retrieved using "input_edge_id_set_lexicon". vector> layer_edges; vector> layer_input_edge_ids; IdSetLexicon input_edge_id_set_lexicon; + vector> layer_vertices; BuildLayerEdges(&layer_edges, &layer_input_edge_ids, &input_edge_id_set_lexicon); - - // At this point we have no further need for the input geometry or nearby - // site data, so we clear those fields to save space. - vector().swap(input_vertices_); - vector().swap(input_edges_); - vector>().swap(edge_sites_); + auto _ = absl::MakeCleanup([&]() { + for (size_t i = 0; i < layers_.size(); ++i) { + tracker_.Untally(layer_edges[i]); + tracker_.Untally(layer_input_edge_ids[i]); + if (!layer_vertices.empty()) tracker_.Untally(layer_vertices[i]); + } + }); // If there are a large number of layers, then we build a minimal subset of // vertices for each layer. This ensures that layer types that iterate over // vertices will run in time proportional to the size of that layer rather // than the size of all layers combined. - vector> layer_vertices; static const int kMinLayersForVertexFiltering = 10; if (layers_.size() >= kMinLayersForVertexFiltering) { // Disable vertex filtering if it is disallowed by any layer. (This could // be optimized, but in current applications either all layers allow // filtering or none of them do.) - bool allow_vertex_filtering = false; + bool allow_vertex_filtering = true; for (const auto& options : layer_options_) { allow_vertex_filtering &= options.allow_vertex_filtering(); } if (allow_vertex_filtering) { - vector filter_tmp; // Temporary used by FilterVertices. + // Track the temporary memory used by FilterVertices(). Note that + // although vertex filtering can increase the number of vertices stored + // (i.e., if the same vertex is referred to by multiple layers), it + // never increases storage quadratically because there can be at most + // two filtered vertices per edge. + if (!tracker_.TallyFilterVertices(sites_.size(), layer_edges)) return; + auto _ = absl::MakeCleanup([this]() { tracker_.DoneFilterVertices(); }); layer_vertices.resize(layers_.size()); - for (int i = 0; i < layers_.size(); ++i) { + vector filter_tmp; // Temporary used by FilterVertices. + for (size_t i = 0; i < layers_.size(); ++i) { layer_vertices[i] = Graph::FilterVertices(sites_, &layer_edges[i], &filter_tmp); + if (!tracker_.Tally(layer_vertices[i])) return; } - vector().swap(sites_); // Release memory + tracker_.Clear(&sites_); // Releases memory. } } - for (int i = 0; i < layers_.size(); ++i) { + if (!tracker_.ok()) return; + + for (size_t i = 0; i < layers_.size(); ++i) { const vector& vertices = (layer_vertices.empty() ? sites_ : layer_vertices[i]); Graph graph(layer_options_[i], &vertices, &layer_edges[i], @@ -1106,7 +1330,7 @@ static void DumpEdges(const vector& edges, vector v; v.push_back(vertices[e.first]); v.push_back(vertices[e.second]); - cpp_compat_cout << "S2Polyline: " << s2textformat::ToString(v) + std::cout << "S2Polyline: " << s2textformat::ToString(v) << "(" << e.first << "," << e.second << ")" << std::endl; } } @@ -1123,31 +1347,41 @@ void S2Builder::BuildLayerEdges( IdSetLexicon* input_edge_id_set_lexicon) { // Edge chains are simplified only when a non-zero snap radius is specified. // If so, we build a map from each site to the set of input vertices that - // snapped to that site. + // snapped to that site. (Note that site_vertices is relatively small and + // that its memory tracking is deferred until TallySimplifyEdgeChains.) vector> site_vertices; bool simplify = snapping_needed_ && options_.simplify_edge_chains(); if (simplify) site_vertices.resize(sites_.size()); layer_edges->resize(layers_.size()); layer_input_edge_ids->resize(layers_.size()); - for (int i = 0; i < layers_.size(); ++i) { + for (size_t i = 0; i < layers_.size(); ++i) { AddSnappedEdges(layer_begins_[i], layer_begins_[i+1], layer_options_[i], &(*layer_edges)[i], &(*layer_input_edge_ids)[i], input_edge_id_set_lexicon, &site_vertices); } + + // We simplify edge chains before processing the per-layer GraphOptions + // because simplification can create duplicate edges and/or sibling edge + // pairs which may need to be removed. if (simplify) { SimplifyEdgeChains(site_vertices, layer_edges, layer_input_edge_ids, input_edge_id_set_lexicon); + vector>().swap(site_vertices); } - // We simplify edge chains before processing the per-layer GraphOptions - // because simplification can create duplicate edges and/or sibling edge - // pairs which may need to be removed. - for (int i = 0; i < layers_.size(); ++i) { + + // At this point we have no further need for nearby site data, so we clear + // it to save space. We keep input_vertices_ and input_edges_ so that + // S2Builder::Layer implementations can access them if desired. (This is + // useful for determining how snapping has changed the input geometry.) + tracker_.ClearEdgeSites(&edge_sites_); + for (size_t i = 0; i < layers_.size(); ++i) { // The errors generated by ProcessEdges are really warnings, so we simply // record them and continue. Graph::ProcessEdges(&layer_options_[i], &(*layer_edges)[i], &(*layer_input_edge_ids)[i], - input_edge_id_set_lexicon, error_); + input_edge_id_set_lexicon, error_, &tracker_); + if (!tracker_.ok()) return; } } @@ -1159,13 +1393,21 @@ void S2Builder::AddSnappedEdges( InputEdgeId begin, InputEdgeId end, const GraphOptions& options, vector* edges, vector* input_edge_ids, IdSetLexicon* input_edge_id_set_lexicon, - vector>* site_vertices) const { + vector>* site_vertices) { bool discard_degenerate_edges = (options.degenerate_edges() == GraphOptions::DegenerateEdges::DISCARD); vector chain; for (InputEdgeId e = begin; e < end; ++e) { InputEdgeIdSetId id = input_edge_id_set_lexicon->AddSingleton(e); SnapEdge(e, &chain); + if (chain.empty()) { + continue; + } + + int num_snapped_edges = max(1, chain.size() - 1); + if (options.edge_type() == EdgeType::UNDIRECTED) num_snapped_edges *= 2; + if (!tracker_.AddSpace(edges, num_snapped_edges)) return; + if (!tracker_.AddSpace(input_edge_ids, num_snapped_edges)) return; MaybeAddInputVertex(input_edges_[e].first, chain[0], site_vertices); if (chain.size() == 1) { if (discard_degenerate_edges) continue; @@ -1173,7 +1415,7 @@ void S2Builder::AddSnappedEdges( edges, input_edge_ids); } else { MaybeAddInputVertex(input_edges_[e].second, chain.back(), site_vertices); - for (int i = 1; i < chain.size(); ++i) { + for (size_t i = 1; i < chain.size(); ++i) { AddSnappedEdge(chain[i-1], chain[i], id, options.edge_type(), edges, input_edge_ids); } @@ -1183,7 +1425,9 @@ void S2Builder::AddSnappedEdges( } // If "site_vertices" is non-empty, ensures that (*site_vertices)[id] contains -// "v". Duplicate entries are allowed. +// "v". Duplicate entries are allowed. The purpose of this function is to +// build a map so that SimplifyEdgeChains() can quickly find all the input +// vertices that snapped to a particular site. inline void S2Builder::MaybeAddInputVertex( InputVertexId v, SiteId id, vector>* site_vertices) const { @@ -1194,6 +1438,7 @@ inline void S2Builder::MaybeAddInputVertex( // destination of one edge is the same as the source of the next edge. auto& vertices = (*site_vertices)[id]; if (vertices.empty() || vertices.back() != v) { + // Memory tracking is deferred until SimplifyEdgeChains. vertices.push_back(v); } } @@ -1236,6 +1481,7 @@ class S2Builder::EdgeChainSimplifier { using VertexId = Graph::VertexId; class InteriorVertexMatcher; + void OutputEdge(EdgeId e); int graph_edge_layer(EdgeId e) const; int input_edge_layer(InputEdgeId id) const; @@ -1245,24 +1491,26 @@ class S2Builder::EdgeChainSimplifier { void OutputAllEdges(VertexId v0, VertexId v1); bool TargetInputVertices(VertexId v, S2PolylineSimplifier* simplifier) const; bool AvoidSites(VertexId v0, VertexId v1, VertexId v2, + flat_hash_set* used_vertices, S2PolylineSimplifier* simplifier) const; void MergeChain(const vector& vertices); void AssignDegenerateEdges( const vector& degenerate_ids, - vector>* merged_input_ids) const; + vector>* merged_ids) const; + // LINT.IfChange const S2Builder& builder_; const Graph& g_; Graph::VertexInMap in_; Graph::VertexOutMap out_; - vector edge_layers_; + const vector& edge_layers_; const vector>& site_vertices_; vector>* layer_edges_; vector>* layer_input_edge_ids_; IdSetLexicon* input_edge_id_set_lexicon_; // Convenience member copied from builder_. - const std::vector& layer_begins_; + const vector& layer_begins_; // is_interior_[v] indicates that VertexId "v" is eligible to be an interior // vertex of a simplified edge chain. You can think of it as vertex whose @@ -1273,9 +1521,10 @@ class S2Builder::EdgeChainSimplifier { // used_[e] indicates that EdgeId "e" has already been processed. vector used_; - // Temporary vectors, declared here to avoid repeated allocation. + // Temporary objects declared here to avoid repeated allocation. vector tmp_vertices_; vector tmp_edges_; + flat_hash_set tmp_vertex_set_; // The output edges after simplification. vector new_edges_; @@ -1288,8 +1537,9 @@ void S2Builder::SimplifyEdgeChains( const vector>& site_vertices, vector>* layer_edges, vector>* layer_input_edge_ids, - IdSetLexicon* input_edge_id_set_lexicon) const { + IdSetLexicon* input_edge_id_set_lexicon) { if (layers_.empty()) return; + if (!tracker_.TallySimplifyEdgeChains(site_vertices, *layer_edges)) return; // Merge the edges from all layers (in order to build a single graph). vector merged_edges; @@ -1316,6 +1566,7 @@ void S2Builder::SimplifyEdgeChains( layer_edges, layer_input_edge_ids, input_edge_id_set_lexicon); simplifier.Run(); } +// LINT.ThenChange(:TallySimplifyEdgeChains) // Merges the edges from all layers and sorts them in lexicographic order so // that we can construct a single graph. The sort is stable, which means that @@ -1326,8 +1577,8 @@ void S2Builder::MergeLayerEdges( vector* edges, vector* input_edge_ids, vector* edge_layers) const { vector order; - for (int i = 0; i < layer_edges.size(); ++i) { - for (int e = 0; e < layer_edges[i].size(); ++e) { + for (size_t i = 0; i < layer_edges.size(); ++i) { + for (size_t e = 0; e < layer_edges[i].size(); ++e) { order.push_back(LayerEdgeId(i, e)); } } @@ -1353,7 +1604,7 @@ inline bool S2Builder::StableLessThan( const Edge& a, const Edge& b, const LayerEdgeId& ai, const LayerEdgeId& bi) { // The compiler doesn't optimize this as well as it should: - // return make_pair(a, ai) < make_pair(b, bi); + // return std::make_pair(a, ai) < std::make_pair(b, bi); if (a.first < b.first) return true; if (b.first < a.first) return false; if (a.second < b.second) return true; @@ -1372,7 +1623,9 @@ S2Builder::EdgeChainSimplifier::EdgeChainSimplifier( layer_input_edge_ids_(layer_input_edge_ids), input_edge_id_set_lexicon_(input_edge_id_set_lexicon), layer_begins_(builder_.layer_begins_), - is_interior_(g.num_vertices()), used_(g.num_edges()) { + is_interior_(g.num_vertices()), used_(g.num_edges()), + // See `AddExtraSites` for explanation of `bucket_count`. + tmp_vertex_set_(/*bucket_count=*/18) { new_edges_.reserve(g.num_edges()); new_input_edge_ids_.reserve(g.num_edges()); new_edge_layers_.reserve(g.num_edges()); @@ -1419,10 +1672,12 @@ void S2Builder::EdgeChainSimplifier::Run() { SimplifyChain(edge.first, edge.second); } } + // TODO(ericv): The graph is not needed past here, so we could save some + // memory by clearing the underlying Edge and InputEdgeIdSetId vectors. // Finally, copy the output edges into the appropriate layers. They don't // need to be sorted because the input edges were also unsorted. - for (int e = 0; e < new_edges_.size(); ++e) { + for (size_t e = 0; e < new_edges_.size(); ++e) { int layer = new_edge_layers_[e]; (*layer_edges_)[layer].push_back(new_edges_[e]); (*layer_input_edge_ids_)[layer].push_back(new_input_edge_ids_[e]); @@ -1527,7 +1782,7 @@ bool S2Builder::EdgeChainSimplifier::IsInterior(VertexId v) { // Check a few simple prerequisites. if (out_.degree(v) == 0) return false; if (out_.degree(v) != in_.degree(v)) return false; - if (v < builder_.num_forced_sites_) return false; // Keep forced vertices. + if (builder_.is_forced(v)) return false; // Keep forced vertices. // Sort the edges so that they are grouped by layer. vector& edges = tmp_edges_; // Avoid allocating each time. @@ -1558,16 +1813,25 @@ bool S2Builder::EdgeChainSimplifier::IsInterior(VertexId v) { void S2Builder::EdgeChainSimplifier::SimplifyChain(VertexId v0, VertexId v1) { // Avoid allocating "chain" each time by reusing it. vector& chain = tmp_vertices_; + // Contains the set of vertices that have either been avoided or added to + // the chain so far. This is necessary so that AvoidSites() doesn't try to + // avoid vertices that have already been added to the chain. + flat_hash_set& used_vertices = tmp_vertex_set_; S2PolylineSimplifier simplifier; VertexId vstart = v0; bool done = false; do { - // Simplify a subchain of edges starting (v0, v1). - simplifier.Init(g_.vertex(v0)); - AvoidSites(v0, v0, v1, &simplifier); + // Simplify a subchain of edges starting with (v0, v1). chain.push_back(v0); + used_vertices.insert(v0); + simplifier.Init(g_.vertex(v0)); + // Note that if the first edge (v0, v1) is longer than the maximum length + // allowed for simplification, then AvoidSites() will return false and we + // exit the loop below after the first iteration. + const bool simplify = AvoidSites(v0, v0, v1, &used_vertices, &simplifier); do { chain.push_back(v1); + used_vertices.insert(v1); done = !is_interior_[v1] || v1 == vstart; if (done) break; @@ -1575,8 +1839,8 @@ void S2Builder::EdgeChainSimplifier::SimplifyChain(VertexId v0, VertexId v1) { VertexId vprev = v0; v0 = v1; v1 = FollowChain(vprev, v0); - } while (TargetInputVertices(v0, &simplifier) && - AvoidSites(chain[0], v0, v1, &simplifier) && + } while (simplify && TargetInputVertices(v0, &simplifier) && + AvoidSites(chain[0], v0, v1, &used_vertices, &simplifier) && simplifier.Extend(g_.vertex(v1))); if (chain.size() == 2) { @@ -1587,6 +1851,7 @@ void S2Builder::EdgeChainSimplifier::SimplifyChain(VertexId v0, VertexId v1) { // Note that any degenerate edges that were not merged into a chain are // output by EdgeChainSimplifier::Run(). chain.clear(); + used_vertices.clear(); } while (!done); } @@ -1626,6 +1891,7 @@ bool S2Builder::EdgeChainSimplifier::TargetInputVertices( // near the edge (v1, v2) are avoided by at least min_edge_vertex_separation. bool S2Builder::EdgeChainSimplifier::AvoidSites( VertexId v0, VertexId v1, VertexId v2, + flat_hash_set* used_vertices, S2PolylineSimplifier* simplifier) const { const S2Point& p0 = g_.vertex(v0); const S2Point& p1 = g_.vertex(v1); @@ -1669,15 +1935,16 @@ bool S2Builder::EdgeChainSimplifier::AvoidSites( S2_DCHECK_GE(best, 0); // Because there is at least one outgoing edge. for (VertexId v : edge_sites[best]) { - // This test is optional since these sites are excluded below anyway. - if (v == v0 || v == v1 || v == v2) continue; - - // We are only interested in sites whose distance from "p0" is in the - // range (r1, r2). Sites closer than "r1" have already been processed, - // and sites further than "r2" aren't relevant yet. + // Sites whose distance from "p0" is at least "r2" are not relevant yet. const S2Point& p = g_.vertex(v); S1ChordAngle r(p0, p); - if (r <= r1 || r >= r2) continue; + if (r >= r2) continue; + + // The following test prevents us from avoiding previous vertices of the + // edge chain that also happen to be nearby the current edge. (It also + // happens to ensure that each vertex is avoided at most once, but this is + // just an optimization.) + if (!used_vertices->insert(v).second) continue; // We need to figure out whether this site is to the left or right of the // edge chain. For the first edge this is easy. Otherwise, since we are @@ -1706,7 +1973,7 @@ void S2Builder::EdgeChainSimplifier::MergeChain( vector> merged_input_ids; vector degenerate_ids; int num_out; // Edge count in the outgoing direction. - for (int i = 1; i < vertices.size(); ++i) { + for (size_t i = 1; i < vertices.size(); ++i) { VertexId v0 = vertices[i-1]; VertexId v1 = vertices[i]; auto out_edges = out_.edge_ids(v0, v1); @@ -1802,7 +2069,7 @@ void S2Builder::EdgeChainSimplifier::AssignDegenerateEdges( // such edges from the lists of candidates. vector order; order.reserve(merged_ids->size()); - for (int i = 0; i < merged_ids->size(); ++i) { + for (size_t i = 0; i < merged_ids->size(); ++i) { if (!(*merged_ids)[i].empty()) order.push_back(i); } std::sort(order.begin(), order.end(), [&merged_ids](int i, int j) { @@ -1827,3 +2094,138 @@ void S2Builder::EdgeChainSimplifier::AssignDegenerateEdges( (*merged_ids)[it[0]].push_back(degenerate_id); } } + + +/////////////////////// S2Builder::MemoryTracker ///////////////////////// + + +// Called to track memory used to store the set of sites near a given edge. +bool S2Builder::MemoryTracker::TallyEdgeSites( + const compact_array& sites) { + int64 size = GetCompactArrayAllocBytes(sites); + edge_sites_bytes_ += size; + return Tally(size); +} + +// Ensures that "sites" contains space for at least one more edge site. +bool S2Builder::MemoryTracker::ReserveEdgeSite(compact_array* sites) { + int64 new_size = sites->size() + 1; + if (new_size <= sites->capacity()) return true; + int64 old_bytes = GetCompactArrayAllocBytes(*sites); + sites->reserve(new_size); + int64 added_bytes = GetCompactArrayAllocBytes(*sites) - old_bytes; + edge_sites_bytes_ += added_bytes; + return Tally(added_bytes); +} + +// Releases and tracks the memory used to store nearby edge sites. +bool S2Builder::MemoryTracker::ClearEdgeSites( + vector>* edge_sites) { + Tally(-edge_sites_bytes_); + edge_sites_bytes_ = 0; + return Clear(edge_sites); +} + +// Called when a site is added to the S2PointIndex. +bool S2Builder::MemoryTracker::TallyIndexedSite() { + // S2PointIndex stores its data in a btree. In general btree nodes are only + // guaranteed to be half full, but in our case all nodes are full except for + // the rightmost node at each btree level because the values are added in + // sorted order. + int64 delta_bytes = GetBtreeMinBytesPerEntry< + absl::btree_multimap::PointData>>(); + site_index_bytes_ += delta_bytes; + return Tally(delta_bytes); +} + +// Corrects the approximate S2PointIndex memory tracking done above. +bool S2Builder::MemoryTracker::FixSiteIndexTally( + const S2PointIndex& index) { + int64 delta_bytes = index.SpaceUsed() - site_index_bytes_; + site_index_bytes_ += delta_bytes; + return Tally(delta_bytes); +} + +// Tracks memory due to destroying the site index. +bool S2Builder::MemoryTracker::DoneSiteIndex( + const S2PointIndex& index) { + Tally(-site_index_bytes_); + site_index_bytes_ = 0; + return ok(); +} + +// Called to indicate that edge simplification was requested. +// LINT.IfChange(TallySimplifyEdgeChains) +bool S2Builder::MemoryTracker::TallySimplifyEdgeChains( + const vector>& site_vertices, + const vector>& layer_edges) { + if (!is_active()) return true; + + // The simplify_edge_chains() option uses temporary memory per site + // (output vertex) and per output edge, as outlined below. + // + // Per site: + // vector> site_vertices; // BuildLayerEdges + // - compact_array non-inlined space is tallied separately + // vector is_interior_; // EdgeChainSimplifier + // Graph::VertexInMap in_; // EdgeChainSimplifier + // Graph::VertexOutMap out_; // EdgeChainSimplifier + const int64 kTempPerSite = + sizeof(compact_array) + sizeof(bool) + 2 * sizeof(EdgeId); + + // Per output edge: + // vector used_; // EdgeChainSimplifier + // Graph::VertexInMap in_; // EdgeChainSimplifier + // vector merged_edges; // SimplifyEdgeChains + // vector merged_input_edge_ids; // SimplifyEdgeChains + // vector merged_edge_layers; // SimplifyEdgeChains + // vector new_edges_; // EdgeChainSimplifier + // vector new_input_edge_ids_; // EdgeChainSimplifier + // vector new_edge_layers_; // EdgeChainSimplifier + // + // Note that the temporary vector in MergeLayerEdges() does not + // affect peak usage. + const int64 kTempPerEdge = sizeof(bool) + sizeof(EdgeId) + + 2 * sizeof(Edge) + 2 * sizeof(InputEdgeIdSetId) + + 2 * sizeof(int); + int64 simplify_bytes = site_vertices.size() * kTempPerSite; + for (const auto& array : site_vertices) { + simplify_bytes += GetCompactArrayAllocBytes(array); + } + for (const auto& edges : layer_edges) { + simplify_bytes += edges.size() * kTempPerEdge; + } + return TallyTemp(simplify_bytes); +} +// LINT.ThenChange() + +// Tracks the temporary memory used by Graph::FilterVertices. +// LINT.IfChange(TallyFilterVertices) +bool S2Builder::MemoryTracker::TallyFilterVertices( + int num_sites, const vector>& layer_edges) { + if (!is_active()) return true; + + // Vertex filtering (see BuildLayers) uses temporary space of one VertexId + // per Voronoi site plus 2 VertexIds per layer edge, plus space for all the + // vertices after filtering. + // + // vector *tmp; // Graph::FilterVertices + // vector used; // Graph::FilterVertices + const int64 kTempPerSite = sizeof(Graph::VertexId); + const int64 kTempPerEdge = 2 * sizeof(Graph::VertexId); + + size_t max_layer_edges = 0; + for (const auto& edges : layer_edges) { + max_layer_edges = max(max_layer_edges, edges.size()); + } + filter_vertices_bytes_ = (num_sites * kTempPerSite + + max_layer_edges * kTempPerEdge); + return Tally(filter_vertices_bytes_); +} +// LINT.ThenChange() + +bool S2Builder::MemoryTracker::DoneFilterVertices() { + Tally(-filter_vertices_bytes_); + filter_vertices_bytes_ = 0; + return ok(); +} diff --git a/src/s2/s2builder.h b/src/s2/s2builder.h index 32bf0e0c..86648cf4 100644 --- a/src/s2/s2builder.h +++ b/src/s2/s2builder.h @@ -21,19 +21,30 @@ #ifndef S2_S2BUILDER_H_ #define S2_S2BUILDER_H_ +#include +#include #include #include #include -#include "s2/base/integral_types.h" + #include "absl/base/macros.h" +#include "absl/container/flat_hash_set.h" + +#include "s2/base/integral_types.h" #include "s2/_fp_contract_off.h" #include "s2/id_set_lexicon.h" #include "s2/mutable_s2shape_index.h" #include "s2/s1angle.h" #include "s2/s1chord_angle.h" #include "s2/s2cell_id.h" +#include "s2/s2edge_crossings.h" +#include "s2/s2edge_distances.h" #include "s2/s2error.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" #include "s2/s2point_index.h" +#include "s2/s2point_span.h" +#include "s2/s2shape.h" #include "s2/s2shape_index.h" #include "s2/util/gtl/compact_array.h" @@ -124,7 +135,7 @@ class S2Polyline; // using s2builderutil::IntLatLngSnapFunction; // S2Builder builder(S2Builder::Options(IntLatLngSnapFunction(7))); // S2Polygon output; -// builder.StartLayer(absl::make_unique(&output)); +// builder.StartLayer(std::make_unique(&output)); // builder.AddPolygon(input); // S2Error error; // if (!builder.Build(&error)) { @@ -155,7 +166,7 @@ class S2Builder { // holes, the outer loops ("shells") should be directed counter-clockwise // while the inner loops ("holes") should be directed clockwise. Note that // S2Builder::AddPolygon() follows this convention automatically. - enum class EdgeType { DIRECTED, UNDIRECTED }; + enum class EdgeType : uint8 { DIRECTED, UNDIRECTED }; // A SnapFunction restricts the locations of the output vertices. For // example, there are predefined snap functions that require vertices to be @@ -172,19 +183,19 @@ class S2Builder { // snapped. The snap_radius must be at least as large as the maximum // distance between P and SnapPoint(P) for any point P. // - // 3. "max_edge_deviation", the maximum distance that edges can move when - // snapped. It is slightly larger than "snap_radius" because when a - // geodesic edge is snapped, the center of the edge moves further than - // its endpoints. This value is computed automatically by S2Builder. + // Note that the maximum distance that edge interiors can move when + // snapped is slightly larger than "snap_radius", and is returned by the + // function S2Builder::Options::max_edge_deviation() (see there for + // details). // - // 4. "min_vertex_separation", the guaranteed minimum distance between + // 3. "min_vertex_separation", the guaranteed minimum distance between // vertices in the output. This is generally a fraction of // "snap_radius" where the fraction depends on the snap function. // - // 5. A "min_edge_vertex_separation", the guaranteed minimum distance - // between edges and non-incident vertices in the output. This is - // generally a fraction of "snap_radius" where the fraction depends on - // the snap function. + // 4. "min_edge_vertex_separation", the guaranteed minimum distance between + // edges and non-incident vertices in the output. This is generally a + // fraction of "snap_radius" where the fraction depends on the snap + // function. // // It is important to note that SnapPoint() does not define the actual // mapping from input vertices to output vertices, since the points it @@ -219,14 +230,15 @@ class S2Builder { // crosses an edge. class SnapFunction { public: - virtual ~SnapFunction() {} + virtual ~SnapFunction() = default; - // The maximum distance that vertices can move when snapped. + // The maximum distance that vertices can move when snapped. The snap + // radius can be any value between zero and SnapFunction::kMaxSnapRadius(). // // If the snap radius is zero, then vertices are snapped together only if // they are identical. Edges will not be snapped to any vertices other // than their endpoints, even if there are vertices whose distance to the - // edge is zero, unless split_crossing_edges() is true. + // edge is zero, unless split_crossing_edges() is true (see below). // // REQUIRES: snap_radius() <= kMaxSnapRadius virtual S1Angle snap_radius() const = 0; @@ -234,11 +246,6 @@ class S2Builder { // The maximum supported snap radius (equivalent to about 7800km). static S1Angle kMaxSnapRadius(); - // The maximum distance that the center of an edge can move when snapped. - // This is slightly larger than "snap_radius" because when a geodesic edge - // is snapped, the center of the edge moves further than its endpoints. - S1Angle max_edge_deviation() const; - // The guaranteed minimum distance between vertices in the output. // This is generally some fraction of "snap_radius". virtual S1Angle min_vertex_separation() const = 0; @@ -281,13 +288,30 @@ class S2Builder { const SnapFunction& snap_function() const; void set_snap_function(const SnapFunction& snap_function); + // The maximum distance from snapped edge vertices to the original edge. + // This is the same as snap_function().snap_radius() except when + // split_crossing_edges() is true (see below), in which case the edge snap + // radius is increased by S2::kIntersectionError. + S1Angle edge_snap_radius() const; + + // The maximum distance that any point along an edge can move when snapped. + // It is slightly larger than edge_snap_radius() because when a geodesic + // edge is snapped, the edge center moves further than its endpoints. + // S2Builder ensures that this distance is at most 10% larger than + // edge_snap_radius(). + S1Angle max_edge_deviation() const; + // If true, then detect all pairs of crossing edges and eliminate them by - // adding a new vertex at their intersection point. + // adding a new vertex at their intersection point. See also the + // AddIntersection() method which allows intersection points to be added + // selectively. // - // When this option is true, the effective snap_radius() for edges is - // increased by S2::kIntersectionError to take into account the - // additional error when computing intersection points. In other words, - // edges may move by up to snap_radius() + S2::kIntersectionError. + // When this option if true, intersection_tolerance() is automatically set + // to a minimum of S2::kIntersectionError (see intersection_tolerance() + // for why this is necessary). Note that this means that edges can move + // by up to S2::kIntersectionError even when the specified snap radius is + // zero. The exact distance that edges can move is always given by + // max_edge_deviation() defined above. // // Undirected edges should always be used when the output is a polygon, // since splitting a directed loop at a self-intersection converts it into @@ -300,12 +324,59 @@ class S2Builder { // projection. You can minimize this problem by subdividing the input // edges so that the S2 edges (which are geodesics) stay close to the // original projected edges (which are curves on the sphere). This can - // be done using s2builderutil::EdgeSplitter(), for example. + // be done using S2EdgeTessellator, for example. // // DEFAULT: false bool split_crossing_edges() const; void set_split_crossing_edges(bool split_crossing_edges); + // Specifes the maximum allowable distance between a vertex added by + // AddIntersection() and the edge(s) that it is intended to snap to. This + // method must be called before AddIntersection() can be used. It has the + // effect of increasing the snap radius for edges (but not vertices) by + // the given distance. + // + // The intersection tolerance should be set to the maximum error in the + // intersection calculation used. For example, if S2::GetIntersection() + // is used then the error should be set to S2::kIntersectionError. If + // S2::GetPointOnLine() is used then the error should be set to + // S2::kGetPointOnLineError. If S2::Project() is used then the error + // should be set to S2::kProjectPerpendicularError. If more than one + // method is used then the intersection tolerance should be set to the + // maximum such error. + // + // The reason this option is necessary is that computed intersection + // points are not exact. For example, S2::GetIntersection(a, b, c, d) + // returns a point up to S2::kIntersectionError away from the true + // mathematical intersection of the edges AB and CD. Furthermore such + // intersection points are subject to further snapping in order to ensure + // that no pair of vertices is closer than the specified snap radius. For + // example, suppose the computed intersection point X of edges AB and CD + // is 1 nanonmeter away from both edges, and the snap radius is 1 meter. + // In that case X might snap to another vertex Y exactly 1 meter away, + // which would leave us with a vertex Y that could be up to 1.000000001 + // meters from the edges AB and/or CD. This means that AB and/or CD might + // not snap to Y leaving us with two edges that still cross each other. + // + // However if the intersection tolerance is set to 1 nanometer then the + // snap radius for edges is increased to 1.000000001 meters ensuring that + // both edges snap to a common vertex even in this worst case. (Tthis + // technique does not work if the vertex snap radius is increased as well; + // it requires edges and vertices to be handled differently.) + // + // Note that this option allows edges to move by up to the given + // intersection tolerance even when the snap radius is zero. The exact + // distance that edges can move is always given by max_edge_deviation() + // defined above. + // + // When split_crossing_edges() is true, the intersection tolerance is + // automatically set to a minimum of S2::kIntersectionError. A larger + // value can be specified by calling this method explicitly. + // + // DEFAULT: S1Angle::Zero() + S1Angle intersection_tolerance() const; + void set_intersection_tolerance(S1Angle intersection_tolerance); + // If true, then simplify the output geometry by replacing nearly straight // chains of short edges with a single long edge. // @@ -369,6 +440,35 @@ class S2Builder { bool idempotent() const; void set_idempotent(bool idempotent); + // Specifies that internal memory usage should be tracked using the given + // S2MemoryTracker. If a memory limit is specified and more more memory + // than this is required then an error will be returned. Example usage: + // + // S2MemoryTracker tracker; + // tracker.set_limit(500 << 20); // 500 MB + // S2Builder::Options options; + // options.set_memory_tracker(&tracker); + // S2Builder builder{options}; + // ... + // S2Error error; + // if (!builder.Build(&error)) { + // if (error.code() == S2Error::RESOURCE_EXHAUSTED) { + // S2_LOG(ERROR) << error; // Memory limit exceeded + // } + // } + // + // CAVEATS: + // + // - Memory allocated by the output S2Builder layers is not tracked. + // + // - While memory tracking is reasonably complete and accurate, it does + // not account for every last byte. It is intended only for the + // purpose of preventing clients from running out of memory. + // + // DEFAULT: nullptr (memory tracking disabled) + S2MemoryTracker* memory_tracker() const; + void set_memory_tracker(S2MemoryTracker* tracker); + // Options may be assigned and copied. Options(const Options& options); Options& operator=(const Options& options); @@ -376,13 +476,15 @@ class S2Builder { private: std::unique_ptr snap_function_; bool split_crossing_edges_ = false; + S1Angle intersection_tolerance_ = S1Angle::Zero(); bool simplify_edge_chains_ = false; bool idempotent_ = true; + S2MemoryTracker* memory_tracker_ = nullptr; }; + class Graph; // The following classes are only needed by Layer implementations. class GraphOptions; - class Graph; // For output layers that represent polygons, there is an ambiguity inherent // in spherical geometry that does not exist in planar geometry. Namely, if @@ -445,6 +547,7 @@ class S2Builder { // S2Error error; // S2_CHECK(builder.Build(&error)) << error; // Builds "line1" & "line2" class Layer; + void StartLayer(std::unique_ptr layer); // Adds a degenerate edge (representing a point) to the current layer. @@ -453,25 +556,56 @@ class S2Builder { // Adds the given edge to the current layer. void AddEdge(const S2Point& v0, const S2Point& v1); - // Adds the edges in the given polyline. (Note that if the polyline - // consists of 0 or 1 vertices, this method does nothing.) + // Adds the edges in the given polyline to the current layer. Note that + // polylines with 0 or 1 vertices are defined to have no edges. + void AddPolyline(S2PointSpan polyline); void AddPolyline(const S2Polyline& polyline); - // Adds the edges in the given loop. If the sign() of the loop is negative - // (i.e. this loop represents a hole within a polygon), the edge directions - // are automatically reversed to ensure that the polygon interior is always - // to the left of every edge. + // Adds the edges in the given loop to the current layer. Note that a loop + // consisting of one vertex adds a single degenerate edge. + // + // If the sign() of an S2Loop is negative (i.e. the loop represents a hole + // within a polygon), the edge directions are automatically reversed to + // ensure that the polygon interior is always to the left of every edge. + void AddLoop(S2PointLoopSpan loop); void AddLoop(const S2Loop& loop); - // Adds the loops in the given polygon. Loops representing holes have their - // edge directions automatically reversed as described for AddLoop(). Note - // that this method does not distinguish between the empty and full polygons, - // i.e. adding a full polygon has the same effect as adding an empty one. + // Adds the loops in the given polygon to the current layer. Loops + // representing holes have their edge directions automatically reversed as + // described for AddLoop(). Note that this method does not distinguish + // between the empty and full polygons, i.e. adding a full polygon has the + // same effect as adding an empty one. void AddPolygon(const S2Polygon& polygon); // Adds the edges of the given shape to the current layer. void AddShape(const S2Shape& shape); + // If "vertex" is the intersection point of two edges AB and CD (as computed + // by S2::GetIntersection()), this method ensures that AB and CD snap to a + // common vertex. (Note that the common vertex may be different than + // "vertex" in order to ensure that no pair of vertices is closer than the + // given snap radius.) Unlike Options::split_crossing_edges(), this method + // may be used to split crossing edge pairs selectively. + // + // This method can also be used to tessellate edges using S2::GetPointOnLine() + // or S2::Project() provided that a suitable intersection tolerance is + // specified (see intersection_tolerance() for details). + // + // This method implicitly overrides the idempotent() option, since adding an + // intersection point implies a desire to have nearby edges snapped to it + // even if these edges already satisfy the S2Builder output guarantees. + // (Otherwise for example edges would never be snapped to nearby + // intersection points when the snap radius is zero.) + // + // Note that unlike ForceVertex(), this method maintains all S2Builder + // guarantees regarding minimum vertex-vertex separation, minimum + // edge-vertex separation, and edge chain simplification. + // + // REQUIRES: options().intersection_tolerance() > S1Angle::Zero() + // REQUIRES: "vertex" was computed by S2::GetIntersection() (in order to + // guarantee that both edges snap to a common vertex) + void AddIntersection(const S2Point& vertex); + // 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 @@ -510,15 +644,30 @@ class S2Builder { // 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 - // prevent edges from being split by new vertices. + // preserve input edges, be aware that this option does not prevent edges from + // being split by new vertices. + // + // Forced vertices are subject to the following limitations: // - // Forced vertices are never snapped; if this is desired then you need to - // call options().snap_function().SnapPoint() explicitly. Forced vertices - // are also never simplified away (if simplify_edge_chains() is used). + // - Forced vertices are never snapped. This is true even when the given + // position is not allowed by the given snap function (e.g. you can force + // a vertex at a non-S2CellId center when using S2CellIdSnapFunction). + // If you want to ensure that forced vertices obey the snap function + // restrictions, you must call snap_function().SnapPoint() explicitly. // - // Caveat: Since this method can place vertices arbitrarily close together, - // S2Builder makes no minimum separation guaranteees with forced vertices. + // - There is no guaranteed minimum separation between pairs of forced + // vertices, i.e. snap_function().min_vertex_separation() does not apply. + // (This must be true because forced vertices can be placed arbitrarily.) + // + // - There is no guaranteed minimum separation between forced vertices and + // non-incident edges, i.e. snap_function().min_edge_vertex_separation() + // does not apply. + // + // - Forced vertices are never simplified away (i.e. when simplification is + // requested using options().simplify_edge_chains()). + // + // All other guarantees continue to hold, e.g. the input topology will always + // be preserved. void ForceVertex(const S2Point& vertex); // Every edge can have a set of non-negative integer labels attached to it. @@ -568,6 +717,18 @@ class S2Builder { // specified are preserved. void Reset(); + /////////////////////////////////////////////////////////////////////////// + // The following methods may be called at any time, including from + // S2Builder::Layer implementations. + + // Returns the number of input edges. + int num_input_edges() const; + + // Returns the endpoints of the given input edge. + // + // REQUIRES: 0 <= input_edge_id < num_input_edges() + S2Shape::Edge input_edge(int input_edge_id) const; + private: ////////////////////// Input Types ///////////////////////// // All types associated with the S2Builder inputs are prefixed with "Input". @@ -606,11 +767,61 @@ class S2Builder { // Identifies an output edge in a particular layer. using LayerEdgeId = std::pair; + ////////////////////// Internal Types ///////////////////////// class EdgeChainSimplifier; + // MemoryTracker is a helper class to measure S2Builder memory usage. It is + // based on a detailed analysis of the data structures used. This approach + // is fragile because the memory tracking code needs to be updated whenever + // S2Builder is modified, however S2Builder has been quite stable and this + // approach allows the memory usage to be measured quite accurately. + // + // CAVEATS: + // + // - Does not track memory used by edge labels. (It is tricky to do this + // accurately because they are stored in an IdSetLexicon, and labels + // are typically a tiny fraction of the total space used.) + // + // - Does not track memory used to represent layers internally. (The + // number of layers is typically small compared to the numbers of + // vertices and edges, and the amount of memory used by the Layer and + // IsFullPolygonPredicate objects is difficult to measure.) + // + // - Does not track memory used by the output layer Build() methods. (This + // includes both temporary space, e.g. due to calling S2Builder::Graph + // methods, and also any geometric objects created by these layers.) + class MemoryTracker : public S2MemoryTracker::Client { + public: + bool TallyEdgeSites(const gtl::compact_array& sites); + bool ReserveEdgeSite(gtl::compact_array* sites); + bool ClearEdgeSites(std::vector>* edge_sites); + + bool TallyIndexedSite(); + bool FixSiteIndexTally(const S2PointIndex& index); + bool DoneSiteIndex(const S2PointIndex& index); + + bool TallySimplifyEdgeChains( + const std::vector>& site_vertices, + const std::vector>& layer_edges); + + bool TallyFilterVertices(int num_sites, + const std::vector>& layer_edges); + bool DoneFilterVertices(); + + private: + // The amount of non-inline memory used to store edge sites. + int64 edge_sites_bytes_ = 0; + + // The amount of memory used by the S2PointIndex for sites. + int64 site_index_bytes_ = 0; + + // The amount of temporary memory used by Graph::FilterVertices(). + int64 filter_vertices_bytes_ = 0; + }; + InputVertexId AddVertex(const S2Point& v); void ChooseSites(); - void CopyInputEdges(); + void ChooseAllVerticesAsSites(); std::vector SortInputVertices(); void AddEdgeCrossings(const MutableS2ShapeIndex& input_edge_index); void AddForcedSites(S2PointIndex* site_index); @@ -620,21 +831,19 @@ class S2Builder { void CollectSiteEdges(const S2PointIndex& site_index); void SortSitesByDistance(const S2Point& x, gtl::compact_array* sites) const; + void InsertSiteByDistance(SiteId new_site_id, const S2Point& x, + gtl::compact_array* sites); void AddExtraSites(const MutableS2ShapeIndex& input_edge_index); - void MaybeAddExtraSites(InputEdgeId edge_id, - InputEdgeId max_edge_id, - const std::vector& chain, + void MaybeAddExtraSites(InputEdgeId edge_id, const std::vector& chain, const MutableS2ShapeIndex& input_edge_index, - std::vector* snap_queue); + absl::flat_hash_set* edges_to_resnap); void AddExtraSite(const S2Point& new_site, - InputEdgeId max_edge_id, const MutableS2ShapeIndex& input_edge_index, - std::vector* snap_queue); + absl::flat_hash_set* edges_to_resnap); S2Point GetSeparationSite(const S2Point& site_to_avoid, const S2Point& v0, const S2Point& v1, InputEdgeId input_edge_id) const; - S2Point GetCoverageEndpoint(const S2Point& p, const S2Point& x, - const S2Point& y, const S2Point& n) const; + S2Point GetCoverageEndpoint(const S2Point& p, const S2Point& n) const; void SnapEdge(InputEdgeId e, std::vector* chain) const; void BuildLayers(); @@ -646,7 +855,7 @@ class S2Builder { InputEdgeId begin, InputEdgeId end, const GraphOptions& options, std::vector* edges, std::vector* input_edge_ids, IdSetLexicon* input_edge_id_set_lexicon, - std::vector>* site_vertices) const; + std::vector>* site_vertices); void MaybeAddInputVertex( InputVertexId v, SiteId id, std::vector>* site_vertices) const; @@ -657,7 +866,7 @@ class S2Builder { const std::vector>& site_vertices, std::vector>* layer_edges, std::vector>* layer_input_edge_ids, - IdSetLexicon* input_edge_id_set_lexicon) const; + IdSetLexicon* input_edge_id_set_lexicon); void MergeLayerEdges( const std::vector>& layer_edges, const std::vector>& layer_input_edge_ids, @@ -681,6 +890,15 @@ class S2Builder { // edges are being split at crossings. S1ChordAngle edge_snap_radius_ca_; + // True if we need to check that snapping has not changed the input topology + // around any vertex (i.e. Voronoi site). Normally this is only necessary for + // forced vertices, but if the snap radius is very small (e.g., zero) and + // split_crossing_edges() is true then we need to do this for all vertices. + // In all other situations, any snapped edge that crosses a vertex will also + // be closer than min_edge_vertex_separation() to that vertex, which will + // cause us to add a separation site anyway. + bool check_all_site_crossings_; + S1Angle max_edge_deviation_; S1ChordAngle edge_site_query_radius_ca_; S1ChordAngle min_edge_length_to_split_ca_; @@ -761,6 +979,9 @@ class S2Builder { // the "sites to avoid" (needed for simplification). std::vector> edge_sites_; + // An object to track the memory usage of this class. + MemoryTracker tracker_; + S2Builder(const S2Builder&) = delete; S2Builder& operator=(const S2Builder&) = delete; }; @@ -775,9 +996,9 @@ class S2Builder { class S2Builder::GraphOptions { public: using EdgeType = S2Builder::EdgeType; - enum class DegenerateEdges; - enum class DuplicateEdges; - enum class SiblingPairs; + enum class DegenerateEdges : uint8; + enum class DuplicateEdges : uint8; + enum class SiblingPairs : uint8; // All S2Builder::Layer subtypes should specify GraphOptions explicitly // using this constructor, rather than relying on default values. @@ -800,10 +1021,13 @@ class S2Builder::GraphOptions { // Specifies whether the S2Builder input edges should be treated as // undirected. If true, then all input edges are duplicated into pairs - // consisting of an edge and a sibling (reverse) edge. The layer - // implementation is responsible for ensuring that exactly one edge from - // each pair is used in the output, i.e. *only half* of the graph edges will - // be used. (Note that some values of the sibling_pairs() option + // consisting of an edge and a sibling (reverse) edge. Note that the + // automatically created sibling edge has an empty set of labels and does + // not have an associated InputEdgeId. + // + // The layer implementation is responsible for ensuring that exactly one + // edge from each pair is used in the output, i.e. *only half* of the graph + // edges will 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.) // @@ -820,10 +1044,10 @@ class S2Builder::GraphOptions { // do not support degeneracies, such as S2PolygonLayer. // // DISCARD_EXCESS: Discards all degenerate edges that are connected to - // non-degenerate edges. (Any remaining duplicate edges can - // be merged using DuplicateEdges::MERGE.) This is useful - // for simplifying polygons while ensuring that loops that - // collapse to a single point do not disappear. + // non-degenerate edges and merges any remaining duplicate + // degenerate edges. This is useful for simplifying + // polygons while ensuring that loops that collapse to a + // single point do not disappear. // // KEEP: Keeps all degenerate edges. Be aware that this may create many // redundant edges when simplifying geometry (e.g., a polyline of the @@ -831,7 +1055,7 @@ class S2Builder::GraphOptions { // for algorithms that require an output edge for every input edge. // // DEFAULT: DegenerateEdges::KEEP - enum class DegenerateEdges { DISCARD, DISCARD_EXCESS, KEEP }; + enum class DegenerateEdges : uint8 { DISCARD, DISCARD_EXCESS, KEEP }; DegenerateEdges degenerate_edges() const; void set_degenerate_edges(DegenerateEdges degenerate_edges); @@ -842,7 +1066,7 @@ class S2Builder::GraphOptions { // input edge ids. // // DEFAULT: DuplicateEdges::KEEP - enum class DuplicateEdges { MERGE, KEEP }; + enum class DuplicateEdges : uint8 { MERGE, KEEP }; DuplicateEdges duplicate_edges() const; void set_duplicate_edges(DuplicateEdges duplicate_edges); @@ -870,8 +1094,8 @@ class S2Builder::GraphOptions { // // CREATE: Ensures that all edges have a sibling edge by creating them if // necessary. This is useful with polygon meshes where the input - // polygons do not cover the entire sphere. Such edges always - // have an empty set of labels. + // polygons do not cover the entire sphere. Such edges always have + // an empty set of labels and do not have an associated InputEdgeId. // // If edge_type() is EdgeType::UNDIRECTED, a sibling edge pair is considered // to consist of four edges (two duplicate edges and their siblings), since @@ -895,23 +1119,27 @@ class S2Builder::GraphOptions { // when duplicate edges are present, all of the corresponding edge labels // are merged together and assigned to the remaining edges. (This avoids // the problem of having to decide which edges are discarded.) Note that - // this merging takes place even when all copies of an edge are kept, and - // that even labels attached to duplicate degenerate edges are merged. For - // example, consider the graph {AB1, AB2, BA3, CD4, CD5} (where XYn denotes - // an edge from X to Y with label "n"). With SiblingPairs::DISCARD, we need - // to discard one of the copies of AB. But which one? Rather than choosing - // 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). + // this merging takes place even when all copies of an edge are kept. For + // example, consider the graph {AB1, AB2, AB3, BA4, CD5, CD6} (where XYn + // denotes an edge from X to Y with label "n"). With SiblingPairs::DISCARD, + // we need to discard one of the copies of AB. But which one? Rather than + // choosing arbitrarily, instead we merge the labels of all duplicate edges + // (even ones where no sibling pairs were discarded), yielding {AB123, + // AB123, CD45, CD45} (assuming that duplicate edges are being kept). + // Notice that the labels of duplicate edges are merged even if no siblings + // were discarded (such as CD5, CD6 in this example), and that this would + // happen even with duplicate degenerate edges (e.g. the edges EE7, EE8). // // DEFAULT: SiblingPairs::KEEP - enum class SiblingPairs { DISCARD, DISCARD_EXCESS, KEEP, REQUIRE, CREATE }; + enum class SiblingPairs : uint8 { + DISCARD, DISCARD_EXCESS, KEEP, REQUIRE, CREATE + }; SiblingPairs sibling_pairs() const; void set_sibling_pairs(SiblingPairs sibling_pairs); - // This is a specialized option that is only needed by clients want to work - // with the graphs for multiple layers at the same time (e.g., in order to - // check whether the same edge is present in two different graphs). [Note + // This is a specialized option that is only needed by clients that want to + // work with the graphs for multiple layers at the same time (e.g., in order + // to check whether the same edge is present in two different graphs). [Note // that if you need to do this, usually it is easier just to build a single // graph with suitable edge labels.] // @@ -974,6 +1202,17 @@ inline void S2Builder::Options::set_split_crossing_edges( split_crossing_edges_ = split_crossing_edges; } +inline S1Angle S2Builder::Options::intersection_tolerance() const { + if (!split_crossing_edges()) return intersection_tolerance_; + return std::max(intersection_tolerance_, S2::kIntersectionError); +} + +inline void S2Builder::Options::set_intersection_tolerance( + S1Angle intersection_tolerance) { + S2_DCHECK_GE(intersection_tolerance, S1Angle::Zero()); + intersection_tolerance_ = intersection_tolerance; +} + inline bool S2Builder::Options::simplify_edge_chains() const { return simplify_edge_chains_; } @@ -998,6 +1237,15 @@ inline void S2Builder::Options::set_idempotent(bool idempotent) { idempotent_ = idempotent; } +inline S2MemoryTracker* S2Builder::Options::memory_tracker() const { + return memory_tracker_; +} + +inline void S2Builder::Options::set_memory_tracker( + S2MemoryTracker* tracker) { + memory_tracker_ = tracker; +} + inline S2Builder::GraphOptions::EdgeType S2Builder::GraphOptions::edge_type() const { return edge_type_; @@ -1054,4 +1302,14 @@ inline void S2Builder::AddPoint(const S2Point& v) { AddEdge(v, v); } +inline int S2Builder::num_input_edges() const { + return input_edges_.size(); +} + +inline S2Shape::Edge S2Builder::input_edge(int input_edge_id) const { + const InputEdge& edge = input_edges_[input_edge_id]; + return S2Shape::Edge(input_vertices_[edge.first], + input_vertices_[edge.second]); +} + #endif // S2_S2BUILDER_H_ diff --git a/src/s2/s2builder_graph.cc b/src/s2/s2builder_graph.cc index 3d513535..fb6f6014 100644 --- a/src/s2/s2builder_graph.cc +++ b/src/s2/s2builder_graph.cc @@ -18,15 +18,19 @@ #include "s2/s2builder_graph.h" #include +#include #include -#include #include +#include #include -#include "s2/base/logging.h" + +#include "s2/base/integral_types.h" #include "absl/container/btree_map.h" #include "s2/id_set_lexicon.h" #include "s2/s2builder.h" #include "s2/s2error.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" #include "s2/s2predicates.h" using std::make_pair; @@ -72,6 +76,12 @@ vector Graph::GetInEdgeIds() const { vector Graph::GetSiblingMap() const { vector in_edge_ids = GetInEdgeIds(); MakeSiblingMap(&in_edge_ids); + // Validates the sibling map, and indirectly the edge ordering comparator, + // which must break ties on equal edges correctly for the sibling map to be + // created correctly. + for (EdgeId e = 0; e < num_edges(); ++e) { + S2_DCHECK(e == in_edge_ids[in_edge_ids[e]]); + } return in_edge_ids; } @@ -172,15 +182,13 @@ vector Graph::GetInputEdgeOrder( // A struct for sorting the incoming and outgoing edges around a vertex "v0". struct VertexEdge { - VertexEdge(bool _incoming, Graph::EdgeId _index, - Graph::VertexId _endpoint, int32 _rank) - : incoming(_incoming), index(_index), - endpoint(_endpoint), rank(_rank) { - } + VertexEdge(bool _incoming, Graph::EdgeId _index, Graph::VertexId _endpoint, + int32 _rank) + : incoming(_incoming), index(_index), endpoint(_endpoint), rank(_rank) {} bool incoming; // Is this an incoming edge to "v0"? Graph::EdgeId index; // Index of this edge in "edges_" or "in_edge_ids" Graph::VertexId endpoint; // The other (not "v0") endpoint of this edge - int32 rank; // Secondary key for edges with the same endpoint + int32 rank; // Secondary key for edges with the same endpoint }; // Given a set of duplicate outgoing edges (v0, v1) and a set of duplicate @@ -327,9 +335,9 @@ void Graph::CanonicalizeLoopOrder(const vector& min_input_ids, // This has the advantage that if an undirected loop is assembled with the // wrong orientation and later inverted (e.g. by S2Polygon::InitOriented), // we still end up preserving the original cyclic vertex order. - int pos = 0; + size_t pos = 0; bool saw_gap = false; - for (int i = 1; i < loop->size(); ++i) { + for (size_t i = 1; i < loop->size(); ++i) { int cmp = min_input_ids[(*loop)[i]] - min_input_ids[(*loop)[pos]]; if (cmp < 0) { saw_gap = true; @@ -346,9 +354,10 @@ void Graph::CanonicalizeVectorOrder(const vector& min_input_ids, vector>* chains) { std::sort(chains->begin(), chains->end(), [&min_input_ids](const vector& a, const vector& b) { - return min_input_ids[a[0]] < min_input_ids[b[0]]; - }); -} + // Comparison function ensures sort is stable. + return make_pair(min_input_ids[a[0]], a[0]) < + make_pair(min_input_ids[b[0]], b[0]); + });} bool Graph::GetDirectedLoops(LoopType loop_type, vector* loops, S2Error* error) const { @@ -414,10 +423,9 @@ bool Graph::GetDirectedComponents( options_.sibling_pairs() == SiblingPairs::CREATE); S2_DCHECK(options_.edge_type() == EdgeType::DIRECTED); // Implied by above. - vector sibling_map = GetInEdgeIds(); + vector sibling_map = GetSiblingMap(); vector left_turn_map; if (!GetLeftTurnMap(sibling_map, &left_turn_map, error)) return false; - MakeSiblingMap(&sibling_map); vector min_input_ids = GetMinInputEdgeIds(); vector frontier; // Unexplored sibling edges. @@ -427,24 +435,24 @@ bool Graph::GetDirectedComponents( if (degenerate_boundaries == DegenerateBoundaries::DISCARD) { path_index.assign(num_edges(), -1); } - for (EdgeId min_start = 0; min_start < num_edges(); ++min_start) { - if (left_turn_map[min_start] < 0) continue; // Already used. + for (EdgeId start = 0; start < num_edges(); ++start) { + if (left_turn_map[start] < 0) continue; // Already used. // Build a connected component by keeping a stack of unexplored siblings // of the edges used so far. DirectedComponent component; - frontier.push_back(min_start); + frontier.push_back(start); while (!frontier.empty()) { - EdgeId start = frontier.back(); + EdgeId e = frontier.back(); frontier.pop_back(); - if (left_turn_map[start] < 0) continue; // Already used. + if (left_turn_map[e] < 0) continue; // Already used. - // Build a path by making left turns at each vertex until we return to - // "start". Whenever we encounter an edge that is a sibling of an edge + // Build a path by making left turns at each vertex until we complete a + // loop. Whenever we encounter an edge that is a sibling of an edge // that is already on the path, we "peel off" a loop consisting of any // edges that were between these two edges. vector path; - for (EdgeId e = start, next; left_turn_map[e] >= 0; e = next) { + for (EdgeId next; left_turn_map[e] >= 0; e = next) { path.push_back(e); next = left_turn_map[e]; left_turn_map[e] = -1; @@ -460,7 +468,7 @@ bool Graph::GetDirectedComponents( // Common special case: the edge and its sibling are adjacent, in // which case we can simply remove them from the path and continue. - if (sibling_index == path.size() - 2) { + if (static_cast(sibling_index) == path.size() - 2) { path.resize(sibling_index); // We don't need to update "path_index" for these two edges // because both edges of the sibling pair have now been used. @@ -752,7 +760,7 @@ vector Graph::PolylineBuilder::BuildWalks() { // start from the edge with minimum input edge id. If the minimal input // edge was split into several edges, then we start from the first edge of // the chain. - for (int i = 0; i < edges.size() && edges_left_ > 0; ++i) { + for (size_t i = 0; i < edges.size() && edges_left_ > 0; ++i) { EdgeId e = edges[i]; if (used_[e]) continue; @@ -763,7 +771,8 @@ vector Graph::PolylineBuilder::BuildWalks() { VertexId v = g_.edge(e).first; InputEdgeId id = min_input_ids_[e]; int excess = 0; - for (int j = i; j < edges.size() && min_input_ids_[edges[j]] == id; ++j) { + for (size_t j = i; j < edges.size() && min_input_ids_[edges[j]] == id; + ++j) { EdgeId e2 = edges[j]; if (used_[e2]) continue; if (g_.edge(e2).first == v) ++excess; @@ -820,7 +829,7 @@ void Graph::PolylineBuilder::MaximizeWalk(EdgePolyline* polyline) { // and insert it into the polyline. (The walk is guaranteed to be a loop // because this method is only called when all vertices have equal numbers // of unused incoming and outgoing edges.) - for (int i = 0; i <= polyline->size(); ++i) { + for (size_t i = 0; i <= polyline->size(); ++i) { VertexId v = (i == 0 ? g_.edge((*polyline)[i]).first : g_.edge((*polyline)[i - 1]).second); for (EdgeId e : out_.edge_ids(v)) { @@ -863,18 +872,43 @@ class Graph::EdgeProcessor { vector tmp_ids_; }; -void Graph::ProcessEdges( - GraphOptions* options, std::vector* edges, - std::vector* input_ids, IdSetLexicon* id_set_lexicon, - S2Error* error) { - EdgeProcessor processor(*options, edges, input_ids, id_set_lexicon); - processor.Run(error); +void Graph::ProcessEdges(GraphOptions* options, vector* edges, + vector* input_ids, + IdSetLexicon* id_set_lexicon, S2Error* error, + S2MemoryTracker::Client* tracker) { + // Graph::EdgeProcessor uses 8 bytes per input edge (out_edges_ and + // in_edges_) plus 12 bytes per output edge (new_edges_, new_input_ids_). + // For simplicity we assume that num_input_edges == num_output_edges, since + // Graph:EdgeProcessor does not increase the number of edges except possibly + // in the case of SiblingPairs::CREATE (which we ignore). + // + // vector out_edges_; // Graph::EdgeProcessor + // vector in_edges_; // Graph::EdgeProcessor + // vector new_edges_; // Graph::EdgeProcessor + // vector new_input_ids_; // Graph::EdgeProcessor + // + // EdgeProcessor discards the "edges" and "input_ids" vectors and replaces + // them with new vectors that could be larger or smaller. To handle this + // correctly, we untally these vectors now and retally them at the end. + const int64 kFinalPerEdge = sizeof(Edge) + sizeof(InputEdgeIdSetId); + const int64 kTempPerEdge = kFinalPerEdge + 2 * sizeof(EdgeId); + if (tracker) { + tracker->TallyTemp(edges->size() * kTempPerEdge); + tracker->Tally(-edges->capacity() * kFinalPerEdge); + } + if (!tracker || tracker->ok()) { + EdgeProcessor processor(*options, edges, input_ids, id_set_lexicon); + processor.Run(error); + } // Certain values of sibling_pairs() discard half of the edges and change // the edge_type() to DIRECTED (see the description of GraphOptions). if (options->sibling_pairs() == SiblingPairs::REQUIRE || options->sibling_pairs() == SiblingPairs::CREATE) { options->set_edge_type(EdgeType::DIRECTED); } + if (tracker && !tracker->Tally(edges->capacity() * kFinalPerEdge)) { + *error = tracker->error(); + } } Graph::EdgeProcessor::EdgeProcessor(const GraphOptions& options, @@ -959,6 +993,7 @@ void Graph::EdgeProcessor::Run(S2Error* error) { int n_out = out - out_begin; int n_in = in - in_begin; if (edge.first == edge.second) { + // This is a degenerate edge. S2_DCHECK_EQ(n_out, n_in); if (options_.degenerate_edges() == DegenerateEdges::DISCARD) { continue; @@ -972,15 +1007,18 @@ void Graph::EdgeProcessor::Run(S2Error* error) { (in < num_edges && edges_[in_edges_[in]].second == edge.first))) { continue; // There were non-degenerate incident edges, so discard. } + // DegenerateEdges::DISCARD_EXCESS also merges degenerate edges. + bool merge = + (options_.duplicate_edges() == DuplicateEdges::MERGE || + options_.degenerate_edges() == DegenerateEdges::DISCARD_EXCESS); if (options_.edge_type() == EdgeType::UNDIRECTED && (options_.sibling_pairs() == SiblingPairs::REQUIRE || options_.sibling_pairs() == SiblingPairs::CREATE)) { // When we have undirected edges and are guaranteed to have siblings, // we cut the number of edges in half (see s2builder.h). S2_DCHECK_EQ(0, n_out & 1); // Number of edges is always even. - AddEdges(options_.duplicate_edges() == DuplicateEdges::MERGE ? - 1 : (n_out / 2), edge, MergeInputIds(out_begin, out)); - } else if (options_.duplicate_edges() == DuplicateEdges::MERGE) { + AddEdges(merge ? 1 : (n_out / 2), edge, MergeInputIds(out_begin, out)); + } else if (merge) { AddEdges(options_.edge_type() == EdgeType::UNDIRECTED ? 2 : 1, edge, MergeInputIds(out_begin, out)); } else if (options_.sibling_pairs() == SiblingPairs::DISCARD || @@ -1047,13 +1085,14 @@ void Graph::EdgeProcessor::Run(S2Error* error) { } } edges_.swap(new_edges_); - edges_.shrink_to_fit(); input_ids_.swap(new_input_ids_); + edges_.shrink_to_fit(); input_ids_.shrink_to_fit(); } +// LINT.IfChange vector Graph::FilterVertices(const vector& vertices, - std::vector* edges, + vector* edges, vector* tmp) { // Gather the vertices that are actually used. vector used; @@ -1071,7 +1110,7 @@ vector Graph::FilterVertices(const vector& vertices, vector& vmap = *tmp; vmap.resize(vertices.size()); vector new_vertices(used.size()); - for (int i = 0; i < used.size(); ++i) { + for (size_t i = 0; i < used.size(); ++i) { new_vertices[i] = vertices[used[i]]; vmap[used[i]] = i; } @@ -1082,3 +1121,35 @@ vector Graph::FilterVertices(const vector& vertices, } return new_vertices; } +// LINT.ThenChange(s2builder.cc:TallyFilterVertices) + +Graph Graph::MakeSubgraph( + GraphOptions new_options, vector* new_edges, + vector* new_input_edge_id_set_ids, + IdSetLexicon* new_input_edge_id_set_lexicon, + IsFullPolygonPredicate is_full_polygon_predicate, + S2Error* error, S2MemoryTracker::Client* tracker) const { + if (options().edge_type() == EdgeType::DIRECTED && + new_options.edge_type() == EdgeType::UNDIRECTED) { + // Create a reversed edge for every edge. + int n = new_edges->size(); + if (tracker == nullptr) { + new_edges->reserve(2 * n); + new_input_edge_id_set_ids->reserve(2 * n); + } else if (!tracker->AddSpaceExact(new_edges, n) || + !tracker->AddSpaceExact(new_input_edge_id_set_ids, n)) { + *error = tracker->error(); + return Graph(); + } + for (int i = 0; i < n; ++i) { + new_edges->push_back(Graph::reverse((*new_edges)[i])); + new_input_edge_id_set_ids->push_back(IdSetLexicon::EmptySetId()); + } + } + Graph::ProcessEdges(&new_options, new_edges, new_input_edge_id_set_ids, + new_input_edge_id_set_lexicon, error, tracker); + if (tracker && !tracker->ok()) return Graph(); // Graph would be invalid. + return Graph(new_options, &vertices(), new_edges, new_input_edge_id_set_ids, + new_input_edge_id_set_lexicon, &label_set_ids(), + &label_set_lexicon(), std::move(is_full_polygon_predicate)); +} diff --git a/src/s2/s2builder_graph.h b/src/s2/s2builder_graph.h index b1ba3e43..21e2d381 100644 --- a/src/s2/s2builder_graph.h +++ b/src/s2/s2builder_graph.h @@ -18,15 +18,20 @@ #ifndef S2_S2BUILDER_GRAPH_H_ #define S2_S2BUILDER_GRAPH_H_ +#include #include #include #include +#include #include #include + #include "s2/base/integral_types.h" #include "s2/id_set_lexicon.h" #include "s2/s2builder.h" #include "s2/s2error.h" +#include "s2/s2memory_tracker.h" +#include "s2/s2point.h" // An S2Builder::Graph represents a collection of snapped edges that is passed // to a Layer for assembly. (Example layers include polygons, polylines, and @@ -95,9 +100,11 @@ class S2Builder::Graph { // "label_set_ids": // - a vector indexed by InputEdgeId that allows access to the set of // labels that were attached to the given input edge, by looking up the - // returned value (a LabelSetId) in the "label_set_lexicon". + // returned value (a LabelSetId) in the "label_set_lexicon". This + // vector may be empty to indicate that no labels are present. // "label_set_lexicon": - // - a class that maps a LabelSetId to a set of S2Builder::Labels. + // - a class that maps a LabelSetId to a set of S2Builder::Labels. (Must + // be provided even if no labels are present.) // "is_full_polygon_predicate": // - a predicate called to determine whether a graph consisting only of // polygon degeneracies represents the empty polygon or the full polygon @@ -143,8 +150,9 @@ class S2Builder::Graph { // from EdgeId to the sibling EdgeId. This method is identical to // GetInEdgeIds() except that (1) it requires edges to have siblings, and // (2) undirected degenerate edges are grouped together in pairs such that - // one edge is the sibling of the other. Handles duplicate edges correctly - // and is also consistent with GetLeftTurnMap(). + // one edge is the sibling of the other. (The sibling of a directed + // degenerate edge is itself.) Handles duplicate edges correctly and is + // also consistent with GetLeftTurnMap(). // // REQUIRES: An option is chosen that guarantees sibling pairs: // (options.sibling_pairs() == { REQUIRE, CREATE } || @@ -153,13 +161,13 @@ class S2Builder::Graph { // Like GetSiblingMap(), but constructs the map starting from the vector of // incoming edge ids returned by GetInEdgeIds(). (This operation is a no-op - // except unless undirected degenerate edges are present, in which case such - // edges are grouped together in pairs to satisfy the requirement that every - // edge must have a sibling edge.) + // unless undirected degenerate edges are present, in which case such edges + // are grouped together in pairs to satisfy the requirement that every edge + // must have a sibling edge.) void MakeSiblingMap(std::vector* in_edge_ids) const; - class VertexOutMap; // Forward declaration class VertexInMap; // Forward declaration + class VertexOutMap; // Forward declaration // A helper class for VertexOutMap that represents the outgoing edges // from a given vertex. @@ -268,12 +276,12 @@ class S2Builder::Graph { }; // Defines a value larger than any valid InputEdgeId. - static const InputEdgeId kMaxInputEdgeId = + static constexpr InputEdgeId kMaxInputEdgeId = std::numeric_limits::max(); // The following value of InputEdgeId means that an edge does not // corresponds to any input edge. - static const InputEdgeId kNoInputEdgeId = kMaxInputEdgeId - 1; + static constexpr InputEdgeId kNoInputEdgeId = kMaxInputEdgeId - 1; // Returns the set of input edge ids that were snapped to the given // edge. ("Input edge ids" are assigned to input edges sequentially in @@ -345,9 +353,10 @@ class S2Builder::Graph { // labels from one or both siblings are returned. void Init(const Graph& g, EdgeType edge_type); - // Returns the set of labels associated with edge "e" (and also the labels - // associated with the sibling of "e" if edge_type() is UNDIRECTED). - // Labels are sorted and duplicate labels are automatically removed. + // Fills "labels" with the set of labels associated with edge "e" (and also + // the labels associated with the sibling of "e" if edge_type() is + // UNDIRECTED). Labels are sorted and duplicate labels are automatically + // removed. // // This method uses an output parameter rather than returning by value in // order to avoid allocating a new vector on every call to this method. @@ -361,6 +370,7 @@ class S2Builder::Graph { // Returns the set of labels associated with a given input edge. Example: // for (Label label : g.labels(input_edge_id)) { ... } + // See also LabelFetcher, which returns the labels for a given graph edge. IdSetLexicon::IdSet labels(InputEdgeId e) const; // Low-level method that returns an integer representing the set of @@ -369,7 +379,8 @@ class S2Builder::Graph { LabelSetId label_set_id(InputEdgeId e) const; // Low-level method that returns a vector where each element represents the - // set of labels associated with a particular output edge. + // set of labels associated with a particular input edge. Note that this + // vector may be empty, which indicates that no labels are present. const std::vector& label_set_ids() const; // Returns a mapping from a LabelSetId to a set of labels. @@ -385,11 +396,11 @@ class S2Builder::Graph { // (see s2builder.h for details). const IsFullPolygonPredicate& is_full_polygon_predicate() const; - // Returns a map "m" that maps each edge e=(v0,v1) to the following outgoing - // edge around "v1" in clockwise order. (This corresponds to making a "left - // turn" at the vertex.) By starting at a given edge and making only left - // turns, you can construct a loop whose interior does not contain any edges - // in the same connected component. + // Fills in "left_turn_map" so it maps each edge e=(v0,v1) to the following + // outgoing edge around "v1" in clockwise order. (This corresponds to making + // a "left turn" at the vertex.) By starting at a given edge and making only + // left turns, you can construct a loop whose interior does not contain any + // edges in the same connected component. // // If the incoming and outgoing edges around a vertex do not alternate // perfectly (e.g., there are two incoming edges in a row), then adjacent @@ -435,7 +446,7 @@ class S2Builder::Graph { const std::vector& min_input_ids, std::vector>* chains); - // A loop consisting of a sequence of edges. + // A loop consisting of a sequence of edge ids. using EdgeLoop = std::vector; // Indicates whether loops should be simple cycles (no repeated vertices) or @@ -585,19 +596,28 @@ class S2Builder::Graph { // should already have been transformed into a pair of directed edges. // // "input_ids" is a vector of the same length as "edges" that indicates - // which input edges were snapped to each edge. This vector is also updated - // appropriately as edges are discarded, merged, etc. + // which input edges were snapped to each edge, by mapping each edge id to a + // set of input edge ids in "id_set_lexicon". This vector and the lexicon are + // also updated appropriately as edges are discarded, merged, etc. // - // Note that "options" may be modified by this method: in particular, the - // edge_type() can be changed if sibling_pairs() is CREATE or REQUIRE (see - // the description of S2Builder::GraphOptions). + // Note that "options" may be modified by this method: in particular, if + // edge_type() is UNDIRECTED and sibling_pairs() is CREATE or REQUIRE, then + // half of the edges in each direction will be discarded and edge_type() + // will be changed to DIRECTED (see S2Builder::GraphOptions::SiblingPairs). + // + // If "tracker" is provided then the memory usage of this method is tracked + // and an error is returned if the specified memory limit would be exceeded. + // This option requires that "new_edges" and "new_input_edge_id_set_ids" are + // already being tracked, i.e. their current memory usage is reflected in + // "tracker". Note that "id_set_lexicon" typically uses a negligible amount + // of memory and is not tracked. static void ProcessEdges( GraphOptions* options, std::vector* edges, std::vector* input_ids, IdSetLexicon* id_set_lexicon, - S2Error* error); + S2Error* error, S2MemoryTracker::Client* tracker = nullptr); // Given a set of vertices and edges, removes all vertices that do not have - // any edges and returned the new, minimal set of vertices. Also updates + // any edges and returns the new, minimal set of vertices. Also updates // each edge in "edges" to correspond to the new vertex numbering. (Note // that this method does *not* merge duplicate vertices, it simply removes // vertices of degree zero.) @@ -623,6 +643,42 @@ class S2Builder::Graph { static bool StableLessThan(const Edge& a, const Edge& b, EdgeId ai, EdgeId bi); + // Constructs a new graph with the given GraphOptions and containing the + // given edges. Each edge is associated with a (possibly empty) set of + // input edges as specified by new_input_edge_id_set_ids (which must be the + // same length as "new_edges") and the given IdSetLexicon (which allows + // looking up the set of input edge ids associated with a graph edge). + // Finally, the subgraph may also specify a new IsFullPolygonPredicate + // (which is used to distinguish an empty polygon possibly with degenerate + // shells from a full polygon possibly with degenerate holes). + // + // The output graph has the same set of vertices and edge labels as the + // input graph (noting that edge labels are associated with *input* edges, + // not graph edges). + // + // If new_options.edge_type() is UNDIRECTED then note the following: + // + // - If this->options().edge_type() is DIRECTED then each input edge will + // be transformed into a pair of directed edges before calling + // ProcessEdges() above. + // + // - If new_options.sibling_pairs() is CREATE or REQUIRE then ProcessEdges() + // will discard half of the edges in each direction and change edge_type() + // to DIRECTED (see S2Builder::GraphOptions::SiblingPairs). + // + // If "tracker" is provided then the memory usage of this method is tracked + // and an error is returned if the specified memory limit would be exceeded. + // This option requires that "new_edges" and "new_input_edge_id_set_ids" are + // already being tracked, i.e. their current memory usage is reflected in + // "tracker". Note that "id_set_lexicon" typically uses a negligible amount + // of memory and is not tracked. + Graph MakeSubgraph( + GraphOptions new_options, std::vector* new_edges, + std::vector* new_input_edge_id_set_ids, + IdSetLexicon* new_input_edge_id_set_lexicon, + IsFullPolygonPredicate is_full_polygon_predicate, + S2Error* error, S2MemoryTracker::Client* tracker = nullptr) const; + private: class EdgeProcessor; class PolylineBuilder; @@ -758,12 +814,14 @@ inline const IdSetLexicon& S2Builder::Graph::input_edge_id_set_lexicon() const { return *input_edge_id_set_lexicon_; } -inline IdSetLexicon::IdSet S2Builder::Graph::labels(LabelSetId id) const { - return label_set_lexicon().id_set(label_set_ids()[id]); +inline IdSetLexicon::IdSet S2Builder::Graph::labels(InputEdgeId e) const { + return label_set_lexicon().id_set(label_set_id(e)); } -inline S2Builder::LabelSetId S2Builder::Graph::label_set_id(EdgeId e) const { - return label_set_ids()[e]; +inline S2Builder::LabelSetId S2Builder::Graph::label_set_id(InputEdgeId e) + const { + return label_set_ids().empty() ? IdSetLexicon::EmptySetId() + : label_set_ids()[e]; } inline const std::vector& diff --git a/src/s2/s2builder_layer.h b/src/s2/s2builder_layer.h index 051f07ea..b4d6039f 100644 --- a/src/s2/s2builder_layer.h +++ b/src/s2/s2builder_layer.h @@ -18,7 +18,9 @@ #ifndef S2_S2BUILDER_LAYER_H_ #define S2_S2BUILDER_LAYER_H_ +#include "s2/s2builder.h" #include "s2/s2builder_graph.h" +#include "s2/s2error.h" // This class is not needed by ordinary S2Builder clients. It is only // necessary if you wish to implement a new S2Builder::Layer subtype. @@ -31,7 +33,7 @@ class S2Builder::Layer { using Label = S2Builder::Label; using LabelSetId = S2Builder::LabelSetId; - virtual ~Layer() {} + virtual ~Layer() = default; // Defines options for building the edge graph that is passed to Build(). virtual GraphOptions graph_options() const = 0; diff --git a/src/s2/s2builderutil_closed_set_normalizer.cc b/src/s2/s2builderutil_closed_set_normalizer.cc index 8be85f72..bd3c00e7 100644 --- a/src/s2/s2builderutil_closed_set_normalizer.cc +++ b/src/s2/s2builderutil_closed_set_normalizer.cc @@ -17,12 +17,19 @@ #include "s2/s2builderutil_closed_set_normalizer.h" +#include #include +#include +#include -#include "absl/memory/memory.h" +#include "s2/id_set_lexicon.h" +#include "s2/s2builder.h" +#include "s2/s2builder_graph.h" #include "s2/s2builder_layer.h" +#include "s2/s2builderutil_find_polygon_degeneracies.h" +#include "s2/s2error.h" -using absl::make_unique; +using std::make_unique; using std::shared_ptr; using std::unique_ptr; using std::vector; @@ -103,7 +110,8 @@ const vector& ClosedSetNormalizer::Run( bool modified[3]; bool any_modified = false; for (int dim = 2; dim >= 0; --dim) { - if (new_edges_[dim].size() != g[dim].num_edges()) any_modified = true; + if (new_edges_[dim].size() != static_cast(g[dim].num_edges())) + any_modified = true; modified[dim] = any_modified; } if (!any_modified) { @@ -146,8 +154,9 @@ inline Edge ClosedSetNormalizer::Advance(const Graph& g, EdgeId* e) const { // returning a sentinel value once all edges are exhausted. inline Edge ClosedSetNormalizer::AdvanceIncoming( const Graph& g, const vector& in_edges, int* i) const { - return ((++*i == in_edges.size()) ? sentinel_ : - Graph::reverse(g.edge(in_edges[*i]))); + return ((static_cast(++*i) == in_edges.size()) + ? sentinel_ + : Graph::reverse(g.edge(in_edges[*i]))); } void ClosedSetNormalizer::NormalizeEdges(const vector& g, diff --git a/src/s2/s2builderutil_closed_set_normalizer.h b/src/s2/s2builderutil_closed_set_normalizer.h index c7d9fc68..8bcb109a 100644 --- a/src/s2/s2builderutil_closed_set_normalizer.h +++ b/src/s2/s2builderutil_closed_set_normalizer.h @@ -18,10 +18,14 @@ #ifndef S2_S2BUILDERUTIL_CLOSED_SET_NORMALIZER_H_ #define S2_S2BUILDERUTIL_CLOSED_SET_NORMALIZER_H_ +#include #include + #include "s2/id_set_lexicon.h" +#include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builderutil_find_polygon_degeneracies.h" +#include "s2/s2error.h" namespace s2builderutil { @@ -184,10 +188,10 @@ using LayerVector = std::vector>; // polyline_options.set_polyline_type(Graph::PolylineType::WALK); // polyline_options.set_duplicate_edges(DuplicateEdges::MERGE); // LayerVector layers(3); -// layers[0] = absl::make_unique(index); -// layers[1] = absl::make_unique( +// layers[0] = std::make_unique(index); +// layers[1] = std::make_unique( // index, polyline_options); -// layers[2] = absl::make_unique(index); +// layers[2] = std::make_unique(index); // S2BooleanOperation op(S2BooleanOperation::OpType::UNION, // NormalizeClosedSet(std::move(layers))); // return op.Build(a, b, error); diff --git a/src/s2/s2builderutil_find_polygon_degeneracies.cc b/src/s2/s2builderutil_find_polygon_degeneracies.cc index 28bc67cb..27a19b11 100644 --- a/src/s2/s2builderutil_find_polygon_degeneracies.cc +++ b/src/s2/s2builderutil_find_polygon_degeneracies.cc @@ -17,22 +17,27 @@ #include "s2/s2builderutil_find_polygon_degeneracies.h" -#include +#include +#include #include #include -#include "absl/memory/memory.h" #include "s2/mutable_s2shape_index.h" +#include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2builderutil_graph_shape.h" #include "s2/s2contains_vertex_query.h" #include "s2/s2crossing_edge_query.h" #include "s2/s2edge_crosser.h" +#include "s2/s2error.h" +#include "s2/s2point.h" #include "s2/s2pointutil.h" #include "s2/s2predicates.h" +#include "s2/s2shape.h" +#include "s2/s2shapeutil_shape_edge_id.h" -using absl::make_unique; using std::make_pair; +using std::make_unique; using std::pair; using std::vector; @@ -158,8 +163,8 @@ vector DegeneracyFinder::Run(S2Error* error) { known_vertex = FindUnbalancedVertex(); known_vertex_sign = ContainsVertexSign(known_vertex); } - const int kMaxUnindexedContainsCalls = 20; // Tuned using benchmarks. - if (num_unknown_signs <= kMaxUnindexedContainsCalls) { + const int kMaxUnindexedSignComputations = 25; // Tuned using benchmarks. + if (num_unknown_signs <= kMaxUnindexedSignComputations) { ComputeUnknownSignsBruteForce(known_vertex, known_vertex_sign, &components); } else { @@ -254,7 +259,7 @@ bool DegeneracyFinder::CrossingParity(VertexId v0, VertexId v1, int crossings = 0; S2Point p0 = g_.vertex(v0); S2Point p1 = g_.vertex(v1); - S2Point p0_ref = S2::Ortho(p0); + S2Point p0_ref = S2::RefDir(p0); for (const Edge& edge : out_.edges(v0)) { if (edge.second == v1) { if (include_same) ++crossings; @@ -277,7 +282,7 @@ VertexId DegeneracyFinder::FindUnbalancedVertex() const { for (VertexId v = 0; v < g_.num_vertices(); ++v) { if (is_vertex_unbalanced_[v]) return v; } - S2_LOG(DFATAL) << "Could not find previously marked unbalanced vertex"; + S2_LOG(ERROR) << "Could not find previously marked unbalanced vertex"; return -1; } diff --git a/src/s2/s2builderutil_find_polygon_degeneracies.h b/src/s2/s2builderutil_find_polygon_degeneracies.h index 49157ac6..5e502ed9 100644 --- a/src/s2/s2builderutil_find_polygon_degeneracies.h +++ b/src/s2/s2builderutil_find_polygon_degeneracies.h @@ -21,6 +21,7 @@ #include #include "s2/base/integral_types.h" +#include "s2/s2builder.h" #include "s2/s2builder_graph.h" #include "s2/s2error.h" diff --git a/src/s2/s2builderutil_get_snapped_winding_delta.cc b/src/s2/s2builderutil_get_snapped_winding_delta.cc new file mode 100644 index 00000000..dd84f35f --- /dev/null +++ b/src/s2/s2builderutil_get_snapped_winding_delta.cc @@ -0,0 +1,438 @@ +// Copyright 2020 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) +// +// The following algorithm would not be necessary with planar geometry, since +// then winding numbers could be computed by starting at a point at infinity +// (whose winding number is zero) and counting signed edge crossings. However +// points at infinity do not exist on the sphere. +// +// Instead we compute the change in winding number of a reference vertex R +// using only the set of edges incident to the snapped reference vertex R'. +// Essentially this involves looking at the set of input edges that snapped to +// R' and assembling them into edge chains. These edge chains can be divided +// into two categories: +// +// (1) Edge chains that are entirely contained by the Voronoi region of R'. +// This means that the input edges form a closed loop where every vertex +// snaps to R'. We can compute the change in winding number due to this +// loop by simply choosing a point Z outside the Voronoi region of R' and +// computing the winding numbers of R and R' relative to Z. +// +// (2) Edge chains that enter the Voronoi region of R' and later leave it. In +// this case the input chain has the form C = (A0, A1, ..., B0, B1) where +// A0 and B1 are outside the Voronoi region of R' and all other vertices +// snap to R'. In the vicinity of R' this input chain snaps to a chain C' +// of the form (A0', R', B1') where A0' is the second-last vertex in the +// snapped edge chain for A0A1 and B1' is the second vertex in the snapped +// edge chain for B0B1. In principle we handle this similarly to the case +// above (by finding a point Z whose change in winding number is known, +// and then counting signed edge crossings along ZR with respect to C and +// along ZR' with respect to C'). However the details are more +// complicated and are described in GetSnappedWindingDelta(). +// +// The total change in winding number is simply the sum of the changes in +// winding number due to each of these edge chains. + +#include "s2/s2builderutil_get_snapped_winding_delta.h" + +#include +#include +#include + +#include "absl/container/btree_map.h" +#include "absl/types/span.h" +#include "s2/id_set_lexicon.h" +#include "s2/s2builder.h" +#include "s2/s2builder_graph.h" +#include "s2/s2edge_crosser.h" +#include "s2/s2edge_crossings.h" +#include "s2/s2edge_distances.h" +#include "s2/s2error.h" +#include "s2/s2point.h" +#include "s2/s2pointutil.h" +#include "s2/s2shape.h" + +using absl::Span; +using std::make_pair; +using std::vector; + +using Graph = S2Builder::Graph; +using GraphOptions = S2Builder::GraphOptions; + +using EdgeId = Graph::EdgeId; +using InputEdgeId = Graph::InputEdgeId; +using VertexId = Graph::VertexId; + +namespace s2builderutil { + +namespace { + +// An input edge may snap to zero, one, or two non-degenerate output edges +// incident to the reference vertex, consisting of at most one incoming and +// one outgoing edge. +// +// If v_in >= 0, an incoming edge to the reference vertex is present. +// If v_out >= 0, an outgoing edge from the reference vertex is present. +struct EdgeSnap { + S2Shape::Edge input; + VertexId v_in = -1; + VertexId v_out = -1; +}; + +// A map that allows finding all the input edges that start at a given point. +using InputVertexEdgeMap = absl::btree_multimap; + +// The winding number returned when a usage error is detected. +constexpr int kErrorResult = std::numeric_limits::max(); + +bool BuildChain( + VertexId ref_v, const Graph& g, InputVertexEdgeMap* input_vertex_edge_map, + vector* chain_in, vector* chain_out, S2Error* error) { + S2_DCHECK(chain_in->empty()); + S2_DCHECK(chain_out->empty()); + + // First look for an incoming edge to the reference vertex. (This will be + // the start of a chain that eventually leads to an outgoing edge.) + { + auto it = input_vertex_edge_map->begin(); + for (; it != input_vertex_edge_map->end(); ++it) { + const EdgeSnap& snap = it->second; + if (snap.v_in >= 0) { + chain_out->push_back(g.vertex(snap.v_in)); + break; + } + } + if (it == input_vertex_edge_map->end()) { + // Pick an arbitrary edge to start a closed loop. + it = input_vertex_edge_map->begin(); + } + EdgeSnap snap = it->second; + input_vertex_edge_map->erase(it); + + chain_in->push_back(snap.input.v0); + chain_in->push_back(snap.input.v1); + chain_out->push_back(g.vertex(ref_v)); + if (snap.v_out >= 0) { + // This input edge enters and immediately exits the Voronoi region. + chain_out->push_back(g.vertex(snap.v_out)); + return true; + } + } + + // Now repeatedly add edges until the chain or loop is finished. + while (chain_in->back() != chain_in->front()) { + const auto& range = input_vertex_edge_map->equal_range(chain_in->back()); + if (range.first == range.second) { + error->Init(S2Error::INVALID_ARGUMENT, + "Input edges (after filtering) do not form loops"); + return false; + } + EdgeSnap snap = range.first->second; + input_vertex_edge_map->erase(range.first); + chain_in->push_back(snap.input.v1); + if (snap.v_out >= 0) { + // The chain has exited the Voronoi region. + chain_out->push_back(g.vertex(snap.v_out)); + break; + } + } + return true; +} + +// Returns the change in winding number along the edge AB with respect to the +// given edge chain. This is simply the sum of the signed edge crossings. +int GetEdgeWindingDelta(const S2Point& a, const S2Point& b, + absl::Span chain) { + S2_DCHECK_GT(chain.size(), 0); + + int delta = 0; + S2EdgeCrosser crosser(&a, &b, &chain[0]); + for (size_t i = 1; i < chain.size(); ++i) { + delta += crosser.SignedEdgeOrVertexCrossing(&chain[i]); + } + return delta; +} + +// Given an input edge (B0, B1) that snaps to an edge chain (B0', B1', ...), +// returns a connecting vertex "Bc" that can be used as a substitute for the +// remaining snapped vertices "..." when computing winding numbers. This +// requires that (1) the edge (B1', Bc) does not intersect the Voronoi region +// of B0', and (2) the edge chain (B0', B1', Bc, B1) always stays within the +// snap radius of the input edge (B0, B1). +S2Point GetConnector(const S2Point& b0, const S2Point& b1, + const S2Point& b1_snapped) { + // If B1' within 90 degrees of B1, no connecting vertex is necessary. + if (b1_snapped.DotProd(b1) >= 0) return b1; + + // Otherwise we use the point on (B0, B1) that is 90 degrees away from B1'. + // This is sufficient to ensure conditions (1) and (2). + S2Point x = S2::RobustCrossProd(b0, b1).CrossProd(b1_snapped).Normalize(); + return (x.DotProd(S2::Interpolate(b0, b1, 0.5)) >= 0) ? x : -x; +} + +// Returns the set of incoming and outgoing edges incident to the given +// vertex. This method takes time linear in the size of the graph "g"; +// if you need to call this function many times then it is more efficient to +// use Graph::VertexOutMap and Graph::VertexInMap instead. +vector GetIncidentEdgesBruteForce(VertexId v, const Graph& g) { + vector result; + for (EdgeId e = 0; e < g.num_edges(); ++e) { + if (g.edge(e).first == v || g.edge(e).second == v) { + result.push_back(e); + } + } + return result; +} + +} // namespace + +int GetSnappedWindingDelta( + const S2Point& ref_in, VertexId ref_v, Span incident_edges, + const InputEdgeFilter& input_edge_filter, const S2Builder& builder, + const Graph& g, S2Error* error) { + S2_DCHECK(!builder.options().simplify_edge_chains()); + S2_DCHECK(g.options().edge_type() == S2Builder::EdgeType::DIRECTED); + S2_DCHECK(g.options().degenerate_edges() == GraphOptions::DegenerateEdges::KEEP); + S2_DCHECK(g.options().sibling_pairs() == GraphOptions::SiblingPairs::KEEP || + g.options().sibling_pairs() == GraphOptions::SiblingPairs::REQUIRE || + g.options().sibling_pairs() == GraphOptions::SiblingPairs::CREATE); + + // First we group all the incident edges by input edge id, to handle the + // problem that input edges can map to either one or two snapped edges. + absl::btree_map input_id_edge_map; + for (EdgeId e : incident_edges) { + Graph::Edge edge = g.edge(e); + for (InputEdgeId input_id : g.input_edge_ids(e)) { + if (input_edge_filter && input_edge_filter(input_id)) continue; + EdgeSnap* snap = &input_id_edge_map[input_id]; + snap->input = builder.input_edge(input_id); + if (edge.first != ref_v) snap->v_in = edge.first; + if (edge.second != ref_v) snap->v_out = edge.second; + } + } + // Now we regroup the edges according to the reference vertex of the + // corresponding input edge. This makes it easier to assemble these edges + // into (portions of) input edge loops. + InputVertexEdgeMap input_vertex_edge_map; + for (const auto& entry : input_id_edge_map) { + const EdgeSnap& snap = entry.second; + input_vertex_edge_map.insert(make_pair(snap.input.v0, snap)); + } + + // The position of the reference vertex after snapping. In comments we will + // refer to the reference vertex before and after snapping as R and R'. + S2Point ref_out = g.vertex(ref_v); + + // Now we repeatedly assemble edges into an edge chain and determine the + // change in winding number due to snapping of that edge chain. These + // values are summed to compute the final winding number delta. + // + // An edge chain is either a closed loop of input vertices where every + // vertex snapped to the reference vertex R', or a partial loop such that + // all interior vertices snap to R' while the first and last vertex do not. + // Note that the latter includes the case where the first and last input + // vertices in the chain are identical but do not snap to R'. + // + // Essentially we compute the winding number of the unsnapped reference + // vertex R with respect to the input chain and the winding number of the + // snapped reference vertex R' with respect to the output chain, and then + // subtract them. In the case of open chains, we compute winding numbers as + // if the chains had been closed in a way that preserves topology while + // snapping (i.e., in a way that does not the cause chain to pass through + // the reference vertex as it continuously deforms from the input to the + // output). + // + // Any changes to this code should be validated by running the RandomLoops + // unit test with at least 10 million iterations. + int winding_delta = 0; + while (!input_vertex_edge_map.empty()) { + vector chain_in, chain_out; + if (!BuildChain(ref_v, g, &input_vertex_edge_map, + &chain_in, &chain_out, error)) { + return kErrorResult; + } + if (chain_out.size() == 1) { + // We have a closed chain C of input vertices such that every vertex + // snaps to the reference vertex R'. Therefore we can easily find a + // point Z whose winding number is not affected by the snapping of C; it + // just needs to be outside the Voronoi region of R'. Since the snap + // radius is at most 70 degrees, we can use a point 90 degrees away such + // as S2::Ortho(R'). + // + // We then compute the winding numbers of R and R' relative to Z. We + // compute the winding number of R by counting signed crossings of the + // edge ZR, while the winding number of R' relative to Z is always zero + // because the snapped chain collapses to a single point. + S2_DCHECK_EQ(chain_out[0], ref_out); // Snaps to R'. + S2_DCHECK_EQ(chain_in[0], chain_in.back()); // Chain is a loop. + S2Point z = S2::Ortho(ref_out); + winding_delta += 0 - GetEdgeWindingDelta(z, ref_in, chain_in); + } else { + // We have an input chain C = (A0, A1, ..., B0, B1) that snaps to a + // chain C' = (A0', R', B1'), where A0 and B1 are outside the Voronoi + // region of R' and all other input vertices snap to R'. This includes + // the case where A0 == B1 and also the case where the input chain + // consists of only two vertices. Note that technically the chain C + // snaps to a supersequence of C', since A0A1 snaps to a chain whose + // last two vertices are (A0', R') and B0B1 snaps to a chain whose first + // two vertices are (R', B1'). This implies that A0 does not + // necessarily snap to A0', and similarly for B1 and B1'. + // + // Note that A0 and B1 can be arbitrarily far away from R'. This makes + // it difficult (on the sphere) to construct a point Z whose winding + // number is guaranteed not to be affected by snapping the edges A0A1 + // and B0B1. Instead we construct two points Za and Zb such that Za is + // guaranteed not be affected by the snapping of A0A1, Zb is guaranteed + // not to be affected by the snapping of B0B1, and both points are + // guaranteed not to be affected by the snapping of any other input + // edges. We can then compute the change in winding number of Zb by + // considering only the single edge A0A1 that snaps to A0'R'. + // Furthermore we can compute the latter by using Za as the reference + // point, since its winding number is guaranteed not be affected by this + // particular edge. + // + // Given the point Zb, whose change in winding number is now known, we + // can compute the change in winding number of the reference vertex R. + // We essentially want to count the signed crossings of ZbR' with respect + // to C' and subtract the signed crossings of ZbR with respect to C, + // which we will write as s(ZbR', C') - s(ZbR, C). + // + // However to do this we need to close both chains in a way that is + // topologically consistent and does not affect the winding number of + // Zb. This can be achieved by counting the signed crossings of ZbR' by + // following the two-edge path (Zb, R, R'). In other words, we compute + // this as s(ZbR, C') + s(RR', C') - s(ZbR, C). We can then compute + // s(ZbR, C') - s(ZbR, C) by simply concatenating the vertices of C in + // reverse order to C' to form a single closed loop. The remaining term + // s(RR', C') can be implemented as signed edge crossing tests, or more + // directly by testing whether R is contained by the wedge C'. + S2_DCHECK_EQ(chain_out.size(), 3); + S2_DCHECK_EQ(chain_out[1], ref_out); + + // Compute two points Za and Zb such that Za is not affected by the + // snapping of any edge except possibly B0B1, and Zb is not affected by + // the snapping of any edge except possibly A0A1. Za and Zb are simply + // the normals to the edges A0A1 and B0B1 respectively, with their sign + // chosen to point away from the Voronoi site R'. This ensures at least + // 20 degrees of separation from all edges except the ones mentioned. + S2Point za = S2::RobustCrossProd(chain_in[0], chain_in[1]).Normalize(); + S2Point zb = S2::RobustCrossProd(chain_in.end()[-2], chain_in.back()) + .Normalize(); + if (za.DotProd(ref_out) > 0) za = -za; + if (zb.DotProd(ref_out) > 0) zb = -zb; + + // We now want to determine the change in winding number of Zb due to + // A0A1 snapping to A0'R'. Conceptually we do this by closing these + // two single-edge chains into loops L and L' and then computing + // s(ZaZb, L') - s(ZaZb, L). Recall that Za was constructed so as not + // to be affected by the snapping of edge A0A1, however this is only + // true provided that L can snap to L' without passing through Za. + // + // To achieve this we let L be the degenerate loop (A0, A1, A0), and L' + // be the loop (A0', R', A1, A0, A0'). The only problem is that we need + // to ensure that the edge A0A0' stays within 90 degrees of A0A1, since + // otherwise when the latter edge snaps to the former it might pass + // through Za. (This problem arises because we only consider the last + // two vertices (A0', R') that A0A1 snaps to. If we used the full chain + // of snapped vertices for A0A1 then L' would always stay within the + // snap radius of this edge.) + // + // The simplest way to fix this problem is to insert a connecting vertex + // Ac between A0 and A0'. THis vertex acts as a proxy for the missing + // snapped vertices, yielding the loop L' = (A0', R', A1, A0, Ac, A0'). + // The vertex Ac is located on the edge A0A1 and is at most 90 degrees + // away from A0'. This ensures that the chain (A0, Ac, A0') always + // stays within the snap radius of the input edge A0A1. + // + // Similarly we insert a connecting vertex Bc between B0 and B1 to + // ensure that the edge B1'B1 passes on the correct side of Zb. + S2Point a0_connector = GetConnector(chain_in[1], chain_in[0], + chain_out[0]); + S2Point b1_connector = GetConnector(chain_in.end()[-2], chain_in.back(), + chain_out[2]); + + // Compute the change in winding number for reference vertex Zb. Note + // that we must duplicate the first/last vertex of the loop since the + // argument to GetEdgeWindingDelta() is a polyline. + vector chain_z {chain_out[0], chain_out[1], chain_in[1], + chain_in[0], a0_connector, chain_out[0]}; + winding_delta += GetEdgeWindingDelta(za, zb, chain_z); + + // Compute the change in winding number of ZbR due to snapping C to C'. + // As before, conceptually we do this by closing these chains into loops + // L and L' such that L snaps to L' without passing through Zb. Again + // this can be done by concatenating the vertices of C' with the + // reversed vertices of C, along with the two extra connecting vertices + // Ac and Bc to ensure that L and L' pass on the same side of Za and Zb. + // This yields the loop (A0', R', B1', Bc, B1, B0, ..., A1, A0, Ac, A0'). + vector chain_diff = chain_out; + chain_diff.push_back(b1_connector); + chain_diff.insert(chain_diff.end(), chain_in.rbegin(), chain_in.rend()); + chain_diff.push_back(a0_connector); + chain_diff.push_back(chain_out[0]); // Close the loop. + winding_delta += GetEdgeWindingDelta(zb, ref_in, chain_diff); + + // Compute the change in winding number of RR' with respect to C' only. + // (This could also be computed using two calls to s2pred::OrderedCCW.) + S2_DCHECK_EQ(chain_out[1], ref_out); + winding_delta += GetEdgeWindingDelta(ref_in, ref_out, chain_out); + } + } + return winding_delta; +} + +int GetSnappedWindingDelta( + const S2Point& ref_in, S2Builder::Graph::VertexId ref_v, + const InputEdgeFilter &input_edge_filter, const S2Builder& builder, + const Graph& g, S2Error* error) { + return GetSnappedWindingDelta( + ref_in, ref_v, GetIncidentEdgesBruteForce(ref_v, g), + input_edge_filter, builder, g, error); +} + +VertexId FindFirstVertexId(InputEdgeId input_edge_id, const Graph& g) { + // A given input edge snaps to a chain of output edges. To determine which + // output vertex the source of the given input edge snaps to, we must find + // the first edge in this chain. + // + // The search below takes linear time in the number of edges; it can be done + // more efficiently if duplicate edges are not being merged and the mapping + // returned by Graph::GetInputEdgeOrder() is available. The algorithm would + // also be much simpler if input_edge_id were known to be degenerate. + absl::btree_map excess_degree_map; + for (EdgeId e = 0; e < g.num_edges(); ++e) { + IdSetLexicon::IdSet id_set = g.input_edge_ids(e); + for (InputEdgeId id : id_set) { + if (id == input_edge_id) { + excess_degree_map[g.edge(e).first] += 1; + excess_degree_map[g.edge(e).second] -= 1; + break; + } + } + } + if (excess_degree_map.empty()) return -1; // Does not exist. + + // Look for the (unique) vertex whose excess degree is +1. + for (const auto& entry : excess_degree_map) { + if (entry.second == 1) return entry.first; + } + // Otherwise "input_edge_id" must snap to a single degenerate edge. + S2_DCHECK_EQ(excess_degree_map.size(), 1); + return excess_degree_map.begin()->first; +} + +} // namespace s2builderutil diff --git a/src/s2/s2builderutil_get_snapped_winding_delta.h b/src/s2/s2builderutil_get_snapped_winding_delta.h new file mode 100644 index 00000000..d5a41174 --- /dev/null +++ b/src/s2/s2builderutil_get_snapped_winding_delta.h @@ -0,0 +1,165 @@ +// Copyright 2020 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_S2BUILDERUTIL_GET_SNAPPED_WINDING_DELTA_H_ +#define S2_S2BUILDERUTIL_GET_SNAPPED_WINDING_DELTA_H_ + +#include + +#include "absl/types/span.h" +#include "s2/s2builder.h" +#include "s2/s2builder_graph.h" +#include "s2/s2error.h" +#include "s2/s2point.h" + +namespace s2builderutil { + +// A function that returns true if the given S2Builder input edge should be +// ignored in the winding number calculation. This means either that the edge +// is not a loop edge (e.g., a non-closed polyline) or that this loop should +// not affect the winding number. This is useful for two purposes: +// +// - To process graphs that contain polylines and points in addition to loops. +// +// - To process graphs where the winding number is computed with respect to +// only a subset of the input loops. +// +// It can be default-constructed to indicate that no edges should be ignored. +using InputEdgeFilter = std::function; + +// Given an S2Builder::Graph of output edges after snap rounding and a +// reference vertex R, computes the change in winding number of R due to +// snapping. (See S2WindingOperation for an introduction to winding numbers on +// the sphere.) The return value can be added to the original winding number +// of R to obtain the winding number of the corresponding snapped vertex R'. +// +// The algorithm requires that the S2Builder input edges consist entirely of +// (possibly self-intersecting) closed loops. If you need to process inputs +// that include other types of geometry (e.g., non-closed polylines), you will +// need to either (1) put them into a different S2Builder layer, (2) close the +// polylines into loops (e.g. using GraphOptions::SiblingEdges::CREATE), or (3) +// provide a suitable InputEdgeFilter (see above) so that the non-loop edges +// can be ignored. +// +// The algorithm is designed to be robust for any input edge configuration and +// snapping result. However note that it cannot be used in conjunction with +// edge chain simplification (S2Builder::Options::simplify_edge_chains). It +// also requires that S2Builder::GraphOptions be configured to keep all snapped +// edges, even degenerate ones (see requirements below). +// +// "ref_in" is the reference vertex location before snapping. It *must* be an +// input vertex to S2Builder, however this is not checked. +// +// "ref_v" is the Graph::VertexId of the reference vertex after snapping. +// (This can be found using the FindFirstVertexId() function below if desired.) +// +// "input_edge_filter" can optionally be used to ignore input edges that +// should not affect the winding number calculation (such as polyline edges). +// The value can be default-constructed (InputEdgeFilter{}) to use all edges. +// +// "builder" is the S2Builder that produced the given edge graph. It is used +// to map InputEdgeIds back to the original edge definitions, and also to +// verify that no incompatible S2Builder::Options were used (see below). +// +// "g" is the S2Builder output graph of snapped edges. +// +// The only possible errors are usage errors, in which case "error" is set to +// an appropriate error message and a very large value is returned. +// +// Example usage: +// +// This function is generally called from an S2Builder::Layer implementation. +// We assume here that the reference vertex is the first vertex of the input +// edge identified by "ref_input_edge_id_", and that its desired winding number +// with respect to the input loops is "ref_winding_". +// +// using Graph = S2Builder::Graph; +// class SomeLayer : public S2Builder::Layer { +// private: +// int ref_input_edge_id_; +// int ref_winding_; +// const S2Builder& builder_; +// +// public: +// ... +// void Build(const Graph& g, S2Error* error) { +// // Find the positions of the reference vertex before and after snapping. +// S2Point ref_in = builder_.input_edge(ref_input_edge_id_).v0; +// Graph::VertexId ref_v = +// s2builderutil::FindFirstVertexId(ref_input_edge_id_, g); +// S2Point ref_out = g.vertex(ref_v); +// +// // Compute the change in winding number due to snapping. +// S2Error error; +// ref_winding_ += s2builderutil::GetSnappedWindingDelta( +// ref_in, ref_v, InputEdgeFilter{}, builder_, g, error); +// S2_CHECK(error->ok()); // All errors are usage errors. +// +// // Winding numbers of others points can now be found by counting signed +// // edge crossings (S2EdgeCrosser::SignedEdgeOrVertexCrossing) between +// // "ref_out" and the desired point. Note that if DuplicateEdges::MERGE +// // or SiblingPairs::CREATE was used, each crossing has a multiplicity +// // equal to the number of non-filtered input edges that snapped to that +// // output edge. +// } +// } +// +// REQUIRES: The input edges after filtering consist entirely of closed loops. +// (If DuplicateEdges::MERGE or SiblingPairs::CREATE was used, +// each graph edge has a multiplicity equal to the number of +// non-filtered input edges that snapped to it.) +// +// REQUIRES: g.options().edge_type() == DIRECTED +// REQUIRES: g.options().degenerate_edges() == KEEP +// REQUIRES: g.options().sibling_pairs() == {KEEP, REQUIRE, CREATE} +// REQUIRES: builder.options().simplify_edge_chains() == false +// +// CAVEAT: The running time is proportional to the S2Builder::Graph size. If +// you need to call this function many times on the same graph then +// use the alternate version below. (Most clients only need to call +// GetSnappedWindingDelta() once per graph because the winding numbers +// of other points can be computed by counting signed edge crossings.) +int GetSnappedWindingDelta( + const S2Point& ref_in, S2Builder::Graph::VertexId ref_v, + const InputEdgeFilter &input_edge_filter, const S2Builder& builder, + const S2Builder::Graph& g, S2Error* error); + +// This version can be used when GetSnappedWindingDelta() needs to be called +// many times on the same graph. It is faster than the function above, but +// less convenient to use because it requires the client to provide the set of +// graph edges incident to the snapped reference vertex. It runs in time +// proportional to the size of this set. +// +// "incident_edges" is the set of incoming and outgoing graph edges incident +// to ref_v. (These edges can be found efficiently using Graph::VertexOutMap +// and Graph::VertexInMap.) +// +// See the function above for the remaining parameters and requirements. +int GetSnappedWindingDelta( + const S2Point& ref_in, S2Builder::Graph::VertexId ref_v, + absl::Span incident_edges, + const InputEdgeFilter &input_edge_filter, const S2Builder& builder, + const S2Builder::Graph& g, S2Error* error); + +// Returns the first vertex of the snapped edge chain for the given input +// edge, or -1 if this input edge does not exist in the graph "g". +S2Builder::Graph::VertexId FindFirstVertexId( + S2Builder::Graph::InputEdgeId input_edge_id, const S2Builder::Graph& g); + +} // namespace s2builderutil + +#endif // S2_S2BUILDERUTIL_GET_SNAPPED_WINDING_DELTA_H_ diff --git a/src/s2/s2builderutil_graph_shape.h b/src/s2/s2builderutil_graph_shape.h index 9edbc2b2..5fbcfdec 100644 --- a/src/s2/s2builderutil_graph_shape.h +++ b/src/s2/s2builderutil_graph_shape.h @@ -20,7 +20,9 @@ #include +#include "s2/s2builder.h" #include "s2/s2builder_graph.h" +#include "s2/s2shape.h" namespace s2builderutil { diff --git a/src/s2/s2builderutil_lax_polygon_layer.cc b/src/s2/s2builderutil_lax_polygon_layer.cc index 9b92240d..f84a0665 100644 --- a/src/s2/s2builderutil_lax_polygon_layer.cc +++ b/src/s2/s2builderutil_lax_polygon_layer.cc @@ -18,10 +18,17 @@ #include "s2/s2builderutil_lax_polygon_layer.h" #include -#include -#include "absl/memory/memory.h" +#include +#include + +#include "s2/id_set_lexicon.h" +#include "s2/s2builder.h" +#include "s2/s2builder_graph.h" +#include "s2/s2builder_layer.h" #include "s2/s2builderutil_find_polygon_degeneracies.h" -#include "s2/s2debug.h" +#include "s2/s2error.h" +#include "s2/s2lax_polygon_shape.h" +#include "s2/s2point.h" using std::vector; @@ -72,11 +79,8 @@ GraphOptions LaxPolygonLayer::graph_options() const { DuplicateEdges::KEEP, SiblingPairs::DISCARD); } else { // Keep at most one copy of each sibling pair and each isolated vertex. - // We need DuplicateEdges::MERGE because DegenerateEdges::DISCARD_EXCESS - // can still keep multiple copies (it only discards degenerate edges that - // are connected to non-degenerate edges). return GraphOptions(options_.edge_type(), DegenerateEdges::DISCARD_EXCESS, - DuplicateEdges::MERGE, SiblingPairs::DISCARD_EXCESS); + DuplicateEdges::KEEP, SiblingPairs::DISCARD_EXCESS); } } @@ -165,7 +169,7 @@ void LaxPolygonLayer::BuildDirected(Graph g, S2Error* error) { (degenerate_boundaries == DegenerateBoundaries::DISCARD_HOLES); auto degeneracies = s2builderutil::FindPolygonDegeneracies(g, error); if (!error->ok()) return; - if (degeneracies.size() == g.num_edges()) { + if (degeneracies.size() == static_cast(g.num_edges())) { if (degeneracies.empty()) { MaybeAddFullLoop(g, &loops, error); } else if (degeneracies[0].is_hole) { diff --git a/src/s2/s2builderutil_lax_polygon_layer.h b/src/s2/s2builderutil_lax_polygon_layer.h index 8a1e63ea..565cf98f 100644 --- a/src/s2/s2builderutil_lax_polygon_layer.h +++ b/src/s2/s2builderutil_lax_polygon_layer.h @@ -33,9 +33,11 @@ #define S2_S2BUILDERUTIL_LAX_POLYGON_LAYER_H_ #include +#include #include + +#include "s2/base/integral_types.h" #include "s2/base/logging.h" -#include "absl/memory/memory.h" #include "s2/id_set_lexicon.h" #include "s2/mutable_s2shape_index.h" #include "s2/s2builder.h" @@ -43,6 +45,8 @@ #include "s2/s2builder_layer.h" #include "s2/s2error.h" #include "s2/s2lax_polygon_shape.h" +#include "s2/s2point.h" +#include "s2/s2shape.h" namespace s2builderutil { @@ -106,8 +110,11 @@ class LaxPolygonLayer : public S2Builder::Layer { // since it maintains the closest fidelity to the original geometry.) // // DEFAULT: DegenerateBoundaries::KEEP - enum class DegenerateBoundaries { - DISCARD, DISCARD_HOLES, DISCARD_SHELLS, KEEP + enum class DegenerateBoundaries : uint8 { + DISCARD, + DISCARD_HOLES, + DISCARD_SHELLS, + KEEP }; DegenerateBoundaries degenerate_boundaries() const; void set_degenerate_boundaries(DegenerateBoundaries degenerate_boundaries); diff --git a/src/s2/s2builderutil_lax_polyline_layer.cc b/src/s2/s2builderutil_lax_polyline_layer.cc new file mode 100644 index 00000000..4f4ab1b4 --- /dev/null +++ b/src/s2/s2builderutil_lax_polyline_layer.cc @@ -0,0 +1,104 @@ +// Copyright 2020 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/s2builderutil_lax_polyline_layer.h" + +#include + +#include "s2/id_set_lexicon.h" +#include "s2/s2builder.h" +#include "s2/s2builder_graph.h" +#include "s2/s2builder_layer.h" +#include "s2/s2error.h" +#include "s2/s2lax_polyline_shape.h" +#include "s2/s2point.h" + +using std::vector; + +using EdgeType = S2Builder::EdgeType; +using Graph = S2Builder::Graph; +using GraphOptions = S2Builder::GraphOptions; +using Label = S2Builder::Label; + +using DegenerateEdges = GraphOptions::DegenerateEdges; +using DuplicateEdges = GraphOptions::DuplicateEdges; +using SiblingPairs = GraphOptions::SiblingPairs; + +using EdgeId = Graph::EdgeId; +using PolylineType = Graph::PolylineType; + +namespace s2builderutil { + +LaxPolylineLayer::LaxPolylineLayer(S2LaxPolylineShape* polyline, + const LaxPolylineLayer::Options& options) { + Init(polyline, nullptr, nullptr, options); +} + +LaxPolylineLayer::LaxPolylineLayer( + S2LaxPolylineShape* polyline, LabelSetIds* label_set_ids, + IdSetLexicon* label_set_lexicon, const Options& options) { + Init(polyline, label_set_ids, label_set_lexicon, options); +} + +void LaxPolylineLayer::Init(S2LaxPolylineShape* polyline, + LabelSetIds* label_set_ids, + IdSetLexicon* label_set_lexicon, + const Options& options) { + S2_DCHECK_EQ(label_set_ids == nullptr, label_set_lexicon == nullptr); + polyline_ = polyline; + label_set_ids_ = label_set_ids; + label_set_lexicon_ = label_set_lexicon; + options_ = options; +} + +GraphOptions LaxPolylineLayer::graph_options() const { + return GraphOptions(options_.edge_type(), DegenerateEdges::KEEP, + DuplicateEdges::KEEP, SiblingPairs::KEEP); +} + +void LaxPolylineLayer::Build(const Graph& g, S2Error* error) { + if (g.num_edges() == 0) { + polyline_->Init(vector{}); + return; + } + vector edge_polylines = + g.GetPolylines(PolylineType::WALK); + if (edge_polylines.size() != 1) { + error->Init(S2Error::BUILDER_EDGES_DO_NOT_FORM_POLYLINE, + "Input edges cannot be assembled into polyline"); + return; + } + const Graph::EdgePolyline& edge_polyline = edge_polylines[0]; + vector vertices; // Temporary storage for vertices. + vertices.reserve(edge_polyline.size()); + vertices.push_back(g.vertex(g.edge(edge_polyline[0]).first)); + for (EdgeId e : edge_polyline) { + vertices.push_back(g.vertex(g.edge(e).second)); + } + if (label_set_ids_) { + Graph::LabelFetcher fetcher(g, options_.edge_type()); + vector

::internal_verify( - const node_type *node, const key_type *lo, const key_type *hi) const { - assert(node->count() > 0); - assert(node->count() <= node->max_count()); - if (lo) { - assert(!compare_keys(node->key(0), *lo)); - } - if (hi) { - assert(!compare_keys(*hi, node->key(node->count() - 1))); - } - for (int i = 1; i < node->count(); ++i) { - assert(!compare_keys(node->key(i), node->key(i - 1))); - } - int count = node->count(); - if (!node->leaf()) { - for (int i = 0; i <= node->count(); ++i) { - assert(node->child(i) != nullptr); - assert(node->child(i)->parent() == node); - assert(node->child(i)->position() == i); - count += internal_verify( - node->child(i), - (i == 0) ? lo : &node->key(i - 1), - (i == node->count()) ? hi : &node->key(i)); - } - } - return count; -} - -} // namespace internal_btree -} // namespace gtl - - -#endif // S2_UTIL_GTL_BTREE_H_ diff --git a/src/s2/util/gtl/btree_container.h b/src/s2/util/gtl/btree_container.h deleted file mode 100644 index 5a8b57f0..00000000 --- a/src/s2/util/gtl/btree_container.h +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2007 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_UTIL_GTL_BTREE_CONTAINER_H_ -#define S2_UTIL_GTL_BTREE_CONTAINER_H_ - -#include -#include -#include - -#include "absl/base/internal/throw_delegate.h" -#include "s2/util/gtl/btree.h" // IWYU pragma: export - -namespace gtl { -namespace internal_btree { - -// A common base class for btree_set, btree_map, btree_multiset, and -// btree_multimap. -template -class btree_container { - public: - using key_type = typename Tree::key_type; - using value_type = typename Tree::value_type; - using size_type = typename Tree::size_type; - using difference_type = typename Tree::difference_type; - using key_compare = typename Tree::key_compare; - using value_compare = typename Tree::value_compare; - using allocator_type = typename Tree::allocator_type; - using reference = typename Tree::reference; - using const_reference = typename Tree::const_reference; - using pointer = typename Tree::pointer; - using const_pointer = typename Tree::const_pointer; - using iterator = typename Tree::iterator; - using const_iterator = typename Tree::const_iterator; - using reverse_iterator = typename Tree::reverse_iterator; - using const_reverse_iterator = typename Tree::const_reverse_iterator; - - // Constructors/assignments. - btree_container() : tree_(key_compare(), allocator_type()) {} - explicit btree_container(const key_compare &comp, - const allocator_type &alloc = allocator_type()) - : tree_(comp, alloc) {} - btree_container(const btree_container &x) = default; - btree_container(btree_container &&x) noexcept = default; - btree_container &operator=(const btree_container &x) = default; - btree_container &operator=(btree_container &&x) noexcept( - std::is_nothrow_move_assignable::value) = default; - - // Iterator routines. - iterator begin() { return tree_.begin(); } - const_iterator begin() const { return tree_.begin(); } - const_iterator cbegin() const { return tree_.begin(); } - iterator end() { return tree_.end(); } - const_iterator end() const { return tree_.end(); } - const_iterator cend() const { return tree_.end(); } - reverse_iterator rbegin() { return tree_.rbegin(); } - const_reverse_iterator rbegin() const { return tree_.rbegin(); } - const_reverse_iterator crbegin() const { return tree_.rbegin(); } - reverse_iterator rend() { return tree_.rend(); } - const_reverse_iterator rend() const { return tree_.rend(); } - const_reverse_iterator crend() const { return tree_.rend(); } - - // Lookup routines. - template - iterator lower_bound(const K &key) { - return tree_.lower_bound(key); - } - template - const_iterator lower_bound(const K &key) const { - return tree_.lower_bound(key); - } - template - iterator upper_bound(const K &key) { - return tree_.upper_bound(key); - } - template - const_iterator upper_bound(const K &key) const { - return tree_.upper_bound(key); - } - template - std::pair equal_range(const K &key) { - return tree_.equal_range(key); - } - template - std::pair equal_range(const K &key) const { - return tree_.equal_range(key); - } - - // Utility routines. - void clear() { tree_.clear(); } - void swap(btree_container &x) { tree_.swap(x.tree_); } - void verify() const { tree_.verify(); } - - // Size routines. - size_type size() const { return tree_.size(); } - size_type max_size() const { return tree_.max_size(); } - bool empty() const { return tree_.empty(); } - size_type height() const { return tree_.height(); } - size_type internal_nodes() const { return tree_.internal_nodes(); } - size_type leaf_nodes() const { return tree_.leaf_nodes(); } - size_type nodes() const { return tree_.nodes(); } - size_type bytes_used() const { return tree_.bytes_used(); } - static double average_bytes_per_value() { - return Tree::average_bytes_per_value(); - } - double fullness() const { return tree_.fullness(); } - double overhead() const { return tree_.overhead(); } - - friend bool operator==(const btree_container &x, const btree_container &y) { - if (x.size() != y.size()) return false; - return std::equal(x.begin(), x.end(), y.begin()); - } - - friend bool operator!=(const btree_container &x, const btree_container &y) { - return !(x == y); - } - - friend bool operator<(const btree_container &x, const btree_container &y) { - return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end()); - } - - friend bool operator>(const btree_container &x, const btree_container &y) { - return y < x; - } - - friend bool operator<=(const btree_container &x, const btree_container &y) { - return !(y < x); - } - - friend bool operator>=(const btree_container &x, const btree_container &y) { - return !(x < y); - } - - // The allocator used by the btree. - allocator_type get_allocator() const { return tree_.get_allocator(); } - - // The key comparator used by the btree. - key_compare key_comp() const { return tree_.key_comp(); } - value_compare value_comp() const { return tree_.value_comp(); } - - // Support absl::Hash. - template - friend State AbslHashValue(State h, const btree_container &b) { - for (const auto &v : b) { - h = State::combine(std::move(h), v); - } - return State::combine(std::move(h), b.size()); - } - - // Exposed only for tests. - static bool testonly_uses_linear_node_search() { - return Tree::testonly_uses_linear_node_search(); - } - - protected: - Tree tree_; -}; - -// A common base class for btree_set and btree_map. -template -class btree_set_container : public btree_container { - using super_type = btree_container; - using mutable_value_type = typename Tree::mutable_value_type; - using params_type = typename Tree::params_type; - friend class BtreeNodePeer; - - public: - using value_type = typename Tree::value_type; - using size_type = typename Tree::size_type; - using key_compare = typename Tree::key_compare; - using allocator_type = typename Tree::allocator_type; - using iterator = typename Tree::iterator; - using const_iterator = typename Tree::const_iterator; - - // Inherit constructors. - using super_type::super_type; - btree_set_container() {} - - // Range constructor. - template - btree_set_container(InputIterator b, InputIterator e, - const key_compare &comp = key_compare(), - const allocator_type &alloc = allocator_type()) - : super_type(comp, alloc) { - insert(b, e); - } - - // Initializer list constructor. - btree_set_container(std::initializer_list init, - const key_compare &comp = key_compare(), - const allocator_type &alloc = allocator_type()) - : btree_set_container(init.begin(), init.end(), comp, alloc) {} - - // Lookup routines. - template - iterator find(const K &key) { - return this->tree_.find_unique(key); - } - template - const_iterator find(const K &key) const { - return this->tree_.find_unique(key); - } - template - size_type count(const K &key) const { - return this->tree_.count_unique(key); - } - - // Insertion routines. - std::pair insert(const value_type &x) { - return this->tree_.insert_unique(params_type::key(x), x); - } - std::pair insert(value_type &&x) { - return this->tree_.insert_unique(params_type::key(x), std::move(x)); - } - template - std::pair emplace(Args &&... args) { - mutable_value_type v(std::forward(args)...); - return this->tree_.insert_unique(params_type::key(v), std::move(v)); - } - iterator insert(iterator position, const value_type &x) { - return this->tree_.insert_hint_unique(position, params_type::key(x), x); - } - iterator insert(iterator position, value_type &&x) { - return this->tree_.insert_hint_unique(position, params_type::key(x), - std::move(x)); - } - template - iterator emplace_hint(iterator position, Args &&... args) { - mutable_value_type v(std::forward(args)...); - return this->tree_.insert_hint_unique(position, params_type::key(v), - std::move(v)); - } - template - void insert(InputIterator b, InputIterator e) { - this->tree_.insert_iterator_unique(b, e); - } - void insert(std::initializer_list init) { - this->tree_.insert_iterator_unique(init.begin(), init.end()); - } - - // Deletion routines. - template - int erase(const K &key) { - return this->tree_.erase_unique(key); - } - // Erase the specified iterator from the btree. The iterator must be valid - // (i.e. not equal to end()). Return an iterator pointing to the node after - // the one that was erased (or end() if none exists). - iterator erase(const iterator &iter) { return this->tree_.erase(iter); } - void erase(const iterator &first, const iterator &last) { - this->tree_.erase(first, last); - } -}; - -// Base class for btree_map. -template -class btree_map_container : public btree_set_container { - using super_type = btree_set_container; - - public: - using key_type = typename Tree::key_type; - using mapped_type = typename Tree::mapped_type; - using value_type = typename Tree::value_type; - using key_compare = typename Tree::key_compare; - using allocator_type = typename Tree::allocator_type; - - // Inherit constructors. - using super_type::super_type; - btree_map_container() {} - - // Insertion routines. - mapped_type &operator[](const key_type &key) { - return this->tree_ - .insert_unique(key, std::piecewise_construct, - std::forward_as_tuple(key), std::forward_as_tuple()) - .first->second; - } - mapped_type &operator[](key_type &&key) { - return this->tree_ - .insert_unique(key, std::piecewise_construct, - std::forward_as_tuple(std::move(key)), - std::forward_as_tuple()) - .first->second; - } - - mapped_type &at(const key_type &key) { - auto it = this->find(key); - if (it == this->end()) - absl::base_internal::ThrowStdOutOfRange("btree_map::at"); - return it->second; - } - const mapped_type &at(const key_type &key) const { - auto it = this->find(key); - if (it == this->end()) - absl::base_internal::ThrowStdOutOfRange("btree_map::at"); - return it->second; - } -}; - -// A common base class for btree_multiset and btree_multimap. -template -class btree_multiset_container : public btree_container { - using super_type = btree_container; - - public: - using key_type = typename Tree::key_type; - using value_type = typename Tree::value_type; - using mapped_type = typename Tree::mapped_type; - using size_type = typename Tree::size_type; - using key_compare = typename Tree::key_compare; - using allocator_type = typename Tree::allocator_type; - using iterator = typename Tree::iterator; - using const_iterator = typename Tree::const_iterator; - - // Inherit constructors. - using super_type::super_type; - btree_multiset_container() {} - - // Range constructor. - template - btree_multiset_container(InputIterator b, InputIterator e, - const key_compare &comp = key_compare(), - const allocator_type &alloc = allocator_type()) - : super_type(comp, alloc) { - insert(b, e); - } - - // Initializer list constructor. - btree_multiset_container(std::initializer_list init, - const key_compare &comp = key_compare(), - const allocator_type &alloc = allocator_type()) - : btree_multiset_container(init.begin(), init.end(), comp, alloc) {} - - // Lookup routines. - template - iterator find(const K &key) { - return this->tree_.find_multi(key); - } - template - const_iterator find(const K &key) const { - return this->tree_.find_multi(key); - } - template - size_type count(const K &key) const { - return this->tree_.count_multi(key); - } - - // Insertion routines. - iterator insert(const value_type &x) { return this->tree_.insert_multi(x); } - iterator insert(value_type &&x) { - return this->tree_.insert_multi(std::move(x)); - } - iterator insert(iterator position, const value_type &x) { - return this->tree_.insert_hint_multi(position, x); - } - iterator insert(iterator position, value_type &&x) { - return this->tree_.insert_hint_multi(position, std::move(x)); - } - template - void insert(InputIterator b, InputIterator e) { - this->tree_.insert_iterator_multi(b, e); - } - void insert(std::initializer_list init) { - this->tree_.insert_iterator_multi(init.begin(), init.end()); - } - - // Deletion routines. - template - int erase(const K &key) { - return this->tree_.erase_multi(key); - } - // Erase the specified iterator from the btree. The iterator must be valid - // (i.e. not equal to end()). Return an iterator pointing to the node after - // the one that was erased (or end() if none exists). - iterator erase(const iterator &iter) { return this->tree_.erase(iter); } - void erase(const iterator &first, const iterator &last) { - this->tree_.erase(first, last); - } -}; - -// A base class for btree_multimap. -template -class btree_multimap_container : public btree_multiset_container { - using super_type = btree_multiset_container; - - public: - using mapped_type = typename Tree::mapped_type; - - // Inherit constructors. - using super_type::super_type; - btree_multimap_container() {} -}; - -} // namespace internal_btree -} // namespace gtl - -#endif // S2_UTIL_GTL_BTREE_CONTAINER_H_ diff --git a/src/s2/util/gtl/btree_map.h b/src/s2/util/gtl/btree_map.h deleted file mode 100644 index 6e9ef622..00000000 --- a/src/s2/util/gtl/btree_map.h +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2007 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. -// - -// -// A btree_map<> implements the STL unique sorted associative container -// interface and the pair associative container interface (a.k.a map<>) using a -// btree. A btree_multimap<> implements the STL multiple sorted associative -// container interface and the pair associative container interface (a.k.a -// multimap<>) using a btree. See btree.h for details of the btree -// implementation and caveats. -// - -#ifndef S2_UTIL_GTL_BTREE_MAP_H_ -#define S2_UTIL_GTL_BTREE_MAP_H_ - -#include -#include -#include -#include -#include - -#include "s2/util/gtl/btree.h" // IWYU pragma: export -#include "s2/util/gtl/btree_container.h" // IWYU pragma: export - -namespace gtl { - -template , - typename Alloc = std::allocator>, - int TargetNodeSize = 256> -class btree_map - : public internal_btree::btree_map_container< - internal_btree::btree>> { - using Base = typename btree_map::btree_map_container; - - public: - btree_map() {} - using Base::Base; -}; - -template -void swap(btree_map &x, btree_map &y) { - return x.swap(y); -} - -template , - typename Alloc = std::allocator>, - int TargetNodeSize = 256> -class btree_multimap - : public internal_btree::btree_multimap_container< - internal_btree::btree>> { - using Base = typename btree_multimap::btree_multimap_container; - - public: - btree_multimap() {} - using Base::Base; -}; - -template -void swap(btree_multimap &x, btree_multimap &y) { - return x.swap(y); -} - -} // namespace gtl - -#endif // S2_UTIL_GTL_BTREE_MAP_H_ diff --git a/src/s2/util/gtl/btree_set.h b/src/s2/util/gtl/btree_set.h deleted file mode 100644 index c03490e1..00000000 --- a/src/s2/util/gtl/btree_set.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2007 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. -// - -// -// A btree_set<> implements the STL unique sorted associative container -// interface (a.k.a set<>) using a btree. A btree_multiset<> implements the STL -// multiple sorted associative container interface (a.k.a multiset<>) using a -// btree. See btree.h for details of the btree implementation and caveats. -// - -#ifndef S2_UTIL_GTL_BTREE_SET_H_ -#define S2_UTIL_GTL_BTREE_SET_H_ - -#include -#include -#include - -#include "s2/util/gtl/btree.h" // IWYU pragma: export -#include "s2/util/gtl/btree_container.h" // IWYU pragma: export - -namespace gtl { - -template , - typename Alloc = std::allocator, int TargetNodeSize = 256> -class btree_set - : public internal_btree::btree_set_container< - internal_btree::btree>> { - using Base = typename btree_set::btree_set_container; - - public: - btree_set() {} - using Base::Base; -}; - -template -void swap(btree_set &x, btree_set &y) { - return x.swap(y); -} - -template , - typename Alloc = std::allocator, int TargetNodeSize = 256> -class btree_multiset - : public internal_btree::btree_multiset_container< - internal_btree::btree>> { - using Base = typename btree_multiset::btree_multiset_container; - - public: - btree_multiset() {} - using Base::Base; -}; - -template -void swap(btree_multiset &x, btree_multiset &y) { - return x.swap(y); -} - -} // namespace gtl - -#endif // S2_UTIL_GTL_BTREE_SET_H_ diff --git a/src/s2/util/gtl/compact_array.h b/src/s2/util/gtl/compact_array.h index cf11f77b..033db144 100644 --- a/src/s2/util/gtl/compact_array.h +++ b/src/s2/util/gtl/compact_array.h @@ -40,7 +40,9 @@ #include #include #include + #include +#include #include #include #include // NOLINT @@ -48,11 +50,12 @@ #include #include +#include "absl/base/macros.h" +#include "absl/meta/type_traits.h" + #include "s2/base/integral_types.h" #include "s2/base/logging.h" -#include "absl/base/macros.h" #include "s2/base/port.h" -#include "absl/meta/type_traits.h" #include "s2/util/bits/bits.h" #include "s2/util/gtl/container_logging.h" @@ -70,14 +73,14 @@ class compact_array_base { static const int kMaxSize = (1 << kSizeNumBits) - 1; #ifdef IS_LITTLE_ENDIAN - uint32 size_ : kSizeNumBits; // number of valid items in the array - uint32 capacity_ : kCapacityNumBits; // allocated array size - uint32 is_exponent_ : 1; // whether capacity_ is an exponent + uint32 size_ : kSizeNumBits; // number of valid items in the array + uint32 capacity_ : kCapacityNumBits; // allocated array size + uint32 is_exponent_ : 1; // whether capacity_ is an exponent // This object might share memory representation (ie. union) with // other data structures. We reserved the DO_NOT_USE (32nd bit in // little endian format) to be used as a tag. - uint32 DO_NOT_USE : 1; + uint32 DO_NOT_USE : 1; #else uint32 DO_NOT_USE : 1; uint32 is_exponent_ : 1; @@ -86,10 +89,7 @@ class compact_array_base { #endif // Opportunistically consider allowing inlined elements. - // dd: this has to be disabled to pass CRAN checks, since there is a - // (potentially) zero-length array that is not the last element of the class (so - // this can't be silenced using __extension__) -#if defined(_LP64) && defined(__GNUC__) && false +#if defined(_LP64) && defined(__GNUC__) // With 64-bit pointers, our approach is to form a 16-byte struct: // [5 bytes for size, capacity, is_exponent and is_inlined] // [3 bytes of padding or inlined elements] @@ -124,7 +124,7 @@ class compact_array_base { char unused_padding_[kUnusedPaddingBytes]; // inlined_elements_ stores the first N elements, potentially as few as zero. - __extension__ char inlined_elements_[3 - kUnusedPaddingBytes]; + char inlined_elements_[3 - kUnusedPaddingBytes]; // compact_array_base itself is at least as aligned as a T* because of the // T* member inside this union. The only reason to split inlined_elements_ @@ -148,7 +148,8 @@ class compact_array_base { return const_cast*>(this)->Array(); } - typedef typename A::template rebind::other value_allocator_type; + using value_allocator_type = + typename std::allocator_traits::template rebind_alloc; public: typedef T value_type; @@ -157,7 +158,7 @@ class compact_array_base { typedef const value_type* const_pointer; typedef value_type& reference; typedef const value_type& const_reference; - typedef uint32 size_type; + typedef uint32 size_type; typedef ptrdiff_t difference_type; typedef value_type* iterator; @@ -276,6 +277,15 @@ class compact_array_base { insert(p, first, last, Int()); } + template + reference emplace_back(Args&&... args) { + return *Insert(end(), value_type(std::forward(args)...)); + } + template + iterator emplace(const_iterator p, Args&&... args) { + return Insert(p, value_type(std::forward(args)...)); + } + iterator erase(const_iterator p) { size_type index = p - begin(); erase_aux(p, 1); @@ -357,6 +367,13 @@ class compact_array_base { set_size(n); } + template + friend H AbslHashValue(H h, const compact_array_base& v) { + return H::combine( + H::combine_contiguous(std::move(h), v.ConstArray(), v.size()), + v.size()); + } + private: // Low-level helper functions. void set_size(size_type n) { S2_DCHECK_LE(n, capacity()); @@ -396,10 +413,7 @@ class compact_array_base { value_allocator_type allocator; T* new_ptr = allocator.allocate(capacity()); - // dd: this modification fixes a ASAN/UBSAN error, because - // when old_capacity is 0, Array() is nullptr, which is UB - // for memcpy. - if (old_capacity > 0) { + if (old_capacity != 0) { memcpy(new_ptr, Array(), old_capacity * sizeof(T)); allocator.deallocate(Array(), old_capacity); } diff --git a/src/s2/util/gtl/container_logging.h b/src/s2/util/gtl/container_logging.h index 93d0d37d..dbdcf7be 100644 --- a/src/s2/util/gtl/container_logging.h +++ b/src/s2/util/gtl/container_logging.h @@ -18,18 +18,34 @@ // Utilities for container logging. // TODO(user): Broaden the scope and rename to "stream_util.h" // +// +// The typical use looks like this: +// +// S2_LOG(INFO) << gtl::LogContainer(container); +// +// By default, LogContainer() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +// +// Policies can be specified: +// +// S2_LOG(INFO) << gtl::LogContainer(container, gtl::LogMultiline()); +// +// The above example will print the container using newlines between +// elements, enclosed in [] braces. +// +// See below for further details on policies. #ifndef S2_UTIL_GTL_CONTAINER_LOGGING_H_ #define S2_UTIL_GTL_CONTAINER_LOGGING_H_ #include #include +#include #include #include #include "s2/base/integral_types.h" #include "s2/base/port.h" -#include "s2/strings/ostringstream.h" namespace gtl { @@ -162,7 +178,7 @@ inline void LogRangeToStream(std::ostream &out, // NOLINT IteratorT begin, IteratorT end, const PolicyT &policy) { policy.LogOpening(out); - for (size_t i = 0; begin != end && i < policy.MaxElements(); ++i, ++begin) { + for (int64 i = 0; begin != end && i < policy.MaxElements(); ++i, ++begin) { if (i == 0) { policy.LogFirstSeparator(out); } else { @@ -201,9 +217,9 @@ class RangeLogger { // operator<< above is generally recommended. However, some situations may // require a string, so a convenience str() method is provided as well. std::string str() const { - std::string s; - ::strings::OStringStream(&s) << *this; - return s; + std::stringstream ss; + ss << *this; + return ss.str(); } private: diff --git a/src/s2/util/gtl/dense_hash_set.h b/src/s2/util/gtl/dense_hash_set.h index b3b00031..afcdb30c 100644 --- a/src/s2/util/gtl/dense_hash_set.h +++ b/src/s2/util/gtl/dense_hash_set.h @@ -117,6 +117,7 @@ #define S2_UTIL_GTL_DENSE_HASH_SET_H_ #include + #include #include #include diff --git a/src/s2/util/gtl/densehashtable.h b/src/s2/util/gtl/densehashtable.h index 737c307b..9c737818 100644 --- a/src/s2/util/gtl/densehashtable.h +++ b/src/s2/util/gtl/densehashtable.h @@ -107,20 +107,22 @@ #include #include -#include // for FILE, fwrite, fread -#include // For swap(), eg +#include // for FILE, fwrite, fread + +#include // For swap(), eg +#include #include -#include // For iterator tags -#include // for numeric_limits -#include // For uninitialized_fill +#include // For iterator tags +#include // for numeric_limits +#include // For uninitialized_fill #include #include +#include #include #include -#include -#include "s2/util/gtl/hashtable_common.h" #include "s2/base/port.h" +#include "s2/util/gtl/hashtable_common.h" #include // For length_error namespace gtl { @@ -182,7 +184,10 @@ struct dense_hashtable_const_iterator; template struct dense_hashtable_iterator { private: - typedef typename A::template rebind::other value_alloc_type; + using value_alloc_type = + typename std::allocator_traits::template rebind_alloc; + using value_alloc_traits = + typename std::allocator_traits::template rebind_traits; public: typedef dense_hashtable_iterator @@ -192,10 +197,10 @@ struct dense_hashtable_iterator { typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; - typedef typename value_alloc_type::difference_type difference_type; - typedef typename value_alloc_type::size_type size_type; - typedef typename value_alloc_type::reference reference; - typedef typename value_alloc_type::pointer pointer; + typedef typename value_alloc_traits::difference_type difference_type; + typedef typename value_alloc_traits::size_type size_type; + typedef typename value_alloc_traits::value_type& reference; + typedef typename value_alloc_traits::pointer pointer; // "Real" constructor and default constructor dense_hashtable_iterator( @@ -245,7 +250,10 @@ struct dense_hashtable_iterator { template struct dense_hashtable_const_iterator { private: - typedef typename A::template rebind::other value_alloc_type; + using value_alloc_type = + typename std::allocator_traits::template rebind_alloc; + using value_alloc_traits = + typename std::allocator_traits::template rebind_traits; public: typedef dense_hashtable_iterator @@ -255,10 +263,10 @@ struct dense_hashtable_const_iterator { typedef std::forward_iterator_tag iterator_category; // very little defined! typedef V value_type; - typedef typename value_alloc_type::difference_type difference_type; - typedef typename value_alloc_type::size_type size_type; - typedef typename value_alloc_type::const_reference reference; - typedef typename value_alloc_type::const_pointer pointer; + typedef typename value_alloc_traits::difference_type difference_type; + typedef typename value_alloc_traits::size_type size_type; + typedef const typename value_alloc_traits::value_type& reference; + typedef typename value_alloc_traits::const_pointer pointer; // "Real" constructor and default constructor dense_hashtable_const_iterator( @@ -311,7 +319,10 @@ template class dense_hashtable { private: - typedef typename Alloc::template rebind::other value_alloc_type; + using value_alloc_type = + typename std::allocator_traits::template rebind_alloc; + using value_alloc_traits = + typename std::allocator_traits::template rebind_traits; public: @@ -321,12 +332,12 @@ class dense_hashtable { typedef EqualKey key_equal; typedef Alloc allocator_type; - typedef typename value_alloc_type::size_type size_type; - typedef typename value_alloc_type::difference_type difference_type; - typedef typename value_alloc_type::reference reference; - typedef typename value_alloc_type::const_reference const_reference; - typedef typename value_alloc_type::pointer pointer; - typedef typename value_alloc_type::const_pointer const_pointer; + typedef typename value_alloc_traits::size_type size_type; + typedef typename value_alloc_traits::difference_type difference_type; + typedef typename value_alloc_traits::value_type& reference; + typedef const typename value_alloc_traits::value_type& const_reference; + typedef typename value_alloc_traits::pointer pointer; + typedef typename value_alloc_traits::const_pointer const_pointer; typedef dense_hashtable_iterator iterator; @@ -532,9 +543,9 @@ class dense_hashtable { } private: - bool test_empty(size_type bucknum, const_pointer table) const { + bool test_empty(size_type bucknum, const_pointer ptable) const { assert(settings.use_empty()); - return equals(key_info.empty, get_key(table[bucknum])); + return equals(key_info.empty, get_key(ptable[bucknum])); } void fill_range_with_empty(pointer table_start, pointer table_end) { @@ -575,7 +586,9 @@ class dense_hashtable { // FUNCTIONS CONCERNING SIZE public: size_type size() const { return num_elements - num_deleted; } - size_type max_size() const { return get_allocator().max_size(); } + size_type max_size() const { + return std::allocator_traits::max_size(get_allocator()); + } bool empty() const { return size() == 0; } size_type bucket_count() const { return num_buckets; } size_type max_bucket_count() const { return max_size(); } @@ -1034,7 +1047,7 @@ class dense_hashtable { const size_type bucket_count_minus_one = bucket_count() - 1; size_type bucknum = key_hash & bucket_count_minus_one; size_type insert_pos = ILLEGAL_BUCKET; // where we would insert - while (1) { // probe until something happens + while (true) { // probe until something happens if (test_empty(bucknum)) { // bucket is empty if (insert_pos == ILLEGAL_BUCKET) // found no prior place to insert return std::pair(ILLEGAL_BUCKET, bucknum); @@ -1072,7 +1085,7 @@ class dense_hashtable { size_type num_probes = 0; // how many times we've probed const size_type bucket_count_minus_one = bucket_count() - 1; size_type bucknum = key_hash & bucket_count_minus_one; - while (1) { // probe until something happens + while (true) { // probe until something happens if (equals(key, get_key(table[bucknum]))) { return std::pair(bucknum, true); } else if (test_empty(bucknum)) { @@ -1290,7 +1303,7 @@ class dense_hashtable { void erase(iterator pos) { - if (pos == end()) return; // sanity check + if (pos == end()) return; set_deleted(pos); ++num_deleted; // will think about shrink after next insert @@ -1312,7 +1325,7 @@ class dense_hashtable { // you can't use the object after it's erased anyway, so it doesn't matter // if it's const or not. void erase(const_iterator pos) { - if (pos == end()) return; // sanity check + if (pos == end()) return; set_deleted(pos); ++num_deleted; // will think about shrink after next insert diff --git a/src/s2/util/gtl/hashtable_common.h b/src/s2/util/gtl/hashtable_common.h index a5ea2a09..a8c06901 100644 --- a/src/s2/util/gtl/hashtable_common.h +++ b/src/s2/util/gtl/hashtable_common.h @@ -48,6 +48,7 @@ #include #include + #include #include // For length_error diff --git a/src/s2/util/hash/mix.h b/src/s2/util/hash/mix.h index 65f7bfaf..1d0936e1 100644 --- a/src/s2/util/hash/mix.h +++ b/src/s2/util/hash/mix.h @@ -21,8 +21,10 @@ #define S2_UTIL_HASH_MIX_H_ #include + #include + // Fast mixing of hash values -- not strong enough for fingerprinting. // May change from time to time. // @@ -69,6 +71,7 @@ class HashMix { (hash_ >> (std::numeric_limits::digits - 19))) + val; } size_t get() const { return hash_; } + private: size_t hash_; }; diff --git a/src/s2/util/math/exactfloat/exactfloat.cc b/src/s2/util/math/exactfloat/exactfloat.cc index ee645b65..160bf6ba 100644 --- a/src/s2/util/math/exactfloat/exactfloat.cc +++ b/src/s2/util/math/exactfloat/exactfloat.cc @@ -20,20 +20,26 @@ #include #include #include + #include #include +#include #include +#include #include #include // for OPENSSL_free -#include "s2/base/integral_types.h" -#include "s2/base/logging.h" #include "absl/base/macros.h" #include "absl/container/fixed_array.h" +#include "absl/numeric/int128.h" + +#include "s2/base/integral_types.h" +#include "s2/base/logging.h" using std::max; using std::min; +using std::string; // Define storage for constants. const int ExactFloat::kMinExp; @@ -136,10 +142,10 @@ inline static uint64 BN_ext_get_uint64(const BIGNUM* bn) { uint64 r; #ifdef IS_LITTLE_ENDIAN S2_CHECK_EQ(BN_bn2lebinpad(bn, reinterpret_cast(&r), - sizeof(r)), sizeof(r)); -#elif IS_BIG_ENDIAN + sizeof(r)), sizeof(r)); +#elif defined(IS_BIG_ENDIAN) S2_CHECK_EQ(BN_bn2binpad(bn, reinterpret_cast(&r), - sizeof(r)), sizeof(r)); + sizeof(r)), sizeof(r)); #else #error one of IS_LITTLE_ENDIAN or IS_BIG_ENDIAN should be defined! #endif @@ -409,13 +415,13 @@ std::string ExactFloat::ToStringWithMaxDigits(int max_digits) const { str.append(digits.begin() + 1, digits.end()); } char exp_buf[20]; - snprintf(exp_buf, sizeof(exp_buf), "e%+02d", exp10 - 1); + sprintf(exp_buf, "e%+02d", exp10 - 1); str += exp_buf; } else { // Use fixed format. We split this into two cases depending on whether // the integer portion is non-zero or not. if (exp10 > 0) { - if (exp10 >= digits.size()) { + if (static_cast(exp10) >= digits.size()) { str += digits; for (int i = exp10 - digits.size(); i > 0; --i) { str.push_back('0'); @@ -512,7 +518,7 @@ int ExactFloat::GetDecimalDigits(int max_digits, std::string* digits) const { std::string ExactFloat::ToUniqueString() const { char prec_buf[20]; - snprintf(prec_buf, sizeof(prec_buf), "<%d>", prec()); + sprintf(prec_buf, "<%d>", prec()); return ToString() + prec_buf; } diff --git a/src/s2/util/math/exactfloat/exactfloat.h b/src/s2/util/math/exactfloat/exactfloat.h index 028321ee..a2ef9cb8 100644 --- a/src/s2/util/math/exactfloat/exactfloat.h +++ b/src/s2/util/math/exactfloat/exactfloat.h @@ -15,24 +15,24 @@ // Author: ericv@google.com (Eric Veach) // -// ExactFloat is a multiple-precision floating point type based on the OpenSSL -// Bignum library. It has the same interface as the built-in "float" and -// "double" types, but only supports the subset of operators and intrinsics -// where it is possible to compute the result exactly. So for example, -// ExactFloat supports addition and multiplication but not division (since in -// general, the quotient of two floating-point numbers cannot be represented -// exactly). Exact arithmetic is useful for geometric algorithms, especially -// for disambiguating cases where ordinary double-precision arithmetic yields -// an uncertain result. +// ExactFloat is a multiple-precision floating point type that uses the OpenSSL +// Bignum library for numerical calculations. It has the same interface as the +// built-in "float" and "double" types, but only supports the subset of +// operators and intrinsics where it is possible to compute the result exactly. +// So for example, ExactFloat supports addition and multiplication but not +// division (since in general, the quotient of two floating-point numbers cannot +// be represented exactly). Exact arithmetic is useful for geometric +// algorithms, especially for disambiguating cases where ordinary +// double-precision arithmetic yields an uncertain result. // -// ExactFloat is a subset of the faster and more capable MPFloat class (which -// is based on the GNU MPFR library). The main reason to use this class -// rather than MPFloat is that it is subject to a BSD-style license rather -// than the much more restrictive LGPL license. +// ExactFloat is a subset of the now-retired MPFloat class, which used the GNU +// MPFR library for numerical calculations. The main reason for the switch to +// ExactFloat is that OpenSSL has a BSD-style license whereas MPFR has a much +// more restrictive LGPL license. // -// It has the following features: +// ExactFloat has the following features: // -// - ExactFloat uses the same syntax as the built-in "float" and "double" +// - It uses the same syntax as the built-in "float" and "double" // types, for example: x += 4 + fabs(2*y*y - z*z). There are a few // differences (see below), but the syntax is compatible enough so that // ExactFloat can be used as a template argument to templatized classes @@ -112,8 +112,12 @@ #include #include #include + #include +#include #include +#include +#include #include #include @@ -128,16 +132,16 @@ class ExactFloat { // The maximum exponent supported. If a value has an exponent larger than // this, it is replaced by infinity (with the appropriate sign). - static const int kMaxExp = 200*1000*1000; // About 10**(60 million) + static constexpr int kMaxExp = 200 * 1000 * 1000; // About 10**(60 million) // The minimum exponent supported. If a value has an exponent less than // this, it is replaced by zero (with the appropriate sign). - static const int kMinExp = -kMaxExp; // About 10**(-60 million) + static constexpr int kMinExp = -kMaxExp; // About 10**(-60 million) // The maximum number of mantissa bits supported. If a value has more // mantissa bits than this, it is replaced with NaN. (It is expected that // users of this class will never want this much precision.) - static const int kMaxPrec = 64 << 20; // About 20 million digits + static constexpr int kMaxPrec = 64 << 20; // About 20 million digits // Rounding modes. kRoundTiesToEven and kRoundTiesAwayFromZero both round // to the nearest representable value unless two values are equally close. @@ -536,9 +540,9 @@ class ExactFloat { // mantissa of zero. Do not change these values; methods such as // is_normal() make assumptions about their ordering. Non-normal numbers // can have either a positive or negative sign (including zero and NaN). - static const int32 kExpNaN = INT_MAX; - static const int32 kExpInfinity = INT_MAX - 1; - static const int32 kExpZero = INT_MAX - 2; + static constexpr int32 kExpNaN = INT_MAX; + static constexpr int32 kExpInfinity = INT_MAX - 1; + static constexpr int32 kExpZero = INT_MAX - 2; // Normal numbers are represented as (sign_ * bn_ * (2 ** bn_exp_)), where: // - sign_ is either +1 or -1 @@ -550,7 +554,7 @@ class ExactFloat { // A standard IEEE "double" has a 53-bit mantissa consisting of a 52-bit // fraction plus an implicit leading "1" bit. - static const int kDoubleMantissaBits = 53; + static constexpr int kDoubleMantissaBits = 53; // Convert an ExactFloat with no more than 53 bits in its mantissa to a // "double". This method handles non-normal values (NaN, etc). diff --git a/src/s2/util/math/mathutil.cc b/src/s2/util/math/mathutil.cc index c9bad601..7373f7ec 100644 --- a/src/s2/util/math/mathutil.cc +++ b/src/s2/util/math/mathutil.cc @@ -19,17 +19,6 @@ #include #include -namespace { -// Returns the sign of x: -// -1 if x < 0, -// +1 if x > 0, -// 0 if x = 0. -template -inline T sgn(const T x) { - return (x == 0 ? 0 : (x < 0 ? -1 : 1)); -} -} // namespace - bool MathUtil::RealRootsForCubic(long double const a, long double const b, long double const c, diff --git a/src/s2/util/math/mathutil.h b/src/s2/util/math/mathutil.h index ac9eebfe..430159b9 100644 --- a/src/s2/util/math/mathutil.h +++ b/src/s2/util/math/mathutil.h @@ -24,6 +24,18 @@ #include #include "s2/base/integral_types.h" +#include "s2/base/logging.h" +#include "s2/util/bits/bits.h" + +// Returns the sign of x: +// -1 if x < 0, +// +1 if x > 0, +// 0 if x = 0, +// unspecified if x is NaN. +template +inline T sgn(const T x) { + return (x == 0 ? 0 : (x < 0 ? -1 : 1)); +} class MathUtil { public: @@ -154,6 +166,56 @@ class MathUtil { return Round(x); #endif // if defined __GNUC__ && ... } + + // Computes v^i, where i is a non-negative integer. + // When T is a floating point type, this has the same semantics as pow(), but + // is much faster. + // T can also be any integral type, in which case computations will be + // performed in the value domain of this integral type, and overflow semantics + // will be those of T. + // You can also use any type for which operator*= is defined. + template + static T IPow(T base, int exp) { + S2_DCHECK_GE(exp, 0); + uint32 uexp = static_cast(exp); + + if (uexp < 16) { + T result = (uexp & 1) ? base : static_cast(1); + if (uexp >= 2) { + base *= base; + if (uexp & 2) { + result *= base; + } + if (uexp >= 4) { + base *= base; + if (uexp & 4) { + result *= base; + } + if (uexp >= 8) { + base *= base; + result *= base; + } + } + } + return result; + } + + T result = base; + int count = 31 ^ Bits::Log2FloorNonZero(uexp); + + uexp <<= count; + count ^= 31; + + while (count--) { + uexp <<= 1; + result *= result; + if (uexp >= 0x80000000) { + result *= base; + } + } + + return result; + } }; // ========================================================================= // diff --git a/src/s2/util/math/matrix3x3.h b/src/s2/util/math/matrix3x3.h index e6b60afd..abf829cf 100644 --- a/src/s2/util/math/matrix3x3.h +++ b/src/s2/util/math/matrix3x3.h @@ -33,7 +33,7 @@ #include #include -#include +#include #include "s2/base/logging.h" #include "s2/util/math/mathutil.h" @@ -77,22 +77,21 @@ class Matrix3x3 { // Casting constructor template static Matrix3x3 Cast(const Matrix3x3 &mb) { - return Matrix3x3(static_cast(mb(0, 0)), - static_cast(mb(0, 1)), - static_cast(mb(0, 2)), - static_cast(mb(1, 0)), - static_cast(mb(1, 1)), - static_cast(mb(1, 2)), - static_cast(mb(2, 0)), - static_cast(mb(2, 1)), + return Matrix3x3(static_cast(mb(0, 0)), // + static_cast(mb(0, 1)), // + static_cast(mb(0, 2)), // + static_cast(mb(1, 0)), // + static_cast(mb(1, 1)), // + static_cast(mb(1, 2)), // + static_cast(mb(2, 0)), // + static_cast(mb(2, 1)), // static_cast(mb(2, 2))); } // Change the value of all the coefficients of the matrix - inline Matrix3x3 & - Set(const VType &m00, const VType &m01, const VType &m02, - const VType &m10, const VType &m11, const VType &m12, - const VType &m20, const VType &m21, const VType &m22) { + inline Matrix3x3 &Set(const VType &m00, const VType &m01, const VType &m02, + const VType &m10, const VType &m11, const VType &m12, + const VType &m20, const VType &m21, const VType &m22) { m_[0][0] = m00; m_[0][1] = m01; m_[0][2] = m02; @@ -108,7 +107,7 @@ class Matrix3x3 { } // Matrix addition - inline Matrix3x3& operator+=(const Matrix3x3 &mb) { + inline Matrix3x3 &operator+=(const Matrix3x3 &mb) { m_[0][0] += mb.m_[0][0]; m_[0][1] += mb.m_[0][1]; m_[0][2] += mb.m_[0][2]; @@ -124,7 +123,7 @@ class Matrix3x3 { } // Matrix subtration - inline Matrix3x3& operator-=(const Matrix3x3 &mb) { + inline Matrix3x3 &operator-=(const Matrix3x3 &mb) { m_[0][0] -= mb.m_[0][0]; m_[0][1] -= mb.m_[0][1]; m_[0][2] -= mb.m_[0][2]; @@ -140,7 +139,7 @@ class Matrix3x3 { } // Matrix multiplication by a scalar - inline Matrix3x3& operator*=(const VType &k) { + inline Matrix3x3 &operator*=(const VType &k) { m_[0][0] *= k; m_[0][1] *= k; m_[0][2] *= k; @@ -167,8 +166,8 @@ class Matrix3x3 { // Change the sign of all the coefficients in the matrix friend inline Matrix3x3 operator-(const Matrix3x3 &vb) { - return Matrix3x3(-vb.m_[0][0], -vb.m_[0][1], -vb.m_[0][2], - -vb.m_[1][0], -vb.m_[1][1], -vb.m_[1][2], + return Matrix3x3(-vb.m_[0][0], -vb.m_[0][1], -vb.m_[0][2], // + -vb.m_[1][0], -vb.m_[1][1], -vb.m_[1][2], // -vb.m_[2][0], -vb.m_[2][1], -vb.m_[2][2]); } @@ -178,11 +177,12 @@ class Matrix3x3 { } friend inline Matrix3x3 operator*(const VType &k, const Matrix3x3 &mb) { - return Matrix3x3(mb)*k; + return Matrix3x3(mb) * k; } // Matrix multiplication inline Matrix3x3 operator*(const Matrix3x3 &mb) const { + // clang-format off return Matrix3x3( m_[0][0] * mb.m_[0][0] + m_[0][1] * mb.m_[1][0] + m_[0][2] * mb.m_[2][0], m_[0][0] * mb.m_[0][1] + m_[0][1] * mb.m_[1][1] + m_[0][2] * mb.m_[2][1], @@ -195,39 +195,30 @@ class Matrix3x3 { m_[2][0] * mb.m_[0][0] + m_[2][1] * mb.m_[1][0] + m_[2][2] * mb.m_[2][0], m_[2][0] * mb.m_[0][1] + m_[2][1] * mb.m_[1][1] + m_[2][2] * mb.m_[2][1], m_[2][0] * mb.m_[0][2] + m_[2][1] * mb.m_[1][2] + m_[2][2] * mb.m_[2][2]); + // clang-format on } // Multiplication of a matrix by a vector inline MVector operator*(const MVector &v) const { - return MVector( - m_[0][0] * v[0] + m_[0][1] * v[1] + m_[0][2] * v[2], - m_[1][0] * v[0] + m_[1][1] * v[1] + m_[1][2] * v[2], - m_[2][0] * v[0] + m_[2][1] * v[1] + m_[2][2] * v[2]); + return MVector(m_[0][0] * v[0] + m_[0][1] * v[1] + m_[0][2] * v[2], + m_[1][0] * v[0] + m_[1][1] * v[1] + m_[1][2] * v[2], + m_[2][0] * v[0] + m_[2][1] * v[1] + m_[2][2] * v[2]); } // Return the determinant of the matrix - inline VType Det(void) const { - return m_[0][0] * m_[1][1] * m_[2][2] - + m_[0][1] * m_[1][2] * m_[2][0] - + m_[0][2] * m_[1][0] * m_[2][1] - - m_[2][0] * m_[1][1] * m_[0][2] - - m_[2][1] * m_[1][2] * m_[0][0] - - m_[2][2] * m_[1][0] * m_[0][1]; + inline VType Det() const { + return m_[0][0] * m_[1][1] * m_[2][2] + m_[0][1] * m_[1][2] * m_[2][0] + + m_[0][2] * m_[1][0] * m_[2][1] - m_[2][0] * m_[1][1] * m_[0][2] - + m_[2][1] * m_[1][2] * m_[0][0] - m_[2][2] * m_[1][0] * m_[0][1]; } // Return the trace of the matrix - inline VType Trace(void) const { - return m_[0][0] + m_[1][1] + m_[2][2]; - } + inline VType Trace() const { return m_[0][0] + m_[1][1] + m_[2][2]; } // Return a pointer to the data array for interface with other libraries // like opencv - VType* Data() { - return reinterpret_cast(m_); - } - const VType* Data() const { - return reinterpret_cast(m_); - } + VType *Data() { return reinterpret_cast(m_); } + const VType *Data() const { return reinterpret_cast(m_); } // Return matrix element (i,j) with 0<=i<=2 0<=j<=2 inline VType &operator()(const int i, const int j) { @@ -249,39 +240,38 @@ class Matrix3x3 { inline VType &operator[](const int i) { S2_DCHECK_GE(i, 0); S2_DCHECK_LT(i, 9); - return reinterpret_cast(m_)[i]; + return reinterpret_cast(m_)[i]; } inline VType operator[](const int i) const { S2_DCHECK_GE(i, 0); S2_DCHECK_LT(i, 9); - return reinterpret_cast(m_)[i]; + return reinterpret_cast(m_)[i]; } // Return the transposed matrix - inline Matrix3x3 Transpose(void) const { - return Matrix3x3(m_[0][0], m_[1][0], m_[2][0], - m_[0][1], m_[1][1], m_[2][1], + inline Matrix3x3 Transpose() const { + return Matrix3x3(m_[0][0], m_[1][0], m_[2][0], // + m_[0][1], m_[1][1], m_[2][1], // m_[0][2], m_[1][2], m_[2][2]); } // Return the transposed of the matrix of the cofactors // (Useful for inversion for example) - inline Matrix3x3 ComatrixTransposed(void) const { - return Matrix3x3( - m_[1][1] * m_[2][2] - m_[2][1] * m_[1][2], - m_[2][1] * m_[0][2] - m_[0][1] * m_[2][2], - m_[0][1] * m_[1][2] - m_[1][1] * m_[0][2], + inline Matrix3x3 ComatrixTransposed() const { + return Matrix3x3(m_[1][1] * m_[2][2] - m_[2][1] * m_[1][2], + m_[2][1] * m_[0][2] - m_[0][1] * m_[2][2], + m_[0][1] * m_[1][2] - m_[1][1] * m_[0][2], - m_[1][2] * m_[2][0] - m_[2][2] * m_[1][0], - m_[2][2] * m_[0][0] - m_[0][2] * m_[2][0], - m_[0][2] * m_[1][0] - m_[1][2] * m_[0][0], + m_[1][2] * m_[2][0] - m_[2][2] * m_[1][0], + m_[2][2] * m_[0][0] - m_[0][2] * m_[2][0], + m_[0][2] * m_[1][0] - m_[1][2] * m_[0][0], - m_[1][0] * m_[2][1] - m_[2][0] * m_[1][1], - m_[2][0] * m_[0][1] - m_[0][0] * m_[2][1], - m_[0][0] * m_[1][1] - m_[1][0] * m_[0][1]); + m_[1][0] * m_[2][1] - m_[2][0] * m_[1][1], + m_[2][0] * m_[0][1] - m_[0][0] * m_[2][1], + m_[0][0] * m_[1][1] - m_[1][0] * m_[0][1]); } // Matrix inversion - inline Matrix3x3 Inverse(void) const { + inline Matrix3x3 Inverse() const { VType det = Det(); S2_CHECK_NE(det, VType(0)) << " Can't inverse. Determinant = 0."; return (VType(1) / det) * ComatrixTransposed(); @@ -302,27 +292,34 @@ class Matrix3x3 { } // Create a matrix from 3 row vectors - static inline Matrix3x3 FromRows(const MVector &v1, - const MVector &v2, - const MVector &v3) { + static inline Matrix3x3 FromRows(const MVector &v1, const MVector &v2, + const MVector &v3) { Matrix3x3 temp; - temp.Set(v1[0], v1[1], v1[2], - v2[0], v2[1], v2[2], + temp.Set(v1[0], v1[1], v1[2], // + v2[0], v2[1], v2[2], // v3[0], v3[1], v3[2]); return temp; } // Create a matrix from 3 column vectors - static inline Matrix3x3 FromCols(const MVector &v1, - const MVector &v2, - const MVector &v3) { + static inline Matrix3x3 FromCols(const MVector &v1, const MVector &v2, + const MVector &v3) { Matrix3x3 temp; - temp.Set(v1[0], v2[0], v3[0], - v1[1], v2[1], v3[1], + temp.Set(v1[0], v2[0], v3[0], // + v1[1], v2[1], v3[1], // v1[2], v2[2], v3[2]); return temp; } + // Create a matrix from outer product of two vectors. + static inline Matrix3x3 FromOuter(const MVector &ma, const MVector &mb) { + Matrix3x3 m; + for (int row = 0; row < 3; ++row) { + m.SetRow(row, ma[row] * mb); + } + return m; + } + // Set the vector in row i to be v1 void SetRow(int i, const MVector &v1) { S2_DCHECK_GE(i, 0); @@ -352,7 +349,7 @@ class Matrix3x3 { } // Return the identity matrix - static inline Matrix3x3 Identity(void) { + static inline Matrix3x3 Identity() { Matrix3x3 temp; temp.Set(VType(1), VType(0), VType(0), // VType(0), VType(1), VType(0), // @@ -361,30 +358,27 @@ class Matrix3x3 { } // Return a matrix full of zeros - static inline Matrix3x3 Zero(void) { - return Matrix3x3(); - } + static inline Matrix3x3 Zero() { return Matrix3x3(); } // Return a diagonal matrix with the coefficients in v static inline Matrix3x3 Diagonal(const MVector &v) { - return Matrix3x3(v[0], VType(), VType(), - VType(), v[1], VType(), + return Matrix3x3(v[0], VType(), VType(), // + VType(), v[1], VType(), // VType(), VType(), v[2]); } // Return the matrix vvT static Matrix3x3 Sym3(const MVector &v) { - return Matrix3x3( - v[0]*v[0], v[0]*v[1], v[0]*v[2], - v[1]*v[0], v[1]*v[1], v[1]*v[2], - v[2]*v[0], v[2]*v[1], v[2]*v[2]); + return Matrix3x3(v[0] * v[0], v[0] * v[1], v[0] * v[2], // + v[1] * v[0], v[1] * v[1], v[1] * v[2], // + v[2] * v[0], v[2] * v[1], v[2] * v[2]); } // Return a matrix M such that: // for each u, M * u = v.CrossProd(u) static Matrix3x3 AntiSym3(const MVector &v) { - return Matrix3x3(VType(), -v[2], v[1], - v[2], VType(), -v[0], - -v[1], v[0], VType()); + return Matrix3x3(VType(), -v[2], v[1], // + v[2], VType(), -v[0], // + -v[1], v[0], VType()); } // Returns matrix that rotates |rot| radians around axis rot. @@ -395,10 +389,18 @@ class Matrix3x3 { Matrix3x3 Wv = Matrix3x3::AntiSym3(w); Matrix3x3 I = Matrix3x3::Identity(); Matrix3x3 A = Matrix3x3::Sym3(w); + using std::cos; + using std::sin; R = (1 - cos(theta)) * A + sin(theta) * Wv + cos(theta) * I; return R; } + // Return a matrix that reflects a point across the plane defined by normal. + static Matrix3x3 Householder(const MVector &normal) { + MVector unit = normal.Normalize(); + return Matrix3x3::Identity() - 2 * Matrix3x3::FromOuter(unit, unit); + } + // Returns v.Transpose() * (*this) * u VType MulBothSides(const MVector &v, const MVector &u) const { return ((*this) * u).DotProd(v); @@ -418,6 +420,7 @@ class Matrix3x3 { sum += m_[i][j] * m_[i][j]; } } + using std::sqrt; return sqrt(sum); } @@ -431,8 +434,8 @@ class Matrix3x3 { // characteristic polynomial // x^3 + a*x^2 + b*x + c VType a = -Trace(); - VType b = m_[0][0]*m_[1][1] + m_[1][1]*m_[2][2] + m_[2][2]*m_[0][0] - - m_[1][0]*m_[0][1] - m_[2][1]*m_[1][2] - m_[0][2]*m_[2][0]; + VType b = m_[0][0] * m_[1][1] + m_[1][1] * m_[2][2] + m_[2][2] * m_[0][0] - + m_[1][0] * m_[0][1] - m_[2][1] * m_[1][2] - m_[0][2] * m_[2][0]; VType c = -Det(); bool res = MathUtil::RealRootsForCubic(a, b, c, &r1, &r2, &r3); (*eig_val)[0] = r1; @@ -457,9 +460,9 @@ class Matrix3x3 { Matrix3x3 *eig_vec /*nullable*/) const { // Compute characteristic polynomial coefficients. double c2 = -Trace(); - double c1 = -(m_[1][0] * m_[1][0] - m_[0][0] * m_[1][1] - - m_[0][0] * m_[2][2] - m_[1][1] * m_[2][2] - + m_[2][0] * m_[2][0] + m_[2][1] * m_[2][1]); + double c1 = + -(m_[1][0] * m_[1][0] - m_[0][0] * m_[1][1] - m_[0][0] * m_[2][2] - + m_[1][1] * m_[2][2] + m_[2][0] * m_[2][0] + m_[2][1] * m_[2][1]); double c0 = -(m_[0][0] * m_[1][1] * m_[2][2] // - m_[2][0] * m_[2][0] * m_[1][1] // - m_[1][0] * m_[1][0] * m_[2][2] // @@ -470,8 +473,8 @@ class Matrix3x3 { // NOTE: Cannot reuse general cubic solver MathUtil::RealRootsForCubic() // because it doesn't guarantee finding 3 real roots, e.g. it won't always // return roots {2, 2, 0} for the cubic x^3 - 4*x^2 + 4*x + epsilon = 0. - double q = (c2*c2-3*c1)/9.0; - double r = (2*c2*c2*c2-9*c2*c1+27*c0)/54.0; + double q = (c2 * c2 - 3 * c1) / 9.0; + double r = (2 * c2 * c2 * c2 - 9 * c2 * c1 + 27 * c0) / 54.0; // Assume R^2 <= Q^3 so there are three real roots. // Avoid sqrt of negative q, which can only happen due to numerical error. if (q < 0) q = 0; @@ -482,19 +485,18 @@ class Matrix3x3 { double theta = atan2(q3_r2 <= 0 ? 0 : sqrt(q3_r2), r); double c2_3 = c2 / 3; (*eig_val)[0] = sqrt_q * cos(theta / 3.0) - c2_3; - (*eig_val)[1] = sqrt_q * cos((theta + 2.0 * M_PI)/3.0) - c2_3; - (*eig_val)[2] = sqrt_q * cos((theta - 2.0 * M_PI)/3.0) - c2_3; + (*eig_val)[1] = sqrt_q * cos((theta + 2.0 * M_PI) / 3.0) - c2_3; + (*eig_val)[2] = sqrt_q * cos((theta - 2.0 * M_PI) / 3.0) - c2_3; // Sort eigen value in decreasing order Vector3 d_order = eig_val->ComponentOrder(); - (*eig_val) = MVector((*eig_val)[d_order[2]], - (*eig_val)[d_order[1]], + (*eig_val) = MVector((*eig_val)[d_order[2]], (*eig_val)[d_order[1]], (*eig_val)[d_order[0]]); // Compute eigenvectors if (!eig_vec) return; for (int i = 0; i < 3; ++i) { - MVector r1 , r2 , r3 , e1 , e2 , e3; + MVector r1, r2, r3, e1, e2, e3; r1[0] = m_[0][0] - (*eig_val)[i]; r2[0] = m_[1][0]; r3[0] = m_[2][0]; @@ -519,9 +521,9 @@ class Matrix3x3 { // Return true is one of the elements of the matrix is NaN bool IsNaN() const { - for ( int i = 0; i < 3; ++i ) { - for ( int j = 0; j < 3; ++j ) { - if ( isnan(m_[i][j]) ) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (isnan(m_[i][j])) { return true; } } @@ -530,14 +532,10 @@ class Matrix3x3 { } friend bool operator==(const Matrix3x3 &a, const Matrix3x3 &b) { - return a.m_[0][0] == b.m_[0][0] && - a.m_[0][1] == b.m_[0][1] && - a.m_[0][2] == b.m_[0][2] && - a.m_[1][0] == b.m_[1][0] && - a.m_[1][1] == b.m_[1][1] && - a.m_[1][2] == b.m_[1][2] && - a.m_[2][0] == b.m_[2][0] && - a.m_[2][1] == b.m_[2][1] && + return a.m_[0][0] == b.m_[0][0] && a.m_[0][1] == b.m_[0][1] && + a.m_[0][2] == b.m_[0][2] && a.m_[1][0] == b.m_[1][0] && + a.m_[1][1] == b.m_[1][1] && a.m_[1][2] == b.m_[1][2] && + a.m_[2][0] == b.m_[2][0] && a.m_[2][1] == b.m_[2][1] && a.m_[2][2] == b.m_[2][2]; } @@ -545,10 +543,10 @@ class Matrix3x3 { return !(a == b); } - friend std::ostream &operator <<(std::ostream &out, const Matrix3x3 &mb) { + friend std::ostream &operator<<(std::ostream &out, const Matrix3x3 &mb) { int i, j; for (i = 0; i < 3; i++) { - if (i ==0) { + if (i == 0) { out << "["; } else { out << " "; @@ -564,10 +562,15 @@ class Matrix3x3 { } return out; } + + template + friend H AbslHashValue(H h, const Matrix3x3 &m) { + return H::combine_contiguous(std::move(h), m.Data(), 3 * 3); + } }; -typedef Matrix3x3 Matrix3x3_i; -typedef Matrix3x3 Matrix3x3_f; +typedef Matrix3x3 Matrix3x3_i; +typedef Matrix3x3 Matrix3x3_f; typedef Matrix3x3 Matrix3x3_d; diff --git a/src/s2/util/math/vector.h b/src/s2/util/math/vector.h index 6cddc9cf..8bd3338f 100644 --- a/src/s2/util/math/vector.h +++ b/src/s2/util/math/vector.h @@ -22,10 +22,13 @@ #include #include +#include #include +#include #include #include // NOLINT(readability/streams) #include +#include #include #include "s2/base/integral_types.h" @@ -33,9 +36,12 @@ #include "absl/base/macros.h" #include "absl/utility/utility.h" -template class Vector2; -template class Vector3; -template class Vector4; +template +class Vector2; +template +class Vector3; +template +class Vector4; namespace util { namespace math { @@ -50,20 +56,19 @@ class BasicVector { // FloatType is the type returned by Norm() and Angle(). These methods are // special because they return floating-point values even when VType is an // integer. - typedef typename std::conditional::value, - double, T>::type FloatType; + typedef typename std::conditional::value, double, T>::type + FloatType; using IdxSeqN = typename absl::make_index_sequence; template - static auto Reduce(F f, As*... as) - -> decltype(f(as[I]...)) { + static auto Reduce(F f, As*... as) -> decltype(f(as[I]...)) { return f(as[I]...); } template static R GenerateEach(absl::index_sequence, F f, As*... as) { - return R(Reduce(f, as...)...); + return R(Reduce(std::move(f), as...)...); } // Generate(f,a,b,...) returns an R(...), where the constructor arguments @@ -71,7 +76,8 @@ class BasicVector { // and with a,b,... all optional. template static R Generate(F f, As&&... as) { - return GenerateEach(IdxSeqN(), f, std::forward(as).Data()...); + return GenerateEach(IdxSeqN(), std::move(f), + std::forward(as).Data()...); } public: @@ -92,20 +98,20 @@ class BasicVector { } // TODO(user): Relationals should be nonmembers. - bool operator==(const D& b) const { - const T* ap = static_cast(*this).Data(); - return std::equal(ap, ap + this->Size(), b.Data()); + bool operator==(const BasicVector& b) const { + const T* ap = AsD().Data(); + return std::equal(ap, ap + this->Size(), b.AsD().Data()); } - bool operator!=(const D& b) const { return !(AsD() == b); } - bool operator<(const D& b) const { - const T* ap = static_cast(*this).Data(); - const T* bp = b.Data(); - return std::lexicographical_compare( - ap, ap + this->Size(), bp, bp + b.Size()); + bool operator!=(const BasicVector& b) const { return !(*this == b); } + bool operator<(const BasicVector& b) const { + const T* ap = AsD().Data(); + const T* bp = b.AsD().Data(); + return std::lexicographical_compare(ap, ap + this->Size(), bp, + bp + b.Size()); } - bool operator>(const D& b) const { return b < AsD(); } - bool operator<=(const D& b) const { return !(AsD() > b); } - bool operator>=(const D& b) const { return !(AsD() < b); } + bool operator>(const BasicVector& b) const { return b < *this; } + bool operator<=(const BasicVector& b) const { return !(*this > b); } + bool operator>=(const BasicVector& b) const { return !(*this < b); } D& operator+=(const D& b) { PlusEq(static_cast(*this).Data(), b.Data(), IdxSeqN{}); @@ -118,19 +124,19 @@ class BasicVector { } D& operator*=(T k) { - MulEq(static_cast(*this).Data(), k, IdxSeqN{}); + MulEq(static_cast(*this).Data(), std::move(k), IdxSeqN{}); return static_cast(*this); } D& operator/=(T k) { - DivEq(static_cast(*this).Data(), k, IdxSeqN{}); + DivEq(static_cast(*this).Data(), std::move(k), IdxSeqN{}); return static_cast(*this); } D operator+(const D& b) const { return D(AsD()) += b; } D operator-(const D& b) const { return D(AsD()) -= b; } - D operator*(T k) const { return D(AsD()) *= k; } - D operator/(T k) const { return D(AsD()) /= k; } + D operator*(T k) const { return D(AsD()) *= std::move(k); } + D operator/(T k) const { return D(AsD()) /= std::move(k); } friend D operator-(const D& a) { return Generate([](const T& x) { return -x; }, a); @@ -138,31 +144,29 @@ class BasicVector { // Convert from another vector type template - static D Cast(const VecTemplate &b) { + static D Cast(const VecTemplate& b) { return Generate([](const T2& x) { return static_cast(x); }, b); } // multiply two vectors component by component - D MulComponents(const D &b) const { + D MulComponents(const D& b) const { return Generate([](const T& x, const T& y) { return x * y; }, AsD(), b); } // divide two vectors component by component - D DivComponents(const D &b) const { + D DivComponents(const D& b) const { return Generate([](const T& x, const T& y) { return x / y; }, AsD(), b); } // Element-wise max. {max(a[0],b[0]), max(a[1],b[1]), ...} - friend D Max(const D &a, const D &b) { - return Generate([](const T& x, const T& y) { - return std::max(x, y); - }, a, b); + friend D Max(const D& a, const D& b) { + return Generate([](const T& x, const T& y) { return std::max(x, y); }, a, + b); } // Element-wise min. {min(a[0],b[0]), min(a[1],b[1]), ...} - friend D Min(const D &a, const D &b) { - return Generate([](const T& x, const T& y) { - return std::min(x, y); - }, a, b); + friend D Min(const D& a, const D& b) { + return Generate([](const T& x, const T& y) { return std::min(x, y); }, a, + b); } T DotProd(const D& b) const { @@ -186,25 +190,37 @@ class BasicVector { if (n != T(0.0)) { n = T(1.0) / n; } - return D(AsD()) *= n; + return D(AsD()) *= std::move(n); } // Compose a vector from the sqrt of each component. D Sqrt() const { - return Generate([](const T& x) { - using std::sqrt; - return sqrt(x); - }, AsD()); + return Generate( + [](const T& x) { + using std::sqrt; + return sqrt(x); + }, + AsD()); } // Take the floor of each component. D Floor() const { - return Generate([](const T& x) { return floor(x); }, AsD()); + return Generate( + [](const T& x) { + using std::floor; + return floor(x); + }, + AsD()); } // Take the ceil of each component. D Ceil() const { - return Generate([](const T& x) { return ceil(x); }, AsD()); + return Generate( + [](const T& x) { + using std::ceil; + return ceil(x); + }, + AsD()); } // Round of each component. @@ -224,8 +240,8 @@ class BasicVector { bool IsNaN() const { bool r = false; const T* ap = AsD().Data(); - for (int i = 0; i < SIZE; ++i) - r = r || isnan(ap[i]); + using std::isnan; + for (int i = 0; i < SIZE; ++i) r = r || isnan(ap[i]); return r; } @@ -236,7 +252,7 @@ class BasicVector { friend std::ostream& operator<<(std::ostream& out, const D& v) { out << "["; - const char *sep = ""; + const char* sep = ""; for (int i = 0; i < SIZE; ++i) { out << sep; Print(out, v[i]); @@ -255,6 +271,25 @@ class BasicVector { return Generate([k](const T& x) { return k / x; }, AsD()); } + template + friend H AbslHashValue(H h, const BasicVector& vec) { + return H::combine_contiguous(std::move(h), + static_cast(vec).Data(), vec.Size()); + } + + // Enable Flume default PCoder. + template + friend void FlumeEncode(E e, const BasicVector& vec) { + for (int i = 0; i < N; ++i) e(vec[i]); + } + template + friend bool FlumeDecode(D d, BasicVector& vec) { + for (int i = 0; i < N; ++i) { + if (!d(vec[i])) return false; + } + return true; + } + private: const D& AsD() const { return static_cast(*this); } D& AsD() { return static_cast(*this); } @@ -262,8 +297,12 @@ class BasicVector { // ostream << uint8 prints the ASCII character, which is not useful. // Cast to int so that numbers will be printed instead. template - static void Print(std::ostream& out, const U& v) { out << v; } - static void Print(std::ostream& out, uint8 v) { out << static_cast(v); } + static void Print(std::ostream& out, const U& v) { + out << v; + } + static void Print(std::ostream& out, uint8 v) { + out << static_cast(v); + } // Ignores its arguments so that side-effects of variadic unpacking can occur. static void Ignore(std::initializer_list) {} @@ -298,13 +337,13 @@ class BasicVector { // These templates must be defined outside of BasicVector so that the // template specialization match algorithm must deduce 'a'. See the review // of cl/119944115. -template class VT2, typename T2, std::size_t N2> +template class VT2, typename T2, + std::size_t N2> VT2 operator*(const K& k, const BasicVector& a) { return a.MulScalarInternal(k); } -template class VT2, typename T2, std::size_t N2> +template class VT2, typename T2, + std::size_t N2> VT2 operator/(const K& k, const BasicVector& a) { return a.DivScalarInternal(k); } @@ -315,8 +354,7 @@ VT2 operator/(const K& k, const BasicVector& a) { // ====================================================================== template -class Vector2 - : public util::math::internal_vector::BasicVector { +class Vector2 : public util::math::internal_vector::BasicVector { private: using Base = util::math::internal_vector::BasicVector<::Vector2, T, 2>; using VType = T; @@ -326,13 +364,13 @@ class Vector2 using FloatType = typename Base::FloatType; using Base::SIZE; - Vector2() : c_() {} - Vector2(T x, T y) { - c_[0] = x; - c_[1] = y; + constexpr Vector2() : c_() {} + constexpr Vector2(T x, T y) { + c_[0] = std::move(x); + c_[1] = std::move(y); } - explicit Vector2(const Vector3 &b) : Vector2(b.x(), b.y()) {} - explicit Vector2(const Vector4 &b) : Vector2(b.x(), b.y()) {} + explicit Vector2(const Vector3& b) : Vector2(b.x(), b.y()) {} + explicit Vector2(const Vector4& b) : Vector2(b.x(), b.y()) {} T* Data() { return c_; } const T* Data() const { return c_; } @@ -342,23 +380,25 @@ class Vector2 T x() const { return c_[0]; } T y() const { return c_[1]; } - bool aequal(const Vector2 &vb, FloatType margin) const { + // Returns true if this vector's dimensions are at most `margin` from `vb`. + bool aequal(const Vector2& vb, FloatType margin) const { using std::fabs; - return (fabs(c_[0]-vb.c_[0]) < margin) && (fabs(c_[1]-vb.c_[1]) < margin); + return (fabs(c_[0] - vb.c_[0]) <= margin) && + (fabs(c_[1] - vb.c_[1]) <= margin); } - void Set(T x, T y) { *this = Vector2(x, y); } + void Set(T x, T y) { *this = Vector2(std::move(x), std::move(y)); } // Cross product. Be aware that if T is an integer type, the high bits // of the result are silently discarded. - T CrossProd(const Vector2 &vb) const { + T CrossProd(const Vector2& vb) const { return c_[0] * vb.c_[1] - c_[1] * vb.c_[0]; } - // Returns the angle between "this" and v in radians. If either vector is - // zero-length, or nearly zero-length, the result will be zero, regardless of - // the other value. - FloatType Angle(const Vector2 &v) const { + // Returns the angle from "this" to v in the counterclockwise direction in + // radians. Result range: [-pi, pi]. If either vector is zero-length, or + // nearly zero-length, the result will be zero, regardless of the other value. + FloatType Angle(const Vector2& v) const { using std::atan2; return atan2(CrossProd(v), this->DotProd(v)); } @@ -384,8 +424,7 @@ class Vector2 }; template -class Vector3 - : public util::math::internal_vector::BasicVector { +class Vector3 : public util::math::internal_vector::BasicVector { private: using Base = util::math::internal_vector::BasicVector<::Vector3, T, 3>; using VType = T; @@ -395,33 +434,36 @@ class Vector3 using FloatType = typename Base::FloatType; using Base::SIZE; - Vector3() : c_() {} - Vector3(T x, T y, T z) { - c_[0] = x; - c_[1] = y; - c_[2] = z; + constexpr Vector3() : c_() {} + constexpr Vector3(T x, T y, T z) { + c_[0] = std::move(x); + c_[1] = std::move(y); + c_[2] = std::move(z); } - Vector3(const Vector2 &b, T z) : Vector3(b.x(), b.y(), z) {} - explicit Vector3(const Vector4 &b) : Vector3(b.x(), b.y(), b.z()) {} + Vector3(const Vector2& b, T z) : Vector3(b.x(), b.y(), z) {} + explicit Vector3(const Vector4& b) : Vector3(b.x(), b.y(), b.z()) {} T* Data() { return c_; } const T* Data() const { return c_; } - void x(const T &v) { c_[0] = v; } - void y(const T &v) { c_[1] = v; } - void z(const T &v) { c_[2] = v; } + void x(const T& v) { c_[0] = v; } + void y(const T& v) { c_[1] = v; } + void z(const T& v) { c_[2] = v; } T x() const { return c_[0]; } T y() const { return c_[1]; } T z() const { return c_[2]; } - bool aequal(const Vector3 &vb, FloatType margin) const { + // Returns true if this vector's dimensions are at most `margin` from `vb`. + bool aequal(const Vector3& vb, FloatType margin) const { using std::abs; - return (abs(c_[0] - vb.c_[0]) < margin) - && (abs(c_[1] - vb.c_[1]) < margin) - && (abs(c_[2] - vb.c_[2]) < margin); + return (abs(c_[0] - vb.c_[0]) <= margin) && + (abs(c_[1] - vb.c_[1]) <= margin) && + (abs(c_[2] - vb.c_[2]) <= margin); } - void Set(T x, T y, T z) { *this = Vector3(x, y, z); } + void Set(T x, T y, T z) { + *this = Vector3(std::move(x), std::move(y), std::move(z)); + } // Cross product. Be aware that if VType is an integer type, the high bits // of the result are silently discarded. @@ -440,17 +482,15 @@ class Vector3 return CrossProd(temp).Normalize(); } - // Returns the angle between two vectors in radians. If either vector is - // zero-length, or nearly zero-length, the result will be zero, regardless of - // the other value. - FloatType Angle(const Vector3 &va) const { + // Returns the angle between "this" and v in radians, in the range [0, pi]. If + // either vector is zero-length, or nearly zero-length, the result will be + // zero, regardless of the other value. + FloatType Angle(const Vector3& v) const { using std::atan2; - return atan2(CrossProd(va).Norm(), this->DotProd(va)); + return atan2(CrossProd(v).Norm(), this->DotProd(v)); } - Vector3 Fabs() const { - return Abs(); - } + Vector3 Fabs() const { return Abs(); } Vector3 Abs() const { static_assert( @@ -463,9 +503,9 @@ class Vector3 // return the index of the largest component (fabs) int LargestAbsComponent() const { Vector3 temp = Abs(); - return temp[0] > temp[1] ? - temp[0] > temp[2] ? 0 : 2 : - temp[1] > temp[2] ? 1 : 2; + return temp[0] > temp[1] ? temp[0] > temp[2] ? 0 : 2 + : temp[1] > temp[2] ? 1 + : 2; } // return the index of the smallest, median ,largest component of the vector @@ -483,8 +523,7 @@ class Vector3 }; template -class Vector4 - : public util::math::internal_vector::BasicVector { +class Vector4 : public util::math::internal_vector::BasicVector { private: using Base = util::math::internal_vector::BasicVector<::Vector4, T, 4>; using VType = T; @@ -494,36 +533,36 @@ class Vector4 using FloatType = typename Base::FloatType; using Base::SIZE; - Vector4() : c_() {} - Vector4(T x, T y, T z, T w) { - c_[0] = x; - c_[1] = y; - c_[2] = z; - c_[3] = w; + constexpr Vector4() : c_() {} + constexpr Vector4(T x, T y, T z, T w) { + c_[0] = std::move(x); + c_[1] = std::move(y); + c_[2] = std::move(z); + c_[3] = std::move(w); } - Vector4(const Vector2 &b, T z, T w) - : Vector4(b.x(), b.y(), z, w) {} - Vector4(const Vector2 &a, const Vector2 &b) + Vector4(const Vector2& b, T z, T w) : Vector4(b.x(), b.y(), z, w) {} + Vector4(const Vector2& a, const Vector2& b) : Vector4(a.x(), a.y(), b.x(), b.y()) {} - Vector4(const Vector3 &b, T w) - : Vector4(b.x(), b.y(), b.z(), w) {} + Vector4(const Vector3& b, T w) + : Vector4(b.x(), b.y(), b.z(), std::move(w)) {} T* Data() { return c_; } const T* Data() const { return c_; } - bool aequal(const Vector4 &vb, FloatType margin) const { + // Returns true if this vector's dimensions are at most `margin` from `vb`. + bool aequal(const Vector4& vb, FloatType margin) const { using std::fabs; - return (fabs(c_[0] - vb.c_[0]) < margin) - && (fabs(c_[1] - vb.c_[1]) < margin) - && (fabs(c_[2] - vb.c_[2]) < margin) - && (fabs(c_[3] - vb.c_[3]) < margin); + return (fabs(c_[0] - vb.c_[0]) <= margin) && + (fabs(c_[1] - vb.c_[1]) <= margin) && + (fabs(c_[2] - vb.c_[2]) <= margin) && + (fabs(c_[3] - vb.c_[3]) <= margin); } - void x(const T &v) { c_[0] = v; } - void y(const T &v) { c_[1] = v; } - void z(const T &v) { c_[2] = v; } - void w(const T &v) { c_[3] = v; } + void x(const T& v) { c_[0] = v; } + void y(const T& v) { c_[1] = v; } + void z(const T& v) { c_[2] = v; } + void w(const T& v) { c_[3] = v; } T x() const { return c_[0]; } T y() const { return c_[1]; } T z() const { return c_[2]; } @@ -547,22 +586,22 @@ class Vector4 VType c_[SIZE]; }; -typedef Vector2 Vector2_b; -typedef Vector2 Vector2_s; -typedef Vector2 Vector2_i; -typedef Vector2 Vector2_f; +typedef Vector2 Vector2_b; +typedef Vector2 Vector2_s; +typedef Vector2 Vector2_i; +typedef Vector2 Vector2_f; typedef Vector2 Vector2_d; -typedef Vector3 Vector3_b; -typedef Vector3 Vector3_s; -typedef Vector3 Vector3_i; -typedef Vector3 Vector3_f; +typedef Vector3 Vector3_b; +typedef Vector3 Vector3_s; +typedef Vector3 Vector3_i; +typedef Vector3 Vector3_f; typedef Vector3 Vector3_d; -typedef Vector4 Vector4_b; -typedef Vector4 Vector4_s; -typedef Vector4 Vector4_i; -typedef Vector4 Vector4_f; +typedef Vector4 Vector4_b; +typedef Vector4 Vector4_s; +typedef Vector4 Vector4_i; +typedef Vector4 Vector4_f; typedef Vector4 Vector4_d; diff --git a/src/s2/util/math/vector3_hash.h b/src/s2/util/math/vector3_hash.h deleted file mode 100644 index 0c212a73..00000000 --- a/src/s2/util/math/vector3_hash.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 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_UTIL_MATH_VECTOR3_HASH_H_ -#define S2_UTIL_MATH_VECTOR3_HASH_H_ - -#include -#include -#include - -#include "s2/util/hash/mix.h" -#include "s2/util/math/vector.h" - -template -struct GoodFastHash; - -template -struct GoodFastHash> { - std::size_t operator()(const Vector2& v) const { - static_assert(std::is_pod::value, "POD expected"); - // std::hash collapses +/-0. - std::hash h; - HashMix mix(h(v.x())); - mix.Mix(h(v.y())); - return mix.get(); - } -}; - -template -struct GoodFastHash> { - std::size_t operator()(const Vector3& v) const { - static_assert(std::is_pod::value, "POD expected"); - // std::hash collapses +/-0. - std::hash h; - HashMix mix(h(v.x())); - mix.Mix(h(v.y())); - mix.Mix(h(v.z())); - return mix.get(); - } -}; - -#endif // S2_UTIL_MATH_VECTOR3_HASH_H_ diff --git a/src/s2/util/units/physical-units.h b/src/s2/util/units/physical-units.h index 9d99cebf..610f6d0b 100644 --- a/src/s2/util/units/physical-units.h +++ b/src/s2/util/units/physical-units.h @@ -85,8 +85,11 @@ #define S2_UTIL_UNITS_PHYSICAL_UNITS_H_ #include +#include #include #include +#include +#include #include #include @@ -103,10 +106,10 @@ namespace units { template struct UnitConversion { - static const int SCALE_NUMERATOR = ScaleNumerator; - static const int SCALE_DENOMINATOR = ScaleDenominator; - static const int OFFSET_NUMERATOR = OffsetNumerator; - static const int OFFSET_DENOMINATOR = OffsetDenominator; + static constexpr int SCALE_NUMERATOR = ScaleNumerator; + static constexpr int SCALE_DENOMINATOR = ScaleDenominator; + static constexpr int OFFSET_NUMERATOR = OffsetNumerator; + static constexpr int OFFSET_DENOMINATOR = OffsetDenominator; }; template @@ -120,19 +123,21 @@ struct UnitConverter { constexpr static inline Float Convert(Float value) { // scaling and offset return static_cast( - (static_cast(value * - (static_cast(static_cast(ToUnit::SCALE_NUMERATOR) * - FromUnit::SCALE_DENOMINATOR) / - static_cast(static_cast(ToUnit::SCALE_DENOMINATOR) * - FromUnit::SCALE_NUMERATOR)))) - - (static_cast(static_cast(ToUnit::SCALE_NUMERATOR) * - FromUnit::SCALE_DENOMINATOR * - FromUnit::OFFSET_NUMERATOR) / - static_cast(static_cast(ToUnit::SCALE_DENOMINATOR) * - FromUnit::SCALE_NUMERATOR * - FromUnit::OFFSET_DENOMINATOR)) + - (static_cast(ToUnit::OFFSET_NUMERATOR) / - static_cast(ToUnit::OFFSET_DENOMINATOR))); + (static_cast( + value * (static_cast( + static_cast(ToUnit::SCALE_NUMERATOR) * + FromUnit::SCALE_DENOMINATOR) / + static_cast( + static_cast(ToUnit::SCALE_DENOMINATOR) * + FromUnit::SCALE_NUMERATOR)))) - + (static_cast(static_cast(ToUnit::SCALE_NUMERATOR) * + FromUnit::SCALE_DENOMINATOR * + FromUnit::OFFSET_NUMERATOR) / + static_cast(static_cast(ToUnit::SCALE_DENOMINATOR) * + FromUnit::SCALE_NUMERATOR * + FromUnit::OFFSET_DENOMINATOR)) + + (static_cast(ToUnit::OFFSET_NUMERATOR) / + static_cast(ToUnit::OFFSET_DENOMINATOR))); } }; @@ -142,7 +147,7 @@ struct UnitConverter { // default unit transformations are assumed to be linear; see // temperature-units.h for an example of how to override this default. template -struct is_linear_unit_transformation : std::true_type { }; +struct is_linear_unit_transformation : std::true_type {}; // Template class holding a single value with an associated physical // unit. The unit and conversion parameters are statically defined @@ -190,7 +195,7 @@ class PhysicalUnit { // Copy operation from other units of the same Base type. template - Type operator = (PhysicalUnit other) { + Type& operator=(PhysicalUnit other) { value_ = UnitConverter::Convert(other.value()); return *this; } @@ -206,36 +211,36 @@ class PhysicalUnit { constexpr Float value() const { return value_; } // Trivial arithematic operator wrapping. - Type operator - () const { + Type operator-() const { return Type(-value_); } - Type operator * (const Float scale) const { + Type operator*(const Float scale) const { return Type(value_ * scale); } - Type operator + (const Type other) const { + Type operator+(const Type other) const { static_assert(is_linear_unit_transformation::value, "operation not defined"); return Type(value_ + other.value()); } - Type operator - (const Type other) const { + Type operator-(const Type other) const { static_assert(is_linear_unit_transformation::value, "operation not defined"); return Type(value_ - other.value()); } - Float operator / (const Type other) const { + Float operator/(const Type other) const { return value_ / other.value(); } - Type operator *= (const Float scale) { + Type operator*=(const Float scale) { value_ *= scale; return *this; } - Type operator += (const Type other) { + Type operator+=(const Type other) { static_assert(is_linear_unit_transformation::value, "operation not defined"); value_ += other.value(); return *this; } - Type operator -= (const Type other) { + Type operator-=(const Type other) { static_assert(is_linear_unit_transformation::value, "operation not defined"); value_ -= other.value(); @@ -244,16 +249,16 @@ class PhysicalUnit { // Simple comparisons. Overloaded equality is intentionally omitted; // use equals() instead. - bool operator < (const Type other) const { + bool operator<(const Type other) const { return value_ < other.value(); } - bool operator > (const Type other) const { + bool operator>(const Type other) const { return value_ > other.value(); } - bool operator <= (const Type other) const { + bool operator<=(const Type other) const { return value_ <= other.value(); } - bool operator >= (const Type other) const { + bool operator>=(const Type other) const { return value_ >= other.value(); } @@ -306,8 +311,15 @@ std::ostream& operator<<(std::ostream& os, << Base::output_suffix << ")"; } -} // end namespace units +template +std::string ToString(PhysicalUnit value) { + std::ostringstream string_stream; + string_stream << value; + return string_stream.str(); +} + +} // end namespace units -} // end namespace util +} // end namespace util #endif // S2_UTIL_UNITS_PHYSICAL_UNITS_H_ diff --git a/src/s2/value_lexicon.h b/src/s2/value_lexicon.h index 668439aa..c8a0c0ee 100644 --- a/src/s2/value_lexicon.h +++ b/src/s2/value_lexicon.h @@ -18,8 +18,11 @@ #ifndef S2_VALUE_LEXICON_H_ #define S2_VALUE_LEXICON_H_ +#include + #include #include +#include #include #include "s2/base/integral_types.h"