From 2da94b0ed9fbb5b5981c7199485a610d0f9ffe78 Mon Sep 17 00:00:00 2001
From: Ivan Gerasimov
Date: Wed, 26 Nov 2025 16:28:37 -0800
Subject: [PATCH 1/6] feat: Add s2geometry.io website content from
manshreck/s2geometry.github.io
---
docs/.gitignore | 6 +
docs/CNAME | 1 +
docs/Gemfile | 28 +
docs/LICENSE | 204 +++
docs/README.md | 4 +
docs/_config.yml | 57 +
docs/_includes/header.html | 18 +
docs/about/index.md | 29 +
docs/about/install.md | 247 ++++
docs/about/overview.md | 317 ++++
docs/about/platforms.md | 273 ++++
docs/community/code-of-conduct.md | 87 ++
docs/community/index.md | 26 +
docs/devguide/basic_types.md | 1272 +++++++++++++++++
docs/devguide/coding_style.md | 923 ++++++++++++
docs/devguide/cpp/examples/CMakeLists.txt | 4 +
docs/devguide/cpp/examples/point_index.cc | 56 +
docs/devguide/cpp/examples/term_index.cc | 112 ++
docs/devguide/cpp/quickstart.md | 360 +++++
docs/devguide/degenerate_geometry.md | 584 ++++++++
docs/devguide/examples/coverings.md | 152 ++
docs/devguide/examples/img/corner_10.gif | Bin 0 -> 6204 bytes
docs/devguide/examples/img/corner_1000.gif | Bin 0 -> 10824 bytes
docs/devguide/examples/img/corner_20.gif | Bin 0 -> 6052 bytes
docs/devguide/examples/img/corner_200.gif | Bin 0 -> 8067 bytes
docs/devguide/examples/img/corner_50.gif | Bin 0 -> 6623 bytes
docs/devguide/examples/img/edge_4.gif | Bin 0 -> 4324 bytes
docs/devguide/examples/img/edge_50.gif | Bin 0 -> 3666 bytes
docs/devguide/examples/img/edge_500.gif | Bin 0 -> 4705 bytes
docs/devguide/examples/img/edge_6.gif | Bin 0 -> 3839 bytes
docs/devguide/examples/img/florida1.gif | Bin 0 -> 32120 bytes
docs/devguide/examples/img/florida2.gif | Bin 0 -> 32347 bytes
docs/devguide/examples/img/hawaii.gif | Bin 0 -> 10148 bytes
docs/devguide/examples/img/kirkland_1.gif | Bin 0 -> 864 bytes
docs/devguide/examples/img/kirkland_10.gif | Bin 0 -> 3003 bytes
docs/devguide/examples/img/kirkland_2.gif | Bin 0 -> 823 bytes
docs/devguide/examples/img/kirkland_20.gif | Bin 0 -> 3125 bytes
docs/devguide/examples/img/kirkland_3.gif | Bin 0 -> 655 bytes
docs/devguide/examples/img/kirkland_4.gif | Bin 0 -> 3035 bytes
docs/devguide/examples/img/kirkland_5.gif | Bin 0 -> 3010 bytes
docs/devguide/examples/img/kirkland_50.gif | Bin 0 -> 3470 bytes
docs/devguide/examples/img/kirkland_500.gif | Bin 0 -> 8567 bytes
docs/devguide/examples/img/kirkland_6.gif | Bin 0 -> 2877 bytes
docs/devguide/examples/img/polar_100.gif | Bin 0 -> 8046 bytes
docs/devguide/examples/img/polar_20.gif | Bin 0 -> 6408 bytes
docs/devguide/examples/img/polar_500.gif | Bin 0 -> 9987 bytes
docs/devguide/examples/img/polar_8.gif | Bin 0 -> 6282 bytes
docs/devguide/examples/img/washington_10.gif | Bin 0 -> 5043 bytes
docs/devguide/examples/img/washington_100.gif | Bin 0 -> 5817 bytes
docs/devguide/examples/img/washington_20.gif | Bin 0 -> 5878 bytes
docs/devguide/img/hilbert-cell-center.gif | Bin 0 -> 2204 bytes
docs/devguide/img/hilbert-figure.gif | Bin 0 -> 60445 bytes
docs/devguide/img/nesting-animation.gif | Bin 0 -> 606432 bytes
docs/devguide/img/nesting-equator-rings0.png | Bin 0 -> 41443 bytes
docs/devguide/img/nesting-equator-rings1.png | Bin 0 -> 39705 bytes
docs/devguide/img/s2cell_global.jpg | Bin 0 -> 61707 bytes
docs/devguide/img/s2curve-large.gif | Bin 0 -> 3067438 bytes
docs/devguide/img/s2curve-small.gif | Bin 0 -> 225165 bytes
docs/devguide/img/s2curve-tiny.gif | Bin 0 -> 66487 bytes
docs/devguide/img/s2hierarchy.gif | Bin 0 -> 69456 bytes
docs/devguide/img/s2shapeindex_example.png | Bin 0 -> 114478 bytes
docs/devguide/img/xyz_to_uv_to_st.gif | Bin 0 -> 56899 bytes
docs/devguide/index.md | 26 +
docs/devguide/java/benchmarks.md | 293 ++++
docs/devguide/s2cell_hierarchy.md | 1041 ++++++++++++++
docs/devguide/s2chain_interpolation_query.md | 228 +++
docs/devguide/s2closestedgequery.md | 373 +++++
docs/devguide/s2shape_nesting_query.md | 99 ++
docs/devguide/s2shapeindex.md | 685 +++++++++
docs/favicon.ico | Bin 0 -> 1150 bytes
docs/favicons/android-chrome-192x192.png | Bin 0 -> 4274 bytes
docs/favicons/apple-touch-icon.png | Bin 0 -> 4294 bytes
docs/favicons/browserconfig.xml | 9 +
docs/favicons/favicon-16x16.png | Bin 0 -> 399 bytes
docs/favicons/favicon-32x32.png | Bin 0 -> 691 bytes
docs/favicons/favicon.ico | Bin 0 -> 1150 bytes
docs/favicons/manifest.json | 12 +
docs/favicons/mstile-150x150.png | Bin 0 -> 4413 bytes
docs/favicons/safari-pinned-tab.svg | 24 +
docs/getS2.md | 22 +
docs/img/corner_10.gif | Bin 0 -> 6204 bytes
docs/img/corner_1000.gif | Bin 0 -> 10824 bytes
docs/img/corner_20.gif | Bin 0 -> 6052 bytes
docs/img/corner_200.gif | Bin 0 -> 8067 bytes
docs/img/corner_50.gif | Bin 0 -> 6623 bytes
docs/img/edge_4.gif | Bin 0 -> 4324 bytes
docs/img/edge_50.gif | Bin 0 -> 3666 bytes
docs/img/edge_500.gif | Bin 0 -> 4705 bytes
docs/img/edge_6.gif | Bin 0 -> 3839 bytes
docs/img/face0.jpg | Bin 0 -> 132297 bytes
docs/img/face0_disp.jpg | Bin 0 -> 46726 bytes
docs/img/face1.jpg | Bin 0 -> 138421 bytes
docs/img/face1_disp.jpg | Bin 0 -> 48417 bytes
docs/img/face2.jpg | Bin 0 -> 164082 bytes
docs/img/face2_disp.jpg | Bin 0 -> 52611 bytes
docs/img/face3.jpg | Bin 0 -> 105427 bytes
docs/img/face3_disp.jpg | Bin 0 -> 38934 bytes
docs/img/face4.jpg | Bin 0 -> 126992 bytes
docs/img/face4_disp.jpg | Bin 0 -> 44188 bytes
docs/img/face5.jpg | Bin 0 -> 104897 bytes
docs/img/face5_disp.jpg | Bin 0 -> 39622 bytes
docs/img/florida1.gif | Bin 0 -> 32120 bytes
docs/img/florida2.gif | Bin 0 -> 32347 bytes
docs/img/hawaii.gif | Bin 0 -> 10148 bytes
docs/img/hilbert-cell-center.gif | Bin 0 -> 2204 bytes
docs/img/hilbert-figure.gif | Bin 0 -> 60445 bytes
docs/img/kirkland_1.gif | Bin 0 -> 864 bytes
docs/img/kirkland_10.gif | Bin 0 -> 3003 bytes
docs/img/kirkland_2.gif | Bin 0 -> 823 bytes
docs/img/kirkland_20.gif | Bin 0 -> 3125 bytes
docs/img/kirkland_3.gif | Bin 0 -> 655 bytes
docs/img/kirkland_4.gif | Bin 0 -> 3035 bytes
docs/img/kirkland_5.gif | Bin 0 -> 3010 bytes
docs/img/kirkland_50.gif | Bin 0 -> 3470 bytes
docs/img/kirkland_500.gif | Bin 0 -> 8567 bytes
docs/img/kirkland_6.gif | Bin 0 -> 2877 bytes
docs/img/polar_100.gif | Bin 0 -> 8046 bytes
docs/img/polar_20.gif | Bin 0 -> 6408 bytes
docs/img/polar_500.gif | Bin 0 -> 9987 bytes
docs/img/polar_8.gif | Bin 0 -> 6282 bytes
docs/img/s2cell_global.jpg | Bin 0 -> 61707 bytes
docs/img/s2curve-large.gif | Bin 0 -> 1768761 bytes
docs/img/s2curve-medium.gif | Bin 0 -> 573717 bytes
docs/img/s2curve-small.gif | Bin 0 -> 225165 bytes
docs/img/s2curve-tiny.gif | Bin 0 -> 66487 bytes
docs/img/s2hierarchy.gif | Bin 0 -> 69456 bytes
docs/img/s2shapeindex_example.png | Bin 0 -> 114478 bytes
docs/img/washington_10.gif | Bin 0 -> 5043 bytes
docs/img/washington_100.gif | Bin 0 -> 5817 bytes
docs/img/washington_20.gif | Bin 0 -> 5878 bytes
docs/img/xyz_to_uv_to_st.gif | Bin 0 -> 56899 bytes
docs/index.md | 72 +
docs/resources/earthcube.md | 51 +
docs/resources/img/face0.jpg | Bin 0 -> 132297 bytes
docs/resources/img/face0_disp.jpg | Bin 0 -> 46726 bytes
docs/resources/img/face1.jpg | Bin 0 -> 138421 bytes
docs/resources/img/face1_disp.jpg | Bin 0 -> 48417 bytes
docs/resources/img/face2.jpg | Bin 0 -> 164082 bytes
docs/resources/img/face2_disp.jpg | Bin 0 -> 52611 bytes
docs/resources/img/face3.jpg | Bin 0 -> 105427 bytes
docs/resources/img/face3_disp.jpg | Bin 0 -> 38934 bytes
docs/resources/img/face4.jpg | Bin 0 -> 126992 bytes
docs/resources/img/face4_disp.jpg | Bin 0 -> 44188 bytes
docs/resources/img/face5.jpg | Bin 0 -> 104897 bytes
docs/resources/img/face5_disp.jpg | Bin 0 -> 39622 bytes
docs/resources/img/s2cell_global.jpg | Bin 0 -> 61707 bytes
docs/resources/index.md | 6 +
docs/resources/s2cell_statistics.md | 44 +
148 files changed, 7745 insertions(+)
create mode 100644 docs/.gitignore
create mode 100644 docs/CNAME
create mode 100644 docs/Gemfile
create mode 100644 docs/LICENSE
create mode 100644 docs/README.md
create mode 100644 docs/_config.yml
create mode 100644 docs/_includes/header.html
create mode 100644 docs/about/index.md
create mode 100644 docs/about/install.md
create mode 100644 docs/about/overview.md
create mode 100644 docs/about/platforms.md
create mode 100644 docs/community/code-of-conduct.md
create mode 100644 docs/community/index.md
create mode 100644 docs/devguide/basic_types.md
create mode 100644 docs/devguide/coding_style.md
create mode 100644 docs/devguide/cpp/examples/CMakeLists.txt
create mode 100644 docs/devguide/cpp/examples/point_index.cc
create mode 100644 docs/devguide/cpp/examples/term_index.cc
create mode 100644 docs/devguide/cpp/quickstart.md
create mode 100644 docs/devguide/degenerate_geometry.md
create mode 100644 docs/devguide/examples/coverings.md
create mode 100644 docs/devguide/examples/img/corner_10.gif
create mode 100644 docs/devguide/examples/img/corner_1000.gif
create mode 100644 docs/devguide/examples/img/corner_20.gif
create mode 100644 docs/devguide/examples/img/corner_200.gif
create mode 100644 docs/devguide/examples/img/corner_50.gif
create mode 100644 docs/devguide/examples/img/edge_4.gif
create mode 100644 docs/devguide/examples/img/edge_50.gif
create mode 100644 docs/devguide/examples/img/edge_500.gif
create mode 100644 docs/devguide/examples/img/edge_6.gif
create mode 100644 docs/devguide/examples/img/florida1.gif
create mode 100644 docs/devguide/examples/img/florida2.gif
create mode 100644 docs/devguide/examples/img/hawaii.gif
create mode 100644 docs/devguide/examples/img/kirkland_1.gif
create mode 100644 docs/devguide/examples/img/kirkland_10.gif
create mode 100644 docs/devguide/examples/img/kirkland_2.gif
create mode 100644 docs/devguide/examples/img/kirkland_20.gif
create mode 100644 docs/devguide/examples/img/kirkland_3.gif
create mode 100644 docs/devguide/examples/img/kirkland_4.gif
create mode 100644 docs/devguide/examples/img/kirkland_5.gif
create mode 100644 docs/devguide/examples/img/kirkland_50.gif
create mode 100644 docs/devguide/examples/img/kirkland_500.gif
create mode 100644 docs/devguide/examples/img/kirkland_6.gif
create mode 100644 docs/devguide/examples/img/polar_100.gif
create mode 100644 docs/devguide/examples/img/polar_20.gif
create mode 100644 docs/devguide/examples/img/polar_500.gif
create mode 100644 docs/devguide/examples/img/polar_8.gif
create mode 100644 docs/devguide/examples/img/washington_10.gif
create mode 100644 docs/devguide/examples/img/washington_100.gif
create mode 100644 docs/devguide/examples/img/washington_20.gif
create mode 100644 docs/devguide/img/hilbert-cell-center.gif
create mode 100644 docs/devguide/img/hilbert-figure.gif
create mode 100644 docs/devguide/img/nesting-animation.gif
create mode 100644 docs/devguide/img/nesting-equator-rings0.png
create mode 100644 docs/devguide/img/nesting-equator-rings1.png
create mode 100644 docs/devguide/img/s2cell_global.jpg
create mode 100644 docs/devguide/img/s2curve-large.gif
create mode 100644 docs/devguide/img/s2curve-small.gif
create mode 100644 docs/devguide/img/s2curve-tiny.gif
create mode 100644 docs/devguide/img/s2hierarchy.gif
create mode 100644 docs/devguide/img/s2shapeindex_example.png
create mode 100644 docs/devguide/img/xyz_to_uv_to_st.gif
create mode 100644 docs/devguide/index.md
create mode 100644 docs/devguide/java/benchmarks.md
create mode 100644 docs/devguide/s2cell_hierarchy.md
create mode 100644 docs/devguide/s2chain_interpolation_query.md
create mode 100644 docs/devguide/s2closestedgequery.md
create mode 100644 docs/devguide/s2shape_nesting_query.md
create mode 100644 docs/devguide/s2shapeindex.md
create mode 100644 docs/favicon.ico
create mode 100755 docs/favicons/android-chrome-192x192.png
create mode 100755 docs/favicons/apple-touch-icon.png
create mode 100755 docs/favicons/browserconfig.xml
create mode 100755 docs/favicons/favicon-16x16.png
create mode 100755 docs/favicons/favicon-32x32.png
create mode 100644 docs/favicons/favicon.ico
create mode 100755 docs/favicons/manifest.json
create mode 100755 docs/favicons/mstile-150x150.png
create mode 100755 docs/favicons/safari-pinned-tab.svg
create mode 100644 docs/getS2.md
create mode 100644 docs/img/corner_10.gif
create mode 100644 docs/img/corner_1000.gif
create mode 100644 docs/img/corner_20.gif
create mode 100644 docs/img/corner_200.gif
create mode 100644 docs/img/corner_50.gif
create mode 100644 docs/img/edge_4.gif
create mode 100644 docs/img/edge_50.gif
create mode 100644 docs/img/edge_500.gif
create mode 100644 docs/img/edge_6.gif
create mode 100644 docs/img/face0.jpg
create mode 100644 docs/img/face0_disp.jpg
create mode 100644 docs/img/face1.jpg
create mode 100644 docs/img/face1_disp.jpg
create mode 100644 docs/img/face2.jpg
create mode 100644 docs/img/face2_disp.jpg
create mode 100644 docs/img/face3.jpg
create mode 100644 docs/img/face3_disp.jpg
create mode 100644 docs/img/face4.jpg
create mode 100644 docs/img/face4_disp.jpg
create mode 100644 docs/img/face5.jpg
create mode 100644 docs/img/face5_disp.jpg
create mode 100644 docs/img/florida1.gif
create mode 100644 docs/img/florida2.gif
create mode 100644 docs/img/hawaii.gif
create mode 100644 docs/img/hilbert-cell-center.gif
create mode 100644 docs/img/hilbert-figure.gif
create mode 100644 docs/img/kirkland_1.gif
create mode 100644 docs/img/kirkland_10.gif
create mode 100644 docs/img/kirkland_2.gif
create mode 100644 docs/img/kirkland_20.gif
create mode 100644 docs/img/kirkland_3.gif
create mode 100644 docs/img/kirkland_4.gif
create mode 100644 docs/img/kirkland_5.gif
create mode 100644 docs/img/kirkland_50.gif
create mode 100644 docs/img/kirkland_500.gif
create mode 100644 docs/img/kirkland_6.gif
create mode 100644 docs/img/polar_100.gif
create mode 100644 docs/img/polar_20.gif
create mode 100644 docs/img/polar_500.gif
create mode 100644 docs/img/polar_8.gif
create mode 100644 docs/img/s2cell_global.jpg
create mode 100644 docs/img/s2curve-large.gif
create mode 100644 docs/img/s2curve-medium.gif
create mode 100644 docs/img/s2curve-small.gif
create mode 100644 docs/img/s2curve-tiny.gif
create mode 100644 docs/img/s2hierarchy.gif
create mode 100644 docs/img/s2shapeindex_example.png
create mode 100644 docs/img/washington_10.gif
create mode 100644 docs/img/washington_100.gif
create mode 100644 docs/img/washington_20.gif
create mode 100644 docs/img/xyz_to_uv_to_st.gif
create mode 100644 docs/index.md
create mode 100644 docs/resources/earthcube.md
create mode 100644 docs/resources/img/face0.jpg
create mode 100644 docs/resources/img/face0_disp.jpg
create mode 100644 docs/resources/img/face1.jpg
create mode 100644 docs/resources/img/face1_disp.jpg
create mode 100644 docs/resources/img/face2.jpg
create mode 100644 docs/resources/img/face2_disp.jpg
create mode 100644 docs/resources/img/face3.jpg
create mode 100644 docs/resources/img/face3_disp.jpg
create mode 100644 docs/resources/img/face4.jpg
create mode 100644 docs/resources/img/face4_disp.jpg
create mode 100644 docs/resources/img/face5.jpg
create mode 100644 docs/resources/img/face5_disp.jpg
create mode 100644 docs/resources/img/s2cell_global.jpg
create mode 100644 docs/resources/index.md
create mode 100644 docs/resources/s2cell_statistics.md
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..b439b05c
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,6 @@
+_site
+.sass-cache
+.jekyll-metadata
+.DS_Store
+Gemfile.lock
+*~
diff --git a/docs/CNAME b/docs/CNAME
new file mode 100644
index 00000000..34fdd53e
--- /dev/null
+++ b/docs/CNAME
@@ -0,0 +1 @@
+s2geometry.io
\ No newline at end of file
diff --git a/docs/Gemfile b/docs/Gemfile
new file mode 100644
index 00000000..021c9a6f
--- /dev/null
+++ b/docs/Gemfile
@@ -0,0 +1,28 @@
+source "https://rubygems.org"
+ruby RUBY_VERSION
+
+# Hello! This is where you manage which Jekyll version is used to run.
+# When you want to use a different version, change it below, save the
+# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
+#
+# bundle exec jekyll serve
+#
+# This will help ensure the proper Jekyll version is running.
+# Happy Jekylling!
+# gem "jekyll", "3.4.3"
+
+# This is the default theme for new Jekyll sites. You may change this to anything you like.
+gem "minima"
+
+# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
+# uncomment the line below. To upgrade, run `bundle update github-pages`.
+gem "github-pages", group: :jekyll_plugins
+
+# If you have any plugins, put them here!
+group :jekyll_plugins do
+ gem "jekyll-feed", "~> 0.6"
+end
+
+# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
+gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
+
diff --git a/docs/LICENSE b/docs/LICENSE
new file mode 100644
index 00000000..b014ed5b
--- /dev/null
+++ b/docs/LICENSE
@@ -0,0 +1,204 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
+
+© 2017 GitHub, Inc.
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..5d00132b
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,4 @@
+# s2geometry.github.io
+
+Website for S2 Geometry project
+
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 00000000..e8787ffe
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,57 @@
+# Welcome to Jekyll!
+#
+# This config file is meant for settings that affect your whole blog, values
+# which you are expected to set up once and rarely edit after that. If you find
+# yourself editing this file very often, consider using Jekyll's data files
+# feature for the data you need to update frequently.
+#
+# For technical reasons, this file is *NOT* reloaded automatically when you use
+# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
+
+# Site settings
+# These are used to personalize your new site. If you look in the HTML files,
+# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
+# You can create any custom variable you would like, and they will be accessible
+# in the templates via {{ site.myvariable }}.
+
+# Not currently used
+title: S2Geometry
+email: s2geometry-io@googlegroups.com
+description: The s2geometry.io website
+
+# The base hostname & protocol for your site, e.g. http://example.com
+# This site.url path is required by the RSS feed.xml
+url: "http://s2geometry.io"
+
+# the subpath of your site. The main Abseil website is not on a subpath
+baseurl: ""
+
+# host: 0.0.0.0 is required for local development.
+host: 0.0.0.0
+
+# Build settings
+markdown: kramdown
+theme: minima
+
+# Minima Navigation Config
+header_pages:
+ - getS2.md
+ - about/index.md
+ - devguide/index.md
+ - community/index.md
+ - resources/index.md
+
+# RSS / Blog configuration settings
+gems:
+ - jekyll-feed
+timezone: "America/New_York"
+
+# Set these false for production, true for staging server
+future: false
+unpublished: false
+show_drafts: false
+
+# Exclusions
+exclude:
+ - Gemfile
+ - Gemfile.lock
diff --git a/docs/_includes/header.html b/docs/_includes/header.html
new file mode 100644
index 00000000..72e2d9d8
--- /dev/null
+++ b/docs/_includes/header.html
@@ -0,0 +1,18 @@
+
diff --git a/docs/about/index.md b/docs/about/index.md
new file mode 100644
index 00000000..e7d83fa8
--- /dev/null
+++ b/docs/about/index.md
@@ -0,0 +1,29 @@
+---
+title: About S2
+---
+
+This guide provides background information for starting work with
+the S2 Geometry Library.
+
+* The S2 [Platforms Guide](platforms) denotes the prerequisites
+ for installation of S2 on your system. This guide also
+ provides build instructions for S2.
+* The S2 [Overview](overview) provides a conceptual introduction
+ to S2, and should be read before diving into the developer
+ guide.
+
+S2 consists of source code repositories for C++ and Java. Upon
+completing the overview, you will probably want to run through
+the Quickstart:
+
+* [C++ Quickstart](/devguide/cpp/quickstart)
+
+(A Java Quickstart is envisioned soon.)
+
+Ready for More?
+
+* Read about [S2 Cells](/devguide/s2cell_hierarchy)
+* Learn about the [Basic Types](/devguide/basic_types)
+* Understand the main [S2ShapeIndex](/devguide/s2shapeindex)
+ Class
+* Discover [Finding Nearby Edges](/devguide/s2closestedgequery)
diff --git a/docs/about/install.md b/docs/about/install.md
new file mode 100644
index 00000000..3185b99a
--- /dev/null
+++ b/docs/about/install.md
@@ -0,0 +1,247 @@
+---
+title: S2 Installation
+---
+
+This document lists the platform requirements and installation
+instructions for working with the S2 Geometry Library in both
+C++ and Python. (The S2 Python interfaces uses SWIG to interact
+with the C++ code.)
+
+## Requirements
+
+Running the S2 code requires the following:
+
+* A MacOSX or Linux platform. (Windows is not supported at this time.)
+* POSIX support (for `getrusage()`).
+* A compatible C++ compiler *supporting at least C++11*, such as
+ [g++](https://gcc.gnu.org/){:target="_blank"} >= 4.7.
+* Installation of the following libraries:
+ * [OpenSSL](https://github.com/openssl/openssl){:target="_blank"} (for its
+ bignum library)
+ * [gflags command line flags](https://github.com/gflags/gflags)
+ {:target="_blank"} (optional, disabled by default)
+ * [glog logging module](https://github.com/google/glog) {:target="_blank"}
+ (optional, disabled by default)
+ * [googletest testing framework](https://github.com/google/googletest)
+ {:target="_blank"}
+ * [SWIG](https://github.com/swig/swig) (optional, for Python interface)
+* [Git](https://git-scm.com/) for interacting with the S2 source code
+ repository, which is contained on [GitHub](http://github.com). To install
+ Git, consult the [Set Up Git](https://help.github.com/articles/set-up-git/)
+ guide on GitHub.
+
+Installation instructions for the above components are [noted below](#Install).
+
+
+Note: this installation guide uses CMake as the official build system for
+S2, which is supported on most major platforms and compilers. The S2
+source code assumes you are using CMake and contains
+CMakeList.txt files for that purpose.
+
+
+Although you are free to use your own build system, most of the documentation
+within this guide will assume you are using
+[CMake](https://cmake.org/){:target="_blank"}.
+
+## Installation Instructions {#Install}
+
+Note: thorough testing has only been done on Ubuntu 14.04.3
+and MacOSX 10.12.
+
+### Setting Up Your Development Environment (Linux)
+
+Note: we recommend you use a Linux package manager such as
+`apt-get`. Alternatively, you may build the dependent
+libraries from source.
+
+1\. Install the additional libraries S2 code requires:
+
+
+$ sudo apt-get install libgflags-dev libgoogle-glog-dev libgtest-dev libssl-dev
+Reading package lists... Done
+Building dependency tree
+Reading state information... Done
+...
+After this operation, 3,090 kB of additional disk space will be used.
+Do you want to continue? [Y/n] Y
+...
+Setting up libgtest-dev (1.6.0-1ubuntu6) ...
+Processing triggers for libc-bin (2.19-0ubuntu6.13) ...
+$
+
+
+2\. Install CMake
+
+
+$ sudo apt-get install cmake
+Reading package lists... Done
+Building dependency tree
+Reading state information... Done
+...
+After this operation, 16.6 MB of additional disk space will be used.
+Do you want to continue? [Y/n] Y
+...
+Setting up cmake (2.8.12.2-0ubuntu3) ...
+$
+
+
+### Setting Up Your Development Environment (MacOSX)
+
+Note: we recommend you use a MacOSX package manager such as
+[MacPorts](https://macports.org) or [Homebrew](https://brew.sh).
+Alternatively, you may build the dependent libraries from source.
+
+1\. Install the additional libraries S2 code requires:
+
+
+# MacPorts
+$ sudo port install gflags google-glog openssl
+
+# Homebrew
+$ sudo brew install gflags glog openssl
+
+
+2\. Download Googletest
+[release 1.8.0](https://github.com/google/googletest/releases/tag/release-1.8.0){:target="_blank"}
+and unpack it in a directory of your choosing. (Take note of this
+directory as you will need to point CMake to it.)
+
+3\. Install CMake
+
+
+# Note: XCode requires command-line tools, which can be installed with:
+$ xcode-select --install
+
+# MacPorts
+$ sudo port install cmake
+
+# Homebrew
+$ sudo brew install cmake
+
+
+### Getting the S2 Code
+
+The [Reference implementation of S2](https://github.com/google/s2geometry)
+is written in C++. Once you have CMake and Git installed, you can obtain
+the S2 code from its repository on GitHub:
+
+
+# Change to the directory where you want to create the code repository
+$ cd ~
+$ mkdir Source; cd Source
+
+
+Clone the S2 code into your development directory:
+
+
+$ git clone https://github.com/google/s2geometry.git
+Cloning into 's2geometry'...
+remote: Counting objects: 5672, done.
+remote: Compressing objects: 100% (52/52), done.
+remote: Total 5672 (delta 21), reused 36 (delta 17), pack-reused 5601
+Receiving objects: 100% (5672/5672), 7.15 MiB | 27.21 MiB/s, done.
+Resolving deltas: 100% (4503/4503), done.
+$
+
+
+Git will create the repository within a directory named `s2geometry`.
+Navigate into this directory.
+
+
+Alternatively, you can download an archive of the S2 library as a
+ZIP file
+and unzip it within your development directory.
+
+
+$ unzip path_to_ZIP_file/s2geometry-master.zip
+
+
+
+### Compiling S2
+
+Once you have installed S2's requirements and the S2 library, you
+are ready to build S2.
+
+1\. Configure the make files using CMake:
+
+
+$ cd s2geometry # or geometry-master if you installed from the ZIP file
+$ mkdir build
+$ cd build
+# See Notes below.
+$ cmake -DWITH_GFLAGS=ON -WITH_GTEST=ON \
+-DGTEST_ROOT=googletest_root_dir \
+-DOPENSSL_INCLUDE_DIR=openssl_include_dir ..
+-- Found OpenSSL: /usr/lib/libcrypto.dylib (found version "1.0.2m")
+-- Looking for pthread.h
+-- Looking for pthread.h - found
+-- Looking for pthread_create
+-- Looking for pthread_create - found
+-- Found Threads: TRUE
+-- Could NOT find SWIG (missing: SWIG_EXECUTABLE SWIG_DIR)
+-- Found PythonInterp: /Library/Frameworks/Python.framework/Versions/2.7/bin/python (found version "2.7.11")
+-- Found PythonLibs: /Library/Frameworks/Python.framework/Versions/2.7/lib/libpython2.7.dylib (found version "2.7.11")
+GTEST_ROOT: /Users/shreck/SOURCE/googletest
+-- Configuring done
+-- Generating done
+-- Build files have been written to: /Users/shreck/SOURCE/s2geometry-master/build
+$
+
+
+Notes:
+
+* `-DWITH_GFLAGS` and `-DWITH_GTEST` may be omitted to avoid depending
+ on those packages.
+* `-DGTEST_ROOT` and `-DOPENSSL_INCLUDE_DIR` must be absolute paths.
+* `-DGTEST_ROOT` is usually `/usr/src/gtest` on Linux systems; on
+ MacOSX use the directory where you installed GoogleTest.
+* MacOSX/Homebrew users may need to set `-DOPENSSL_INCLUDE_DIR` to
+ enable CMake to find your openssl `include` directory.
+* You can omit the `DGTEST_ROOT` flag to skip tests.
+
+2\. Make the S2 binary (and associated tests if `GTEST_ROOT` was
+ specified in CMake:
+
+
+$ make
+Scanning dependencies of target gtest
+...
+Scanning dependencies of target s2
+...
+[ 35%] Built target s2
+...
+[100%] Built target point_index
+$
+
+
+3\. Run the S2 tests (if `GTEST_ROOT` was specified in CMake):
+
+
+$ make test
+Running tests...
+Test project /.../s2geometry-master/build
+ Start 1: id_set_lexicon_test
+...
+83/83 Test #83: value_lexicon_test ............................. Passed 0.01 sec
+100% tests passed, 0 tests failed out of 83
+Total Test time (real) = 78.67 sec
+$
+
+
+
+4\. Install the built S2 library:
+
+
+$ make install
+[ 1%] Built target gtest
+...
+[100%] Built target point_index
+Install the project...
+-- Install configuration: ""
+...
+$
+
+
+Congratulations! You've successfully installed S2, built the library, and run
+all of the tests! You're now ready to move on to the
+[C++ Quickstart](/devguide/cpp/quickstart).
diff --git a/docs/about/overview.md b/docs/about/overview.md
new file mode 100644
index 00000000..b9368ccc
--- /dev/null
+++ b/docs/about/overview.md
@@ -0,0 +1,317 @@
+---
+title: Overview
+---
+
+S2 is a library for spherical geometry that aims to have the same
+robustness, flexibility, and performance as the very best planar
+geometry libraries.
+
+## Spherical Geometry
+
+Let's break down the elements of this goal. First, why spherical
+geometry? (By the way, the name "S2" is derived from the mathematical
+notation for the unit sphere, *S²*.)
+
+Traditional cartography is based on *map projections*, which are simply
+functions that map points on the Earth's surface to points on a planar
+map. Map projections create distortions due to the fact that the shape
+of the Earth is not very close to the shape of a plane. For example,
+the well-known Mercator projection is discontinuous along the 180 degree
+meridian, has large scale distortions at high latitudes, and cannot
+represent the north and south poles at all. Other projections make
+different compromises, but no planar projection does a good job of
+representing the entire surface of the Earth.
+
+S2 approaches this problem by working exclusively with *spherical
+projections*. As the name implies, spherical projections map points on
+the Earth's surface to a perfect mathematical sphere. Such mappings
+still create some distortion, of course, because the Earth is not quite
+spherical--but as it turns out, the Earth is much closer to being a
+sphere than a plane. With spherical projections, it is possible to
+approximate the entire Earth's surface with a maximum distortion of
+0.56%. Perhaps more importantly, spherical projections preserve the
+correct topology of the Earth -- there are no singularities or
+discontinuities to deal with.
+
+Why not project onto an ellipsoid? (The Earth isn't quite ellipsoidal
+either, but it is even closer to being an ellipsoid than a sphere.)
+The answer relates to the other goals stated above, namely performance
+and robustness. Ellipsoidal operations are still orders of magnitude
+slower than the corresponding operations on a sphere. Furthermore,
+robust geometric algorithms require the implementation of exact
+geometric predicates that are not subject to numerical errors. While
+this is fairly straightforward for planar geometry, and somewhat harder
+for spherical geometry, it is not known how to implement all of the
+necessary predicates for ellipsoidal geometry.
+
+## Robustness
+
+Which brings up the question, what do we mean by "robust"?
+
+In the S2 library, the core operations are designed to be 100% robust.
+This means that each operation makes strict mathematical guarantees
+about its output, and is implemented in such a way that it meets those
+guarantees for all possible valid inputs. For example, if you compute
+the intersection of two polygons, not only is the output guaranteed to
+be topologically correct (up to the creation of degeneracies), but it
+is also guaranteed that the boundary of the output stays within a
+user-specified tolerance of true, mathematically exact result.
+
+Robustness is very important when building higher-level algorithms,
+since unexpected results from low-level operations can be very
+difficult to handle. S2 achieves this goal using a combination of
+techniques from computational geometry, including *conservative error
+bounds*, *exact geometric predicates*, and *snap rounding*.
+
+S2 attempts to be precise both in terms of mathematical definitions
+(e.g., whether regions include their boundaries, and how degeneracies
+are handled) and numerical accuracy (e.g., minimizing cancellation
+error).
+
+## Flexibility
+
+S2 is organized as a toolkit that gives clients as much control as
+possible. For example, S2 makes it easy to implement your own geometry
+subtypes that store data in a format of your choice (the `S2Shape`
+interface). Similarly, most operations are designed to give clients
+fine control over the semantics, e.g. whether polygon boundaries are
+considered to be open or closed (or semi-open). S2 has APIs at several
+different levels to accommodate various uses of the library.
+
+## Performance
+
+S2 is designed to have good performance on large geographic datasets.
+Most operations are accelerated using an in-memory edge index data
+structure (`S2ShapeIndex`). For example if you have a million
+polygons, finding the polygon(s) that contain a given point typically
+takes a few hundred nanoseconds. Similarly it is fast to find objects
+that are near each other, such as finding all the places of business
+near a given road, or all the roads near a given location.
+
+Many operations on indexed data also have output-sensitive running
+times. For example, if you intersect the Pacific Ocean with a small
+rectangle near Seattle, the running time essentially depends on the
+complexity of the Pacific Ocean near Seattle, not the complexity of the
+Pacific Ocean overall.
+
+## Scope
+
+The S2 library provides the following:
+
+* Representations of angles, intervals, latitude-longitude points,
+ unit vectors, and so on, and various operations on these types.
+
+* Geometric shapes over the unit sphere, such as spherical caps
+ ("discs"), latitude-longitude rectangles, polylines, and polygons.
+
+* Robust constructive operations (e.g., union) and boolean predicates
+ (e.g., containment) for arbitrary collections of points, polylines,
+ and polygons.
+
+* Fast in-memory indexing of collections of points, polylines, and
+ polygons.
+
+* Algorithms for measuring distances and finding nearby objects.
+
+* Robust algorithms for snapping and simplifying geometry (with
+ accuracy and topology guarantees).
+
+* A collection of efficient yet exact mathematical predicates for
+ testing relationships among geometric objects.
+
+* Support for spatial indexing, including the ability to approximate regions
+ as collections of discrete "S2 cells". This feature makes it easy to
+ build large distributed spatial indexes.
+
+* Extensive testing on Google's vast collection of geographic data.
+
+* Flexible Apache 2.0 license.
+
+On the other hand, the following are outside the scope of S2:
+
+* Planar geometry. (There are many fine existing planar geometry
+ libraries to choose from.)
+
+* Conversions to/from common GIS formats. (To read such formats, use
+ an external library such as
+ [OGR](http://gdal.org/1.11/ogr/){:target="_blank"}.)
+
+## Language Support
+
+The [reference implementation of the S2
+library](https://github.com/google/s2geometry) is written in C++. Versions in
+other languages are ported from the C++ code, and may not have the same
+robustness, performance, or features as the C++ version. As of November 2017,
+the "official" ports are as follows:
+
+* [S2 library in Go](https://github.com/golang/geo). This project is being
+ actively developed and aims to have full equivalence with the C++ version.
+
+* [S2 library in Java](https://github.com/google/s2-geometry-library-java).
+ Be aware that this port is based on the 2011 release of the S2 library, and
+ does not have the same robustness, performance, or features as the current
+ C++ implementation.
+
+* [S2 library in Python](https://github.com/google/s2geometry/tree/master/src/python).
+ A subset of the library has been ported to Python using SWIG, and is
+ included in the `python` directory of the C++ repository. The supported
+ feature set includes polylines, polygons, discs, rectangles, and `S2Cell`
+ coverings.
+
+## Layered Design
+
+The S2 library is structured as a toolkit with various layers. At the
+lowest level, it provides a set of operations on points, edges, and simple
+shapes such as rectangles and discs. For example, this includes classes
+such as `S2Point`, `S2CellId`, `S1Angle`, `S1Interval`, `S2LatLng`,
+`S2Region`. It also includes the exact predicates defined in
+`s2predicates.h` and `s2edge_crossings.h`, utility functions such as those
+in `s2edge_distances.h`, and classes related to the `S2Cell` hierarchy (a
+hierarchical decomposition of the sphere) such as `S2CellId`, `S2Region`,
+and `S2RegionCoverer`.
+
+The next layer provides flexible support for polygonal geometry, including
+point sets and polylines. It is built around the following core classes:
+
+* `S2Builder`: a robust tool for constructing, and simplifying
+ geometry, built on the framework of snap rounding.
+
+* `S2ShapeIndex`: an indexed collection of points, polylines, and/or
+ polygons, possibly overlapping.
+
+* `S2BooleanOperation`: evaluates boolean operations and predicates for
+ arbitrary collections of polygonal geometry.
+
+* `S2ClosestEdgeQuery`: measures distances and finds closest edges for
+ arbitrary collections of polygonal geometry.
+
+These are only the most important classes, but what they have in common
+is that (1) they work with arbitrary collections of points, polylines,
+and polygons; (2) they allow clients to control the underlying geometry
+representation (via the `S2Shape` interface), and (3) they give clients
+fine control over the semantics of operations (e.g., how to handle
+degeneracies, whether polygons are open or closed, whether and how the
+output should be snapped, etc).
+
+The final layer consists of convenience classes, such as `S2Polygon`,
+`S2Loop`, and `S2Polyline`. These types have wide interfaces and
+convenience methods that implement many of the operations above (e.g.,
+intersection, distances), however they don't offer the same flexibility
+in terms of data representation and control over semantics (e.g.,
+polygon boundaries are always semi-open).
+
+For example, you can measure the distance from a point to a polygon as
+follows:
+
+ S2Point p = ...;
+ S2Polygon a = ...;
+ S1Angle dist = a.GetDistance(p);
+
+whereas with the `S2ShapeIndex` layer you would write
+
+ S2Point p = ...;
+ S2Polygon a = ...;
+ S2ShapeIndex index;
+ index.Add(absl::make_unique(&a));
+ S2ClosestEdgeQuery query(&index);
+ S1ChordAngle dist = query.GetDistance(p);
+
+The first case is simpler, but the second case supports many more
+options (e.g. measuring the distance to a collection of geometry, or
+finding the objects within a certain distance).
+
+## Design Choices
+
+This section summarizes some of the major design choices made by the library.
+
+### Spherical Geodesic Edges
+
+In S2, all edges are "spherical geodesics", i.e. shortest paths on the
+sphere. For example, the edge between two points 100 meters apart on
+opposite side of the North pole goes directly through the North pole.
+In contrast, the same edge in the standard "plate carrée"
+projection (i.e., raw latitude/longitude coordinates) would follow a
+line of constant latitude, which in this case happens to be a
+semicircular path that always stays exactly 50 meters away from the
+North pole. (Lines of latitude except for the equator are never
+shortest paths.)
+
+With geodesic edges there are no special cases near the poles or the
+180 degree meridian; for example, if two points 10km apart are
+separated by the 180 degree meridian, the edge between them simply
+crosses the meridian rather than going all the way around the other
+side of the Earth.
+
+### Orientation Matters
+
+In S2, polygon loops contain the region on their left. This differs
+from planar libraries that might define loops as being "clockwise" or
+"counter-clockwise", or that accept loops of either orientation. None
+of these concepts really applies to polygons drawn on a sphere; for
+example, consider a loop that goes around the equator. Is it clockwise
+or counter-clockwise? (For those familiar with the term, the concept
+of "winding number" does not exist on the sphere.)
+
+For these reasons, S2 follows the "interior is on the left" rule. (This
+is the same as the counter-clockwise rule for small loops, but differs
+for very large loops that enclose more than half of the sphere.)
+
+### Unit Vectors vs. Latitude/Longitude Pairs
+
+In S2, points are represented internally as unit-length vectors (points
+on the surface of a three-dimensional unit sphere) as opposed to
+traditional (latitude, longitude) pairs. This is for two reasons:
+
+* Unit vectors are much more efficient when working with geodesic
+ edges. Using (latitude, longitude) pairs would require constantly
+ evaluating trigonometric functions (sin, cos, etc), which is slow
+ even on modern CPU architectures.
+
+* Unit vectors allow exact geometric predicates to be evaluated
+ efficiently. To do this with (latitude, longitude) pairs, you
+ would essentially need to convert them to the unit vector
+ representation first.
+
+For precision and efficiency, coordinates are representated as
+double-precision numbers. Robustness is achieved through the use of
+the techniques mentioned previously: conservative error bounds (which
+measure the error in certain calculations), exact geometric predicates
+(which determine the true mathematical relationship among geometric
+objects), and snap rounding (a technique that allows rounding results
+to finite precision without creating topological errors).
+
+### Classes vs. Functions
+
+Most algorithms in S2 are implemented as classes rather than functions.
+So for example, rather than having a `GetDistance` function, there is
+an `S2ClosestEdgeQuery` class with a `GetDistance` method. This design
+has two advantages:
+
+* It allows caching and reuse of data structures. For example, if an
+ algorithm needs to make thousands of distance queries, it can
+ declare one `S2ClosestEdgeQuery` object and call its methods
+ thousands of times. This provides the opportunity to avoid
+ recomputing data that can be used from one query to the next.
+
+* It provides a convenient way to specify options (via a nested
+ `Options` class), and to avoid respecifying those options many
+ times when repeated operations need to be performed.
+
+### Geocentric vs. Geodetic Coordinates
+
+Geocentric and geodetic coordinates are two methods for defining a
+(latitude, longitude) coordinate system over the Earth's surface. The
+question sometimes arises: which system does the S2 library use?
+
+The answer is: neither (or both). Points in S2 are modeled as lying on
+a perfect sphere (just as points in a traditional planar geometry
+library are modeled as lying on a perfect plane). How points are
+mapped onto the sphere is outside the scope of S2; it works equally
+well with geocentric or geodetic coordinates. (Actual geographic
+datasets use geodetic coordinates exclusively.)
+
+## Authors
+
+The S2 library was written primarily by Eric Veach. Other significant
+contributors include Jesse Rosenstock, Eric Engle (Java port lead), Robert
+Snedegar (Go port lead), Julien Basch, and Tom Manshreck
diff --git a/docs/about/platforms.md b/docs/about/platforms.md
new file mode 100644
index 00000000..33393194
--- /dev/null
+++ b/docs/about/platforms.md
@@ -0,0 +1,273 @@
+---
+title: S2 Installation
+---
+
+This document lists the platform requirements and installation
+instructions for working with the S2 Geometry Library in both
+C++ and Python. (The S2 Python interfaces uses SWIG to interact
+with the C++ code.)
+
+## Requirements
+
+Running the S2 code requires the following:
+
+* A MacOSX or Linux platform. (Windows is not supported at this time.)
+* POSIX support (for `getrusage()`).
+* A compatible C++ compiler *supporting at least C++11*, such as
+ [g++](https://gcc.gnu.org/){:target="_blank"} >= 4.7.
+* Installation of the following libraries:
+ * [OpenSSL](https://github.com/openssl/openssl){:target="_blank"} (for its
+ bignum library)
+ * [gflags command line flags](https://github.com/gflags/gflags)
+ {:target="_blank"} (optional, disabled by default)
+ * [glog logging module](https://github.com/google/glog) {:target="_blank"}
+ (optional, disabled by default)
+ * [googletest testing framework](https://github.com/google/googletest)
+ {:target="_blank"}
+ * [SWIG](https://github.com/swig/swig) (optional, for Python interface)
+* [Git](https://git-scm.com/) for interacting with the S2 source code
+ repository, which is contained on [GitHub](http://github.com). To install
+ Git, consult the [Set Up Git](https://help.github.com/articles/set-up-git/)
+ guide on GitHub.
+
+Installation instructions for the above components are [noted below](#install).
+
+
+Note: this installation guide uses CMake as the official build system for
+S2, which is supported on most major platforms and compilers. The S2
+source code assumes you are using CMake and contains
+CMakeList.txt files for that purpose.
+
+
+Although you are free to use your own build system, most of the documentation
+within this guide will assume you are using
+[CMake](https://cmake.org/){:target="_blank"}.
+
+## Installation Instructions {#install}
+
+Note: thorough testing has only been done on Ubuntu 14.04.3
+and MacOSX 10.12.
+
+### Setting Up Your Development Environment (Linux)
+
+Note: we recommend you use a Linux package manager such as
+`apt-get`. Alternatively, you may build the dependent
+libraries from source.
+
+1\. Install the additional libraries S2 code requires:
+
+
+$ sudo apt-get install libgflags-dev libgoogle-glog-dev libgtest-dev libssl-dev
+Reading package lists... Done
+Building dependency tree
+Reading state information... Done
+...
+After this operation, 3,090 kB of additional disk space will be used.
+Do you want to continue? [Y/n] Y
+...
+Setting up libgtest-dev (1.6.0-1ubuntu6) ...
+Processing triggers for libc-bin (2.19-0ubuntu6.13) ...
+$
+
+
+2\. (Optional) Install SWIG
+
+If you will be developing in Python, you should also install the
+[SWIG](http://www.swig.org){:target="_blank"} library). Most
+Python code will invoke the C++ code through this library.
+
+
+$ sudo apt-get install swig
+$
+
+
+3\. Install CMake
+
+
+$ sudo apt-get install cmake
+Reading package lists... Done
+Building dependency tree
+Reading state information... Done
+...
+After this operation, 16.6 MB of additional disk space will be used.
+Do you want to continue? [Y/n] Y
+...
+Setting up cmake (2.8.12.2-0ubuntu3) ...
+$
+
+
+### Setting Up Your Development Environment (MacOSX)
+
+Note: we recommend you use a MacOSX package manager such as
+[MacPorts](https://macports.org) or [Homebrew](https://brew.sh).
+Alternatively, you may build the dependent libraries from source.
+
+1\. Install the additional libraries S2 code requires:
+
+
+# MacPorts
+$ sudo port install gflags google-glog openssl
+
+# Homebrew
+$ brew install gflags glog openssl
+
+
+2\. Download Googletest
+[release 1.8.0](https://github.com/google/googletest/releases/tag/release-1.8.0){:target="_blank"}
+and unpack it in a directory of your choosing. (Take note of this
+directory as you will need to point CMake to it.)
+
+3\. (Optional) Install SWIG
+
+If you will be developing in Python, you should also install the
+[SWIG](http://www.swig.org){:target="_blank"} library). Most Python code will
+invoke the C++ code through this library.
+
+
+# MacPorts
+$ sudo port install swig
+
+# Homebrew
+$ brew install swig
+
+
+4\. Install CMake
+
+
+# Note: XCode requires command-line tools, which can be installed with:
+$ xcode-select --install
+
+# MacPorts
+$ sudo port install cmake
+
+# Homebrew
+$ brew install cmake
+
+
+### Getting the S2 Code
+
+The [Reference implementation of S2](https://github.com/google/s2geometry)
+is written in C++. Once you have CMake and Git installed, you can obtain
+the S2 code from its repository on GitHub:
+
+
+# Change to the directory where you want to create the code repository
+$ cd ~
+$ mkdir Source; cd Source
+
+
+Clone the S2 code into your development directory:
+
+
+$ git clone https://github.com/google/s2geometry.git
+Cloning into 's2geometry'...
+remote: Counting objects: 5672, done.
+remote: Compressing objects: 100% (52/52), done.
+remote: Total 5672 (delta 21), reused 36 (delta 17), pack-reused 5601
+Receiving objects: 100% (5672/5672), 7.15 MiB | 27.21 MiB/s, done.
+Resolving deltas: 100% (4503/4503), done.
+$
+
+
+Git will create the repository within a directory named `s2geometry`.
+Navigate into this directory.
+
+
+Alternatively, you can download an archive of the S2 library as a
+ZIP file
+and unzip it within your development directory.
+
+
+$ unzip path_to_ZIP_file/s2geometry-master.zip
+
+
+
+### Compiling S2
+
+Once you have installed S2's requirements and the S2 library, you
+are ready to build S2.
+
+1\. Configure the make files using CMake:
+
+
+$ cd s2geometry # or geometry-master if you installed from the ZIP file
+$ mkdir build
+$ cd build
+# See Notes below on whether/when to use these cmake args
+$ cmake -DWITH_GFLAGS=ON -WITH_GTEST=ON \
+-DGTEST_ROOT=googletest_root_dir \
+-DOPENSSL_INCLUDE_DIR=openssl_include_dir ..
+-- Found OpenSSL: /usr/lib/libcrypto.dylib (found version "1.0.2m")
+-- Looking for pthread.h
+-- Looking for pthread.h - found
+-- Looking for pthread_create
+-- Looking for pthread_create - found
+-- Found Threads: TRUE
+-- Found PythonInterp: /Library/Frameworks/Python.framework/Versions/2.7/bin/python (found version "2.7.11")
+-- Found PythonLibs: /Library/Frameworks/Python.framework/Versions/2.7/lib/libpython2.7.dylib (found version "2.7.11")
+GTEST_ROOT: /absolute_path/googletest
+-- Configuring done
+-- Generating done
+-- Build files have been written to: /absolute_path/s2geometry-master/build
+$
+
+
+Notes:
+
+* `-DWITH_GFLAGS` and `-DWITH_GTEST` may be omitted to avoid depending
+ on those packages.
+* `-DGTEST_ROOT` and `-DOPENSSL_INCLUDE_DIR` must be absolute paths.
+* `-DGTEST_ROOT`is the root directory for GoogleTest (e.g. `/usr/src/gtest` on
+ Linux systems, or the directory you directly installed GoogleTest within,
+ such as /Users/username/source/googletest on MacOSX).
+* MacOSX/Homebrew users may need to set `-DOPENSSL_INCLUDE_DIR` to enable
+ CMake to find your openssl `include` directory (e.g.
+ `/usr/local/homebrew/opt/openssl/include`).
+* You can omit the `DGTEST_ROOT` flag to skip tests.
+
+2\. Make the S2 binary (and associated tests if `GTEST_ROOT` was
+ specified in CMake:
+
+
+$ make
+Scanning dependencies of target gtest
+...
+Scanning dependencies of target s2
+...
+[ 35%] Built target s2
+...
+[100%] Built target point_index
+$
+
+
+3\. Run the S2 tests (if `GTEST_ROOT` was specified in CMake):
+
+
+$ make test
+Running tests...
+Test project /.../s2geometry-master/build
+ Start 1: id_set_lexicon_test
+...
+83/83 Test #83: value_lexicon_test ............................. Passed 0.01 sec
+100% tests passed, 0 tests failed out of 83
+Total Test time (real) = 78.67 sec
+$
+
+
+
+4\. Install the built S2 library:
+
+
+$ make install
+[ 1%] Built target gtest
+...
+[100%] Built target point_index
+Install the project...
+-- Install configuration: ""
+...
+$
+
+
+Congratulations! You've successfully installed S2, built the library, and run
+all of the tests! You're now ready to move on to the
+[C++ Quickstart](/devguide/cpp/quickstart).
diff --git a/docs/community/code-of-conduct.md b/docs/community/code-of-conduct.md
new file mode 100644
index 00000000..a9ad35e1
--- /dev/null
+++ b/docs/community/code-of-conduct.md
@@ -0,0 +1,87 @@
+---
+title: Code of Conduct
+---
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/
+This Code of Conduct is licensed under the Creative Commons 4.0.
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual
+ attention or advances
+* Trolling, insulting/derogatory comments, and personal or political
+ attacks
+* Public or private harassment
+* Publishing others’ private information, such as a physical or
+ electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate
+ in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of
+acceptable behavior and are expected to take appropriate and fair corrective
+action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies to all individuals associated with the project
+community and their actions and behaviors both inside and outside of the project
+spaces.
+
+## Conflict Resolution and Enforcement
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to
+address the behavior directly with those involved. Many issues can be resolved
+quickly and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to the
+S2 Geometry Library Admin,
+the Project Steward for S2. It is the Project Steward’s duty to receive and
+address reported violations of the code of conduct. The steward will then work
+with a committee consisting of representatives from the Open Source Programs
+Office and the Google Cloud Platform Open Source Strategy team.
+
+We will investigate every complaint, but you may not receive a direct
+response. We will use our discretion in determining when and how to follow
+up on reported incidents, which may range from not taking action to permanent
+expulsion from the project and project-sponsored spaces. We will notify the
+accused of the report and provide them an opportunity to discuss it before any
+action is taken. In potentially harmful situations, such as ongoing harassment
+or threats to anyone's safety, we may take action without notice. The Project
+Steward will only affirmatively identify the reporter when it is necessary in
+the resolution of the report and with permission from the reporter.
diff --git a/docs/community/index.md b/docs/community/index.md
new file mode 100644
index 00000000..75f0dd64
--- /dev/null
+++ b/docs/community/index.md
@@ -0,0 +1,26 @@
+---
+title: Community
+---
+
+S2 aims to have an active community of developers who are using, enhancing and
+building valuable integrations with other software projects. We’d love your help
+to improve and extend the project. You can reach us via the S2 Mailing List at
+s2geometry-io@googlegroups.com
+to start engaging with the project and its members.
+
+## Code of Conduct
+
+S2 wants to foster an open and welcoming environment for both our
+contributors and maintainers. We pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, nationality, personal appearance, race, religion, or sexual identity
+and orientation. To faciliate this open environment, please review our
+[Code of Conduct](code-of-conduct).
+
+## Mailing List
+
+Any questions or suggestions? Just want to be in the loop of what is going on
+with the project? Join the
+
+S2 Geometry Mailing List.
diff --git a/docs/devguide/basic_types.md b/docs/devguide/basic_types.md
new file mode 100644
index 00000000..1a6a81d3
--- /dev/null
+++ b/docs/devguide/basic_types.md
@@ -0,0 +1,1272 @@
+---
+title: Basic Types
+---
+
+
+
+## S1Angle
+
+The `S1Angle` class represents a one-dimensional angle (as opposed to a 2D solid
+angle). It has methods for converting angles to or from radians, degrees, and
+the E5/E6/E7 representations (i.e. degrees multiplied by 1e5/1e6/1e7 and rounded
+to the nearest integer).
+
+```c++
+class S1Angle {
+ public:
+ // These methods construct S1Angle objects from their measure in radians
+ // or degrees.
+ static constexpr S1Angle Radians(double radians);
+ static constexpr S1Angle Degrees(double degrees);
+ static constexpr S1Angle E5(int32 e5);
+ static constexpr S1Angle E6(int32 e6);
+ static constexpr S1Angle E7(int32 e7);
+
+ // The default constructor yields a zero angle. This is useful for STL
+ // containers and class methods with output arguments.
+ constexpr S1Angle();
+
+ // Return an angle larger than any finite angle.
+ static constexpr S1Angle Infinity();
+
+ // A explicit shorthand for the default constructor.
+ static constexpr S1Angle Zero();
+
+ // Return the angle between two points, which is also equal to the distance
+ // between these points on the unit sphere. The points do not need to be
+ // normalized.
+ S1Angle(const S2Point& x, const S2Point& y);
+
+ // Like the constructor above, but return the angle (i.e., distance)
+ // between two S2LatLng points.
+ S1Angle(const S2LatLng& x, const S2LatLng& y);
+
+ constexpr double radians() const;
+ constexpr double degrees() const;
+
+ int32 e5() const;
+ int32 e6() const;
+ int32 e7() const;
+
+ // Return the absolute value of an angle.
+ S1Angle abs() const;
+
+ // Return the angle normalized to the range (-180, 180] degrees.
+ S1Angle Normalized() const;
+
+ // Normalize this angle to the range (-180, 180] degrees.
+ void Normalize();
+};
+```
+
+See `s1angle.h` for additional methods, including comparison and arithmetic
+operators. For example, if `x` and `y` are angles, then you can write
+
+ if (sin(0.5 * x) > (x + y) / (x - y)) { ... }
+
+## S2Point
+
+The `S2Point` class represents a point on the unit sphere as a 3D vector.
+Usually points are normalized to be unit length, but some methods do not require
+this. The `S2Point` class is simply a synonym for the `Vector_3d` class from
+`util/math/vector.h`, which defines overloaded operators that make it convenient
+to write arithmetic expressions (e.g. `x*p1 + (1-x)*p2`).
+
+Some of its more useful methods include:
+
+```c++
+class S2Point /* Vector3_d */ {
+ public:
+ S2Point(double x, double y, double z);
+ double x(), y(), z(); // Named component accessors
+ double& operator[](int i); // Return component i (0, 1, 2)
+ bool operator==(const S2Point& v); // Equality testing
+ bool operator!=(const S2Point& v); // Inequality testing
+ S2Point operator+=(const S2Point& v); // Add another vector
+ S2Point operator-=(const S2Point& v); // Subtract another vector
+ S2Point operator*=(double k); // Multiply by a scalar
+ S2Point operator/=(double k); // Divide by a scalar
+ S2Point operator+(const S2Point& v); // Add two vectors
+ S2Point operator-(const S2Point& v); // Subtract two vectors
+ S2Point operator*(double k); // Multiply by a scalar
+ S2Point operator/(double k); // Divide by a scalar
+ double DotProd(const S2Point& v); // Dot product
+ S2Point CrossProd(const S2Point& v); // Cross product
+ double Norm2(); // Squared L2 norm
+ double Norm(); // L2 norm
+ S2Point Normalize(); // Return a normalized **copy**
+ double Angle(const S2Point&v); // Angle between two vectors (radians)
+};
+```
+
+Note that the `S2Point` type is technically defined as follows:
+
+ typedef Vector3 Vector3_d;
+ typedef Vector3_d S2Point;
+
+See `util/math/vector.h` for details and additional methods.
+
+## S2LatLng
+
+The `S2LatLng` class represents a point on the unit sphere as a pair of
+latitude-longitude coordinates.
+
+```c++
+class S2LatLng {
+ public:
+ // Constructor. The latitude and longitude are allowed to be outside
+ // the is_valid() range. However, note that most methods that accept
+ // S2LatLngs expect them to be normalized (see Normalized() below).
+ S2LatLng(S1Angle lat, S1Angle lng);
+
+ // The default constructor sets the latitude and longitude to zero. This is
+ // mainly useful when declaring arrays, STL containers, etc.
+ S2LatLng();
+
+ // Convert a direction vector (not necessarily unit length) to an S2LatLng.
+ explicit S2LatLng(const S2Point& p);
+
+ // Returns an S2LatLng for which is_valid() will return false.
+ static S2LatLng Invalid();
+
+ // Convenience functions -- shorter than calling S1Angle::Radians(), etc.
+ static S2LatLng FromRadians(double lat_radians, double lng_radians);
+ static S2LatLng FromDegrees(double lat_degrees, double lng_degrees);
+ static S2LatLng FromE5(int32 lat_e5, int32 lng_e5);
+ static S2LatLng FromE6(int32 lat_e6, int32 lng_e6);
+ static S2LatLng FromE7(int32 lat_e7, int32 lng_e7);
+
+ // Accessor methods.
+ S1Angle lat() const;
+ S1Angle lng() const;
+ const R2Point& coords() const;
+
+ // Return true if the latitude is between -90 and 90 degrees inclusive
+ // and the longitude is between -180 and 180 degrees inclusive.
+ bool is_valid() const;
+
+ // Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts
+ // a multiple of 360 degrees to the longitude if necessary to reduce it to
+ // the range [-180, 180].
+ S2LatLng Normalized() const;
+
+ // Convert a normalized S2LatLng to the equivalent unit-length vector.
+ S2Point ToPoint() const;
+
+ // Return the distance (measured along the surface of the sphere) to the
+ // given S2LatLng. This is mathematically equivalent to:
+ //
+ // S1Angle(ToPoint(), o.ToPoint())
+ //
+ // but this implementation is slightly more efficient. Both S2LatLngs
+ // must be normalized.
+ S1Angle GetDistance(const S2LatLng& o) const;
+};
+```
+
+See `s2latlng.h` for additional methods, including comparison and arithmetic
+operators.
+
+## S2Region
+
+An `S2Region` represents a two-dimensional region over the unit sphere. It is an
+abstract interface with various concrete subtypes, such as discs, rectangles,
+polylines, polygons, geometry collections, buffered shapes, etc.
+
+The main purpose of this interface is to allow complex regions to be
+approximated as simpler regions. So rather than having a wide variety of virtual
+methods that are implemented by all subtypes, the interface is restricted to
+methods that are useful for computing approximations. Here they are:
+
+```c++
+class S2Region {
+ public:
+ virtual ~S2Region();
+
+ // Return a deep copy of this region. If you want to narrow the result to a
+ // specific known region type, use down_cast from casts.h.
+ // Subtypes return pointers to that subtype from their Clone() methods.
+ virtual S2Region* Clone() const = 0;
+
+ // Return a bounding spherical cap. This is not guaranteed to be exact.
+ virtual S2Cap GetCapBound() const = 0;
+
+ // Return a bounding latitude-longitude rectangle that contains the region.
+ // The bounds are not guaranteed to be tight.
+ virtual S2LatLngRect GetRectBound() const = 0;
+
+ // If this method returns true, the region completely contains the given
+ // cell. Otherwise, either the region does not contain the cell or the
+ // containment relationship could not be determined.
+ virtual bool Contains(const S2Cell& cell) const = 0;
+
+ // If this method returns false, the region does not intersect the given
+ // cell. Otherwise, either region intersects the cell, or the intersection
+ // relationship could not be determined.
+ virtual bool MayIntersect(const S2Cell& cell) const = 0;
+
+ // Return true if and only if the given point is contained by the region.
+ // The point 'p' is generally required to be unit length, although some
+ // subtypes may relax this restriction.
+ virtual bool Contains(const S2Point& p) const = 0;
+
+ //////////////////////////////////////////////////////////////////////////
+ // Many S2Region subtypes also define the following non-virtual methods.
+
+ // Appends a serialized representation of the region to "encoder".
+ void Encode(Encoder* const encoder) const;
+
+ // Decodes an S2Region encoded with Encode(). Note that this method
+ // requires that an S2Region object of the appropriate concrete type has
+ // already been constructed. It is not possible to decode regions of
+ // unknown type.
+ bool Decode(Decoder* const decoder);
+};
+```
+
+See `s2region.h` for the full interface.
+
+## S2LatLngRect
+
+An `S2LatLngRect` is a type of `S2Region` that represents a rectangle in
+latitude-longitude space. It is capable of representing the empty and full
+rectangles as well as single points. It has an `AddPoint` method that makes it
+easy to construct a bounding rectangle for a set of points, including point sets
+that span the 180 degree meridian. Here are its methods:
+
+```c++
+class S2LatLngRect final : public S2Region {
+ public:
+ // Construct a rectangle from minimum and maximum latitudes and longitudes.
+ // If lo.lng() > hi.lng(), the rectangle spans the 180 degree longitude
+ // line. Both points must be normalized, with lo.lat() <= hi.lat().
+ // The rectangle contains all the points p such that 'lo' <= p <= 'hi',
+ // where '<=' is defined in the obvious way.
+ S2LatLngRect(const S2LatLng& lo, const S2LatLng& hi);
+
+ // Construct a rectangle from latitude and longitude intervals. The two
+ // intervals must either be both empty or both non-empty, and the latitude
+ // interval must not extend outside [-90, +90] degrees.
+ S2LatLngRect(const R1Interval& lat, const S1Interval& lng);
+
+ // The default constructor creates an empty S2LatLngRect.
+ S2LatLngRect();
+
+ // Construct a rectangle of the given size centered around the given point.
+ static S2LatLngRect FromCenterSize(const S2LatLng& center,
+ const S2LatLng& size);
+
+ // Construct a rectangle containing a single (normalized) point.
+ static S2LatLngRect FromPoint(const S2LatLng& p);
+
+ // Construct the minimal bounding rectangle containing the two given
+ // normalized points. The points do not need to be ordered.
+ static S2LatLngRect FromPointPair(const S2LatLng& p1, const S2LatLng& p2);
+
+ // Accessor methods.
+ S1Angle lat_lo() const;
+ S1Angle lat_hi() const;
+ S1Angle lng_lo() const;
+ S1Angle lng_hi() const;
+ const R1Interval& lat() const;
+ const S1Interval& lng() const;
+ R1Interval *mutable_lat();
+ S1Interval *mutable_lng();
+ S2LatLng lo() const;
+ S2LatLng hi() const;
+
+ // The canonical empty and full rectangles.
+ static S2LatLngRect Empty();
+ static S2LatLngRect Full();
+
+ // Return true if the rectangle is valid, which essentially just means
+ // that the latitude bounds do not exceed Pi/2 in absolute value and
+ // the longitude bounds do not exceed Pi in absolute value. Also, if
+ // either the latitude or longitude bound is empty then both must be.
+ bool is_valid() const;
+
+ // Return true if the rectangle is empty, i.e. it contains no points at all.
+ bool is_empty() const;
+
+ // Return true if the rectangle is full, i.e. it contains all points.
+ bool is_full() const;
+
+ // Return true if the rectangle is a point, i.e. lo() == hi()
+ bool is_point() const;
+
+ // Return true if the rectangle crosses the 180 degree longitude line.
+ bool is_inverted() const;
+
+ // Return the k-th vertex of the rectangle (k = 0,1,2,3) in CCW order (lower
+ // left, lower right, upper right, upper left).
+ S2LatLng GetVertex(int k) const;
+
+ // Return the center of the rectangle in latitude-longitude space
+ // (in general this is not the center of the region on the sphere).
+ S2LatLng GetCenter() const;
+
+ // Return the width and height of this rectangle in latitude-longitude
+ // space. Empty rectangles have a negative width and height.
+ S2LatLng GetSize() const;
+
+ // Returns the surface area of this rectangle on the unit sphere.
+ double Area() const;
+
+ // Return the true centroid of the rectangle multiplied by its surface area.
+ // The true centroid is the "center of mass" of the rectangle viewed as
+ // subset of the unit sphere, i.e. it is the point in space about which this
+ // curved shape would rotate.)
+ //
+ // The reason for multiplying the result by the rectangle area is to make it
+ // easier to compute the centroid of more complicated shapes.
+ S2Point GetCentroid() const;
+
+ // More efficient version of Contains() that accepts a S2LatLng rather than
+ // an S2Point.
+ bool Contains(const S2LatLng& ll) const;
+
+ // Return true if and only if the given point is contained in the interior
+ // of the region (i.e. the region excluding its boundary).
+ bool InteriorContains(const S2Point& p) const;
+
+ // More efficient version of InteriorContains() that accepts a S2LatLng
+ // rather than an S2Point.
+ bool InteriorContains(const S2LatLng& ll) const;
+
+ // Return true if and only if the rectangle contains the given other
+ // rectangle.
+ bool Contains(const S2LatLngRect& other) const;
+
+ // Return true if and only if the interior of this rectangle contains all
+ // points of the given other rectangle (including its boundary).
+ bool InteriorContains(const S2LatLngRect& other) const;
+
+ // Return true if this rectangle and the given other rectangle have any
+ // points in common.
+ bool Intersects(const S2LatLngRect& other) const;
+
+ // Returns true if this rectangle intersects the given cell. (This is an
+ // exact test and may be fairly expensive, see also MayIntersect below.)
+ bool Intersects(const S2Cell& cell) const;
+
+ // Return true if and only if the interior of this rectangle intersects
+ // any point (including the boundary) of the given other rectangle.
+ bool InteriorIntersects(const S2LatLngRect& other) const;
+
+ // Increase the size of the bounding rectangle to include the given point.
+ // The rectangle is expanded by the minimum amount possible.
+ void AddPoint(const S2Point& p);
+ void AddPoint(const S2LatLng& ll);
+
+ // Return a rectangle that has been expanded by margin.lat() on each side in
+ // the latitude direction, and by margin.lng() on each side in the longitude
+ // direction. If either margin is negative, then shrink the rectangle on
+ // the corresponding sides instead. The resulting rectangle may be empty.
+ //
+ // If you are trying to grow a rectangle by a certain *distance* on the
+ // sphere (e.g. 5km), use the ExpandedByDistance() method instead.
+ S2LatLngRect Expanded(const S2LatLng& margin) const;
+
+ // If the rectangle does not include either pole, return it unmodified.
+ // Otherwise expand the longitude range to Full() so that the rectangle
+ // contains all possible representations of the contained pole(s).
+ S2LatLngRect PolarClosure() const;
+
+ // Return the smallest rectangle containing the union of this rectangle and
+ // the given rectangle.
+ S2LatLngRect Union(const S2LatLngRect& other) const;
+
+ // Return the smallest rectangle containing the intersection of this
+ // rectangle and the given rectangle. Note that the region of intersection
+ // may consist of two disjoint rectangles, in which case a single rectangle
+ // spanning both of them is returned.
+ S2LatLngRect Intersection(const S2LatLngRect& other) const;
+
+ // Expand this rectangle so that it contains all points within the given
+ // distance of the boundary, and return the smallest such rectangle. If the
+ // distance is negative, then instead shrink this rectangle so that it
+ // excludes all points within the given absolute distance of the boundary,
+ // and return the largest such rectangle.
+ //
+ // Unlike Expanded(), this method treats the rectangle as a set of points on
+ // the sphere, and measures distances on the sphere. For example, you can
+ // use this method to find a rectangle that contains all points within 5km
+ // of a given rectangle.
+ S2LatLngRect ExpandedByDistance(S1Angle distance) const;
+
+ // Returns the minimum distance (measured along the surface of the sphere) to
+ // the given S2LatLngRect. Both S2LatLngRects must be non-empty.
+ S1Angle GetDistance(const S2LatLngRect& other) const;
+
+ // Returns the minimum distance (measured along the surface of the sphere)
+ // from a given point to the rectangle (both its boundary and its interior).
+ S1Angle GetDistance(const S2LatLng& p) const;
+
+ ////////////////////////////////////////////////////////////////////////
+ // S2Region interface (see s2region.h for details):
+
+ S2LatLngRect* Clone() const override;
+ S2Cap GetCapBound() const override;
+ S2LatLngRect GetRectBound() const override;
+ // The point 'p' does not need to be normalized.
+ bool Contains(const S2Cell& cell) const override;
+
+ // This test is cheap but is NOT exact. Use Intersects() if you want a more
+ // accurate and more expensive test.
+ bool MayIntersect(const S2Cell& cell) const override;
+};
+```
+
+See `s2latlngrect.h` for additional methods.
+
+## S1ChordAngle
+
+The S2 library actually defines two angle representations, `S1Angle` and
+`S1ChordAngle`.
+
+`S1ChordAngle` is a specialized angle type that represents the distance
+between two points on the sphere. (Note that the distance between two
+points can be expressed as the angle between those points measured from the
+sphere's center.) Its representation makes it very efficient for computing
+and comparing distances, but unlike `S1Angle` it is only capable of
+representing angles between 0 and 180 degrees. (Internally, `S1ChordAngle`
+computes the squared distance between two points through the interior of
+the sphere, i.e. the squared length of the chord between those points).
+
+`S1ChordAngle` is the preferred representation for distances internally,
+because it is much faster to compute than `S1Angle` and also supports the
+exact predicates needed for robust geometric algorithms. `S1ChordAngle`
+supports many of the same methods as `S1Angle`, and it is also easy to
+convert back and forth as required.
+
+## S2Cap
+
+An `S2Cap` represents a spherical cap, i.e. a portion of a sphere cut off by a
+plane. This is the equivalent of a *closed disc* in planar geometry (i.e., a
+circle together with its interior), so if you are looking for a way to represent
+a circle or disc then you are probably looking for an `S2Cap`.
+
+Here are the methods available:
+
+```c++
+class S2Cap final : public S2Region {
+ public:
+ // The default constructor returns an empty S2Cap.
+ S2Cap();
+
+ // Construct a cap with the given center and radius. A negative radius
+ // yields an empty cap; a radius of 180 degrees or more yields a full cap
+ // (containing the entire sphere).
+ S2Cap(const S2Point& center, S1Angle radius);
+
+ // Convenience function that creates a cap containing a single point. This
+ // method is more efficient that the S2Cap(center, radius) constructor.
+ static S2Cap FromPoint(const S2Point& center);
+
+ // Return a cap with the given center and height. (The height of a cap is
+ // the distance from the cap center to the cutoff plane; see comments
+ // above.) A negative height yields an empty cap; a height of 2 or more
+ // yields a full cap.
+ static S2Cap FromCenterHeight(const S2Point& center, double height);
+
+ // Return a cap with the given center and surface area. Note that the area
+ // can also be interpreted as the solid angle subtended by the cap (because
+ // the sphere has unit radius). A negative area yields an empty cap; an
+ // area of 4*Pi or more yields a full cap.
+ static S2Cap FromCenterArea(const S2Point& center, double area);
+
+ // Return an empty cap, i.e. a cap that contains no points.
+ static S2Cap Empty();
+
+ // Return a full cap, i.e. a cap that contains all points.
+ static S2Cap Full();
+
+ // Accessor methods.
+ const S2Point& center() const;
+ S1ChordAngle radius() const;
+ double height() const;
+
+ // Return the cap radius. (This method is relatively expensive since only
+ // the cap height is stored internally, and may yield a slightly different
+ // result than the value passed to the (center, radius) constructor.)
+ S1Angle GetRadius() const;
+
+ // Return the area of the cap.
+ double GetArea() const;
+
+ // Return the true centroid of the cap multiplied by its surface area. Note
+ // that if you just want the "surface centroid", i.e. the normalized result,
+ // then it is much simpler just to call center().
+ //
+ // The reason for multiplying the result by the cap area is to make it
+ // easier to compute the centroid of more complicated shapes.
+ S2Point GetCentroid() const;
+
+ // We allow negative heights (to represent empty caps) but heights are
+ // normalized so that they do not exceed 2.
+ bool is_valid() const;
+
+ // Return true if the cap is empty, i.e. it contains no points.
+ bool is_empty() const;
+
+ // Return true if the cap is full, i.e. it contains all points.
+ bool is_full() const;
+
+ // Return the complement of the interior of the cap. A cap and its
+ // complement have the same boundary but do not share any interior points.
+ S2Cap Complement() const;
+
+ // Return true if and only if this cap contains the given other cap
+ // (in a set containment sense, e.g. every cap contains the empty cap).
+ bool Contains(const S2Cap& other) const;
+
+ // Return true if and only if this cap intersects the given other cap,
+ // i.e. whether they have any points in common.
+ bool Intersects(const S2Cap& other) const;
+
+ // Return true if and only if the interior of this cap intersects the
+ // given other cap. (This relationship is not symmetric, since only
+ // the interior of this cap is used.)
+ bool InteriorIntersects(const S2Cap& other) const;
+
+ // Return true if and only if the given point is contained in the interior
+ // of the cap (i.e. the cap excluding its boundary).
+ bool InteriorContains(const S2Point& p) const;
+
+ // Increase the cap height if necessary to include the given point. If the
+ // cap is empty then the center is set to the given point, but otherwise the
+ // center is not changed.
+ void AddPoint(const S2Point& p);
+
+ // Increase the cap height if necessary to include "other". If the current
+ // cap is empty it is set to the given other cap.
+ void AddCap(const S2Cap& other);
+
+ // Return a cap that contains all points within a given distance of this
+ // cap. Note that any expansion of the empty cap is still empty.
+ S2Cap Expanded(S1Angle distance) const;
+
+ // Return the smallest cap which encloses this cap and "other".
+ S2Cap Union(const S2Cap& other) const;
+
+ ////////////////////////////////////////////////////////////////////////
+ // S2Region interface (see s2region.h for details):
+
+ S2Cap* Clone() const override;
+ S2Cap GetCapBound() const override;
+ S2LatLngRect GetRectBound() const override;
+ // The point "p" should be a unit-length vector.
+ bool Contains(const S2Cell& cell) const override;
+ bool MayIntersect(const S2Cell& cell) const override;
+};
+```
+
+See `s2cap.h` for additional methods.
+
+## S2Polyline
+
+An `S2Polyline` represents a sequence of zero or more vertices connected by
+straight edges (geodesics). Edges of length 0 and 180 degrees are not allowed,
+i.e. adjacent vertices should not be identical or antipodal.
+
+```c++
+class S2Polyline final : public S2Region {
+ public:
+ // Creates an empty S2Polyline that should be initialized by calling Init()
+ // or Decode().
+ S2Polyline();
+
+ // Convenience constructors that call Init() with the given vertices.
+ explicit S2Polyline(const std::vector& vertices);
+ explicit S2Polyline(const std::vector& vertices);
+
+ ~S2Polyline();
+
+ // Initialize a polyline that connects the given vertices. Empty polylines are
+ // allowed. Adjacent vertices should not be identical or antipodal. All
+ // vertices should be unit length.
+ void Init(const std::vector& vertices);
+
+ // Convenience initialization function that accepts latitude-longitude
+ // coordinates rather than S2Points.
+ void Init(const std::vector& vertices);
+
+ // Return true if the given vertices form a valid polyline.
+ bool IsValid() const;
+
+ // Returns true if this is *not* a valid polyline and sets "error"
+ // appropriately. Otherwise returns false and leaves "error" unchanged.
+ bool FindValidationError(S2Error* error) const;
+
+ // Accessor methods.
+ int num_vertices() const;
+ const S2Point& vertex(int k) const;
+
+ // Return the length of the polyline.
+ S1Angle GetLength() const;
+
+ // Return the true centroid of the polyline multiplied by the length of the
+ // polyline. Prescaling by the polyline length makes it easy to compute the
+ // centroid of several polylines (by simply adding up their centroids).
+ S2Point GetCentroid() const;
+
+ // Return the point whose distance from vertex 0 along the polyline is the
+ // given fraction of the polyline's total length. Fractions less than zero
+ // or greater than one are clamped. The return value is unit length.
+ S2Point Interpolate(double fraction) const;
+
+ // Like Interpolate(), but also return the index of the next polyline
+ // vertex after the interpolated point P. This allows the caller to easily
+ // construct a given suffix of the polyline by concatenating P with the
+ // polyline vertices starting at "next_vertex".
+ S2Point GetSuffix(double fraction, int* next_vertex) const;
+
+ // The inverse operation of GetSuffix/Interpolate. Given a point on the
+ // polyline, returns the ratio of the distance to the point from the
+ // beginning of the polyline over the length of the polyline. The return
+ // value is always betwen 0 and 1 inclusive. See GetSuffix() for the
+ // meaning of "next_vertex".
+ double UnInterpolate(const S2Point& point, int next_vertex) const;
+
+ // Given a point, returns a point on the polyline that is closest to the given
+ // point. See GetSuffix() for the meaning of "next_vertex".
+ S2Point Project(const S2Point& point, int* next_vertex) const;
+
+ // Returns true if the point given is on the right hand side of the polyline,
+ // using a naive definition of "right-hand-sideness" where the point is on
+ // the RHS of the polyline iff the point is on the RHS of the line segment in
+ // the polyline which it is closest to.
+ bool IsOnRight(const S2Point& point) const;
+
+ // Return true if this polyline intersects the given polyline. If the
+ // polylines share a vertex they are considered to be intersecting.
+ bool Intersects(const S2Polyline* line) const;
+
+ // Reverse the order of the polyline vertices.
+ void Reverse();
+
+ // Return a subsequence of vertex indices such that the polyline connecting
+ // these vertices is never further than "tolerance" from the original
+ // polyline.
+ void SubsampleVertices(S1Angle tolerance, std::vector* indices) const;
+
+ // Return true if "covered" is within "max_error" of a contiguous subpath of
+ // this polyline over its entire length. Specifically, this method returns
+ // true if this polyline has parameterization a:[0,1] -> S^2, "covered" has
+ // parameterization b:[0,1] -> S^2, and there is a non-decreasing function
+ // f:[0,1] -> [0,1] such that distance(a(f(t)), b(t)) <= max_error for all t.
+ //
+ // You can think of this as testing whether it is possible to drive a car
+ // along "covered" and a car along some subpath of this polyline such that no
+ // car ever goes backward, and the cars are always within "max_error" of each
+ // other.
+ bool NearlyCoversPolyline(const S2Polyline& covered,
+ S1Angle max_error) const;
+
+ ////////////////////////////////////////////////////////////////////////
+ // S2Region interface (see s2region.h for details):
+
+ S2Polyline* Clone() const override;
+ S2Cap GetCapBound() const override;
+ S2LatLngRect GetRectBound() const override;
+ bool Contains(const S2Cell& cell) const override;
+ bool MayIntersect(const S2Cell& cell) const override;
+};
+```
+
+## S2Loop
+
+An `S2Loop` represents a simple spherical polygon. It consists of a single chain
+of vertices where the first vertex is implicitly connected to the last. All
+loops are defined to have a CCW orientation, i.e. the interior of the loop is on
+the left side of the edges. This implies that a clockwise loop enclosing a small
+area is interpreted to be a CCW loop enclosing a very large area.
+
+Loops are not allowed to have any duplicate vertices (whether adjacent or not),
+and non-adjacent edges are not allowed to intersect. Loops must have at least 3
+vertices (except for the "empty" and "full" loops discussed below). These
+restrictions make it possible to implement exact polygon-polygon containment and
+intersection tests very efficiently. See `S2Builder` if your data does not meet
+these requirements.
+
+There are two special loops: the "empty" loop contains no points, while the
+"full" loop contains all points. These loops do not have any edges, but to
+preserve the invariant that every loop can be represented as a vertex chain,
+they are defined as having exactly one vertex each (see kEmpty and kFull).
+
+Point containment of loops is defined such that if the sphere is subdivided into
+faces (loops), every point is contained by exactly one face. This implies that
+loops do not necessarily contain their vertices.
+
+```c++
+class S2Loop final : public S2Region {
+ public:
+ // Default constructor. The loop must be initialized by calling Init() or
+ // Decode() before it is used.
+ S2Loop();
+
+ // Convenience constructor that calls Init() with the given vertices.
+ explicit S2Loop(const std::vector& vertices);
+
+ // Construct a loop corresponding to the given cell.
+ //
+ // Note that the loop and cell *do not* contain exactly the same set of
+ // points, because S2Loop and S2Cell have slightly different definitions of
+ // point containment. For example, an S2Cell vertex is contained by all
+ // four neighboring S2Cells, but it is contained by exactly one of four
+ // S2Loops constructed from those cells.
+ explicit S2Loop(const S2Cell& cell);
+
+ ~S2Loop();
+
+ // Initialize a loop with given vertices. The last vertex is implicitly
+ // connected to the first. All points should be unit length. Loops must
+ // have at least 3 vertices (except for the "empty" and "full" loops, see
+ // kEmpty and kFull). This method may be called multiple times.
+ void Init(const std::vector& vertices);
+
+ // A special vertex chain of length 1 that creates an empty loop (i.e., a
+ // loop with no edges that contains no points). Example usage:
+ // S2Loop empty(S2Loop::kEmpty());
+ static std::vector kEmpty();
+
+ // A special vertex chain of length 1 that creates a full loop (i.e., a loop
+ // with no edges that contains all points). See kEmpty() for details.
+ static std::vector kFull();
+
+ // Returns true if this is a valid loop.
+ bool IsValid() const;
+
+ // Returns true if this is *not* a valid loop and sets "error"
+ // appropriately. Otherwise returns false and leaves "error" unchanged.
+ bool FindValidationError(S2Error* error) const;
+
+ int num_vertices() const;
+
+ // For convenience, we make two entire copies of the vertex list available:
+ // vertex(n..2*n-1) is mapped to vertex(0..n-1), where n == num_vertices().
+ const S2Point& vertex(int i) const;
+
+ // Like vertex(), but this method returns vertices in reverse order if the
+ // loop represents a polygon hole. This ensures that the interior of the
+ // polygon is always to the left of the vertex chain.
+ const S2Point& oriented_vertex(int i) const;
+
+ // Return true if this is the special "empty" loop that contains no points.
+ bool is_empty() const;
+
+ // Return true if this is the special "full" loop that contains all points.
+ bool is_full() const;
+
+ // Return true if this loop is either "empty" or "full".
+ bool is_empty_or_full() const;
+
+ // The depth of a loop is defined as its nesting level within its containing
+ // polygon. "Outer shell" loops have depth 0, holes within those loops have
+ // depth 1, shells within those holes have depth 2, etc. This field is only
+ // used by the S2Polygon implementation.
+ int depth() const;
+ void set_depth(int depth);
+
+ // Return true if this loop represents a hole in its containing polygon.
+ bool is_hole() const;
+
+ // The sign of a loop is -1 if the loop represents a hole in its containing
+ // polygon, and +1 otherwise.
+ int sign() const;
+
+ // Return true if the loop area is at most 2*Pi.
+ bool IsNormalized() const;
+
+ // Invert the loop if necessary so that the area enclosed by the loop is at
+ // most 2*Pi.
+ void Normalize();
+
+ // Reverse the order of the loop vertices, effectively complementing
+ // the region represented by the loop.
+ void Invert();
+
+ // Return the area of the loop interior, i.e. the region on the left side of
+ // the loop. The return value is between 0 and 4*Pi.
+ double GetArea() const;
+
+ // Return the true centroid of the loop multiplied by the area of the loop.
+ // We prescale by the loop area for two reasons: (1) it is cheaper to
+ // compute this way, and (2) it makes it easier to compute the centroid of
+ // more complicated shapes (by splitting them into disjoint regions and
+ // adding their centroids).
+ S2Point GetCentroid() const;
+
+ // Return the sum of the turning angles at each vertex. The return value is
+ // positive if the loop is counter-clockwise, negative if the loop is
+ // clockwise, and zero if the loop is a great circle.
+ //
+ // This quantity is also called the "geodesic curvature" of the loop.
+ double GetCurvature() const;
+
+ // Return the maximum error in GetCurvature(). The return value is not
+ // constant; it depends on the loop.
+ double GetCurvatureMaxError() const;
+
+ // Return the distance from the given point to the loop interior. If the
+ // loop is empty, return S1Angle::Infinity().
+ S1Angle GetDistance(const S2Point& x) const;
+
+ // Return the distance from the given point to the loop boundary. If the
+ // loop is empty or full, return S1Angle::Infinity() (since the loop has no
+ // boundary).
+ S1Angle GetDistanceToBoundary(const S2Point& x) const;
+
+ // If the given point is contained by the loop, return it. Otherwise return
+ // the closest point on the loop boundary. If the loop is empty, return the
+ // input argument.
+ S2Point Project(const S2Point& x) const;
+
+ // Return the closest point on the loop boundary to the given point. If the
+ // loop is empty or full, return the input argument (since the loop has no
+ // boundary).
+ S2Point ProjectToBoundary(const S2Point& x) const;
+
+ // Return true if the region contained by this loop is a superset of the
+ // region contained by the given other loop.
+ bool Contains(const S2Loop* b) const;
+
+ // Return true if the region contained by this loop intersects the region
+ // contained by the given other loop.
+ bool Intersects(const S2Loop* b) const;
+
+ // Return true if two loops have the same vertices in the same linear order
+ // (i.e., cyclic rotations are not allowed).
+ bool Equals(const S2Loop* b) const;
+
+ // Return true if two loops have the same boundary. This is true if and
+ // only if the loops have the same vertices in the same cyclic order (i.e.,
+ // the vertices may be cyclically rotated). The empty and full loops are
+ // considered to have different boundaries.
+ bool BoundaryEquals(const S2Loop* b) const;
+
+ // Return true if two loops have the same boundary except for vertex
+ // perturbations. More precisely, the vertices in the two loops must be in
+ // the same cyclic order, and corresponding vertex pairs must be separated
+ // by no more than "max_error".
+ bool BoundaryApproxEquals(const S2Loop& b,
+ S1Angle max_error = S1Angle::Radians(1e-15)) const;
+
+ // Return true if the two loop boundaries are within "max_error" of each
+ // other along their entire lengths. The two loops may have different
+ // numbers of vertices. More precisely, this method returns true if the two
+ // loops have parameterizations a:[0,1] -> S^2, b:[0,1] -> S^2 such that
+ // distance(a(t), b(t)) <= max_error for all t. You can think of this as
+ // testing whether it is possible to drive two cars all the way around the
+ // two loops such that no car ever goes backward and the cars are always
+ // within "max_error" of each other.
+ bool BoundaryNear(const S2Loop& b,
+ S1Angle max_error = S1Angle::Radians(1e-15)) const;
+
+ // This method computes the oriented surface integral of some quantity f(x)
+ // over the loop interior, given a function f_tri(A,B,C) that returns the
+ // corresponding integral over the spherical triangle ABC.
+ template
+ T GetSurfaceIntegral(T f_tri(const S2Point&, const S2Point&, const S2Point&))
+ const;
+
+ ////////////////////////////////////////////////////////////////////////
+ // S2Region interface (see s2region.h for details):
+
+ S2Loop* Clone() const override;
+
+ // GetRectBound() returns essentially tight results, while GetCapBound()
+ // might have a lot of extra padding. Both bounds are conservative in that
+ // if the loop contains a point P, then the bound contains P also.
+ S2Cap GetCapBound() const override;
+ S2LatLngRect GetRectBound() const override;
+ bool Contains(const S2Cell& cell) const override;
+ bool MayIntersect(const S2Cell& cell) const override;
+ // Return true if the loop contains the given point. Point containment is
+ // defined such that if the sphere is subdivided into faces (loops), every
+ // point is contained by exactly one face. This implies that loops do not
+ // necessarily contain their vertices.
+ bool Contains(const S2Point& p) const override;
+
+ // Generally clients should not use S2Loop::Encode(). Instead they should
+ // encode an S2Polygon, which unlike this method supports (lossless)
+ // compression.
+ void Encode(Encoder* const encoder) const override;
+
+ // Decode a loop encoded with Encode() or EncodeCompressed(). These methods
+ // may be called with loops that have already been initialized.
+ bool Decode(Decoder* const decoder) override;
+ bool DecodeWithinScope(Decoder* const decoder) override;
+};
+```
+
+## S2Polygon
+
+An `S2Polygon` is an `S2Region` object that represents a polygon. A polygon is
+defined by zero or more loops; recall that the interior of a loop is defined to
+be its left-hand side (see `S2Loop`). There are two different conventions for
+creating an `S2Polygon`:
+
+* `InitNested()` expects the input loops to be nested hierarchically. The
+ polygon interior then consists of the set of points contained by an odd
+ number of loops. So for example, a circular region with a hole in it would
+ be defined as two CCW loops, with one loop containing the other. The loops
+ can be provided in any order.
+
+ When the orientation of the input loops is unknown, the nesting requirement
+ is typically met by calling `S2Loop::Normalize()` on each loop (which
+ inverts the loop if necessary so that it encloses at most half the sphere).
+ But in fact any set of loops can be used as long as (1) there is no pair of
+ loops that cross, and (2) there is no pair of loops whose union is the
+ entire sphere.
+
+* `InitOriented()` expects the input loops to be oriented such that the
+ polygon interior is on the left-hand side of every loop. So for example, a
+ circular region with a hole in it would be defined using a CCW outer loop
+ and a CW inner loop. The loop orientations must all be consistent; for
+ example, it is not valid to have one CCW loop nested inside another CCW
+ loop, because the region between the two loops is on the left-hand side of
+ one loop and the right-hand side of the other.
+
+Most clients will not call these methods directly; instead they should use
+`S2Builder`, which has better support for dealing with imperfect data.
+
+When the polygon is initialized, the given loops are automatically converted
+into a canonical form consisting of "shells" and "holes". Shells and holes are
+both oriented CCW, and are nested hierarchically. The loops are reordered to
+correspond to a preorder traversal of the nesting hierarchy; `InitOriented()`
+may also invert some loops.
+
+Polygons may represent any region of the sphere with a polygonal boundary,
+including the entire sphere (known as the "full" polygon). The full polygon
+consists of a single full loop (see `S2Loop`), whereas the empty polygon has no
+loops at all.
+
+Polygons have the following restrictions:
+
+* Loops may not cross, i.e. the boundary of a loop may not intersect both the
+ interior and exterior of any other loop.
+
+* Loops may not share edges, i.e. if a loop contains an edge AB, then no other
+ loop may contain AB or BA.
+
+* Loops may share vertices, however no vertex may appear twice in a single
+ loop (see `S2Loop`).
+
+* No loop may be empty. The full loop may appear only in the full polygon.
+
+
+```c++
+class S2Polygon final : public S2Region {
+ public:
+ // The default constructor creates an empty polygon. It can be made
+ // non-empty by calling Init(), Decode(), etc.
+ S2Polygon();
+
+ // Convenience constructor that calls InitNested() with the given loops.
+ // Takes ownership of the loops and clears the vector.
+ explicit S2Polygon(std::vector> loops);
+
+ // Convenience constructor that creates a polygon with a single loop
+ // corresponding to the given cell.
+ explicit S2Polygon(const S2Cell& cell);
+
+ // Convenience constructor that calls Init(unique_ptr).
+ explicit S2Polygon(std::unique_ptr loop);
+
+ // Destroys the polygon and frees its loops.
+ ~S2Polygon();
+
+ // Create a polygon from a set of hierarchically nested loops. The polygon
+ // interior consists of the points contained by an odd number of loops.
+ // (Recall that a loop contains the set of points on its left-hand side.)
+ //
+ // This method takes ownership of the given loops and clears the given
+ // vector. It then figures out the loop nesting hierarchy and assigns every
+ // loop a depth. Shells have even depths, and holes have odd depths. Note
+ // that the loops are reordered so the hierarchy can be traversed more
+ // easily (see GetParent(), GetLastDescendant(), and S2Loop::depth()).
+ //
+ // This method may be called more than once, in which case any existing
+ // loops are deleted before being replaced by the input loops.
+ void InitNested(std::vector> loops);
+
+ // Like InitNested(), but expects loops to be oriented such that the polygon
+ // interior is on the left-hand side of all loops. This implies that shells
+ // and holes should have opposite orientations in the input to this method.
+ void InitOriented(std::vector> loops);
+
+ // Initialize a polygon from a single loop. Takes ownership of the loop.
+ void Init(std::unique_ptr loop);
+
+ // Releases ownership of and returns the loops of this polygon, and resets
+ // the polygon to be empty.
+ std::vector> Release();
+
+ // Makes a deep copy of the given source polygon. The destination polygon
+ // will be cleared if necessary.
+ void Copy(const S2Polygon* src);
+
+ // Returns true if this is a valid polygon (including checking whether all
+ // the loops are themselves valid).
+ bool IsValid() const;
+
+ // Returns true if this is *not* a valid polygon and sets "error"
+ // appropriately. Otherwise returns false and leaves "error" unchanged.
+ bool FindValidationError(S2Error* error) const;
+
+ // Return true if this is the empty polygon (consisting of no loops).
+ bool is_empty() const;
+
+ // Return true if this is the full polygon (consisting of a single loop that
+ // encompasses the entire sphere).
+ bool is_full() const;
+
+ // Return the number of loops in this polygon.
+ int num_loops() const;
+
+ // Total number of vertices in all loops.
+ int num_vertices() const;
+
+ // Return the loop at the given index. Note that during initialization, the
+ // given loops are reordered according to a preorder traversal of the loop
+ // nesting hierarchy. This implies that every loop is immediately followed
+ // by its descendants. This hierarchy can be traversed using the methods
+ // GetParent(), GetLastDescendant(), and S2Loop::depth().
+ const S2Loop* loop(int k) const;
+ S2Loop* loop(int k);
+
+ // Return the index of the parent of loop k, or -1 if it has no parent.
+ int GetParent(int k) const;
+
+ // Return the index of the last loop that is contained within loop k.
+ int GetLastDescendant(int k) const;
+
+ // Return the area of the polygon interior, i.e. the region on the left side
+ // of an odd number of loops. The return value is between 0 and 4*Pi.
+ double GetArea() const;
+
+ // Return the true centroid of the polygon multiplied by the area of the
+ // polygon. We prescale by the polygon area for two reasons: (1) it is
+ // cheaper to compute this way, and (2) it makes it easier to compute the
+ // centroid of more complicated shapes (by splitting them into disjoint
+ // regions and adding their centroids).
+ S2Point GetCentroid() const;
+
+ // If all of the polygon's vertices happen to be the centers of S2Cells at
+ // some level, then return that level, otherwise return -1.
+ int GetSnapLevel() const;
+
+ // Return the distance from the given point to the polygon interior. If the
+ // polygon is empty, return S1Angle::Infinity().
+ S1Angle GetDistance(const S2Point& x) const;
+
+ // Return the distance from the given point to the polygon boundary. If the
+ // polygon is empty or full, return S1Angle::Infinity() (since the polygon
+ // has no boundary).
+ S1Angle GetDistanceToBoundary(const S2Point& x) const;
+
+ // Return the overlap fractions between two polygons, i.e. the ratios of the
+ // area of intersection to the area of each polygon.
+ static std::pair GetOverlapFractions(const S2Polygon* a,
+ const S2Polygon* b);
+
+ // If the given point is contained by the polygon, return it. Otherwise
+ // return the closest point on the polygon boundary. If the polygon is
+ // empty, return the input argument.
+ S2Point Project(const S2Point& x) const;
+
+ // Return the closest point on the polygon boundary to the given point. If
+ // the polygon is empty or full, return the input argument (since the
+ // polygon has no boundary).
+ S2Point ProjectToBoundary(const S2Point& x) const;
+
+ // Return true if this polygon contains the given other polygon, i.e.
+ // if polygon A contains all points contained by polygon B.
+ bool Contains(const S2Polygon* b) const;
+
+ // Returns true if this polgyon (A) approximately contains the given other
+ // polygon (B). This is true if it is possible to move the vertices of B
+ // no further than "tolerance" such that A contains the modified B.
+ //
+ // For example, the empty polygon will contain any polygon whose maximum
+ // width is no more than "tolerance".
+ bool ApproxContains(const S2Polygon* b, S1Angle tolerance) const;
+
+ // Return true if this polygon intersects the given other polygon, i.e.
+ // if there is a point that is contained by both polygons.
+ bool Intersects(const S2Polygon* b) const;
+
+ // Returns true if this polgyon (A) and the given polygon (B) are
+ // approximately disjoint. This is true if it is possible to ensure that A
+ // and B do not intersect by moving their vertices no further than
+ // "tolerance".
+ //
+ // For example, any polygon is approximately disjoint from a polygon whose
+ // maximum width is no more than "tolerance".
+ bool ApproxDisjoint(const S2Polygon& b, S1Angle tolerance) const;
+
+ // Initialize this polygon to the intersection, union, difference (A - B),
+ // or symmetric difference (XOR) of the given two polygons.
+ //
+ // "snap_function" allows you to specify a minimum spacing between output
+ // vertices, and/or that the vertices should be snapped to a discrete set of
+ // points (e.g. S2CellId centers or E7 lat/lng coordinates). Any snap
+ // function can be used, including the IdentitySnapFunction with a
+ // snap_radius of zero (which preserves the input vertices exactly).
+ void InitToIntersection(const S2Polygon* a, const S2Polygon* b);
+ void InitToIntersection(const S2Polygon& a, const S2Polygon& b,
+ const S2Builder::SnapFunction& snap_function);
+
+ void InitToUnion(const S2Polygon* a, const S2Polygon* b);
+ void InitToUnion(const S2Polygon& a, const S2Polygon& b,
+ const S2Builder::SnapFunction& snap_function);
+
+ void InitToDifference(const S2Polygon* a, const S2Polygon* b);
+ void InitToDifference(const S2Polygon& a, const S2Polygon& b,
+ const S2Builder::SnapFunction& snap_function);
+
+ void InitToSymmetricDifference(const S2Polygon* a, const S2Polygon* b);
+ void InitToSymmetricDifference(
+ const S2Polygon& a, const S2Polygon& b,
+ const S2Builder::SnapFunction& snap_function);
+
+ // Convenience functions that use the IdentitySnapFunction with the given
+ // snap radius.
+ void InitToApproxIntersection(const S2Polygon* a, const S2Polygon* b,
+ S1Angle vertex_merge_radius);
+ void InitToApproxUnion(const S2Polygon* a, const S2Polygon* b,
+ S1Angle vertex_merge_radius);
+ void InitToApproxDifference(const S2Polygon* a, const S2Polygon* b,
+ S1Angle vertex_merge_radius);
+ void InitToApproxSymmetricDifference(const S2Polygon* a, const S2Polygon* b,
+ S1Angle vertex_merge_radius);
+
+ // Initializes this polygon according to the given "snap_function" and
+ // reduces the number of vertices if possible, while ensuring that no vertex
+ // moves further than snap_function.snap_radius().
+ //
+ // Simplification works by replacing nearly straight chains of short edges
+ // with longer edges, in a way that preserves the topology of the input
+ // polygon up to the creation of degeneracies. This means that loops or
+ // portions of loops may become degenerate, in which case they are removed.
+ void InitToSimplified(const S2Polygon& a,
+ const S2Builder::SnapFunction& snap_function);
+
+ // Use S2Builder to build this polygon by assembling the edges of a
+ // given polygon after snapping its vertices to the center of leaf cells at
+ // the given "snap_level". The default snap level corresponds to a
+ // tolerance of approximately 1.5cm on the surface of the Earth.
+ // Such a polygon can be efficiently compressed when serialized.
+ void InitToSnapped(const S2Polygon* polygon,
+ int snap_level = S2CellId::kMaxLevel);
+
+ // Initialize this polygon to the complement of the given polygon.
+ void InitToComplement(const S2Polygon* a);
+
+ // Invert the polygon (replace it by its complement).
+ void Invert();
+
+ // Intersect this polygon with the polyline "in" and append the resulting
+ // zero or more polylines to "out" (which must be empty). The polylines
+ // are appended in the order they would be encountered by traversing "in"
+ // from beginning to end. Note that the output may include polylines with
+ // only one vertex, but there will not be any zero-vertex polylines.
+ std::vector>
+ IntersectWithPolyline(const S2Polyline& in) const;
+
+ // Similar to IntersectWithPolyline(), except that vertices will be
+ // dropped as necessary to ensure that all adjacent vertices in the
+ // sequence obtained by concatenating the output polylines will be
+ // farther than "vertex_merge_radius" apart.
+ std::vector>
+ ApproxIntersectWithPolyline(const S2Polyline& in,
+ S1Angle vertex_merge_radius) const;
+
+ // Like IntersectWithPolyline, but subtracts this polygon from
+ // the given polyline.
+ std::vector>
+ SubtractFromPolyline(const S2Polyline& in) const;
+
+ // Like ApproxIntersectWithPolyline, but subtracts this polygon
+ // from the given polyline.
+ std::vector>
+ ApproxSubtractFromPolyline(const S2Polyline& in,
+ S1Angle vertex_merge_radius) const;
+
+ // Return a polygon which is the union of the given polygons.
+ // Clears the vector and deletes the polygons.
+ static std::unique_ptr
+ DestructiveUnion(std::vector> polygons);
+ static std::unique_ptr
+ DestructiveApproxUnion(std::vector> polygons,
+ S1Angle vertex_merge_radius);
+
+ // Initialize this polygon to the outline of the given cell union.
+ // In principle this polygon should exactly contain the cell union and
+ // this polygon's inverse should not intersect the cell union, but rounding
+ // issues may cause this not to be the case.
+ void InitToCellUnionBorder(const S2CellUnion& cells);
+
+ // Return true if every loop of this polygon shares at most one vertex with
+ // its parent loop. Every polygon has a unique normalized form. Normalized
+ // polygons are useful for testing since it is easy to compare whether two
+ // polygons represent the same region.
+ bool IsNormalized() const;
+
+ // Return true if two polygons have exactly the same loops. The loops must
+ // appear in the same order, and corresponding loops must have the same
+ // linear vertex ordering (i.e., cyclic rotations are not allowed).
+ bool Equals(const S2Polygon* b) const;
+
+ // Return true if two polygons have the same boundary. More precisely, this
+ // method requires that both polygons have loops with the same cyclic vertex
+ // order and the same nesting hierarchy. (This implies that vertices may be
+ // cyclically rotated between corresponding loops, and the loop ordering may
+ // be different between the two polygons as long as the nesting hierarchy is
+ // the same.)
+ bool BoundaryEquals(const S2Polygon* b) const;
+
+ // Return true if two polygons have the same boundary except for vertex
+ // perturbations. Both polygons must have loops with the same cyclic vertex
+ // order and the same nesting hierarchy, but the vertex locations are
+ // allowed to differ by up to "max_error".
+ bool BoundaryApproxEquals(const S2Polygon& b,
+ S1Angle max_error = S1Angle::Radians(1e-15)) const;
+
+ // Return true if two polygons have boundaries that are within "max_error"
+ // of each other along their entire lengths. More precisely, there must be
+ // a bijection between the two sets of loops such that for each pair of
+ // loops, "a_loop->BoundaryNear(b_loop)" is true.
+ bool BoundaryNear(const S2Polygon& b,
+ S1Angle max_error = S1Angle::Radians(1e-15)) const;
+
+ ////////////////////////////////////////////////////////////////////////
+ // S2Region interface (see s2region.h for details):
+
+ // GetRectBound() returns essentially tight results, while GetCapBound()
+ // might have a lot of extra padding. Both bounds are conservative in that
+ // if the loop contains a point P, then the bound contains P also.
+ S2Polygon* Clone() const override;
+ S2Cap GetCapBound() const override;
+ S2LatLngRect GetRectBound() const override;
+
+ bool Contains(const S2Cell& cell) const override;
+ bool MayIntersect(const S2Cell& cell) const override;
+ // Return true if the polygon contains the given point. Point containment
+ // is defined such that if the sphere is subdivided into polygons, every
+ // point is contained by exactly one polygons. This implies that polygons
+ // do not necessarily contain their vertices.
+ bool Contains(const S2Point& p) const override;
+
+ // Encode the polygon with about 4 bytes per vertex, assuming the vertices
+ // have all been snapped to the centers of S2Cells at a given level
+ // (typically with InitToSnapped). The other vertices are stored using 24
+ // bytes. Decoding a polygon encoded this way always returns the original
+ // polygon, without any loss of precision.
+ void Encode(Encoder* const encoder) const override;
+
+ // Decode a polygon encoded with Encode().
+ bool Decode(Decoder* const decoder) override;
+};
+```
diff --git a/docs/devguide/coding_style.md b/docs/devguide/coding_style.md
new file mode 100644
index 00000000..f059c767
--- /dev/null
+++ b/docs/devguide/coding_style.md
@@ -0,0 +1,923 @@
+---
+title: S2 C++ Style Guide
+---
+
+
+
+## Purpose
+
+This guide documents the C++ coding conventions used by the S2 Geometry Library.
+It does not attempt to justify why these conventions are used, and indeed in
+some cases they could be improved upon.
+
+In general, the S2 C++ coding style is built on the [Google C++ Style
+Guide](https://google.github.io/styleguide/cppguide.html). However it's
+important to remember that the S2 library has a long history, and many parts of
+the library were developed before modern C++ features even existed. The S2
+library has many clients both within and outside of Google (through the open
+source release), which can make it very difficult to adopt new conventions.
+
+**The overriding objective when adding new code should be to maintain
+consistency within the library.** In cases where the S2 conventions conflict
+with the current Google style guide, the existing S2 conventions should take
+precedence. Changes in coding style should only be made when it is practical to
+adopt them across the entire library, including the important step of updating
+all affected clients so that any deprecated classes or methods can be removed.
+
+The first step when adding new classes or methods should be to find similar
+classes or methods that already exist in the library, and then follow their
+conventions as closely as possible.
+
+## External Dependencies
+
+The S2 library is built on many platforms, and has an open source version with
+many external clients. New dependencies should be avoided. In particular, note
+that S2 does not currently depend on the following:
+
+ - GMock (except for a few files that not currently part of the open-source
+ release). Instead please limit yourself to the more basic features of GUnit.
+
+ - Protocol buffers.
+
+ - Any Google maps or geo library.
+
+ - Many features of Abseil, including `StatusOr` (see "Error Handling" below).
+
+## Filenames
+
+The S2 library has a flat directory structure. Generally each header file
+contains either a single class (plus any associated helper classes) or a
+collection of functions in a namespace.
+
+Filenames should be in lowercase with words separated by underscores. Note that
+"s2", "s1", etc. are not considered to be words. Examples:
+
+ - `s2builder.h`
+ - `s2cell_id.h`
+ - `encoded_s2shape_index.h`
+
+For historical reasons, `latlng` is treated as one word even though the
+corresponding classes are camel-cased. For example, `S2LatLngRect` is defined
+in `s2latlng_rect.h`.
+
+Files that define functions or classes within the `s2shapeutil` and
+`s2builderutil` namespaces include that namespace as a prefix:
+
+ - `s2shapeutil_conversion.h`
+ - `s2shapeutil_count_edges.h`
+ - `s2builderutil_lax_polyon_layer.h`
+ - `s2builderutil_snap_functions.h`
+
+Classes or functions that are needed in multiple places within the S2 library
+but are not part of the public API can be put in header files with the
+`_internal.h` suffix. Examples:
+
+ - `s2coords_internal.h`
+ - `s2predicates_internal.h`
+
+This can help remove clutter from the public API and also facilitate testing.
+Note that the internal content can be implemented in the same `.cc` file as the
+public part of the API.
+
+"Util" header files are strongly discouraged. Instead try to break your API
+into meaningful components. The only remaining "util"-style header file in the
+S2 library is `s2pointutil.h` which contains about ten functions for
+manipulating points.
+
+## Documentation
+
+### Comment Style
+
+Please put effort into your comments; they are the most important part of your
+code.
+
+Comments should be full sentences, starting with a capital letter and ending
+with a period. This rule applies even to partial-line comments when reasonable:
+
+```c++
+ enum IndexStatus {
+ STALE, // There are pending updates.
+ UPDATING, // Updates are currently being applied.
+ FRESH, // There are no pending updates.
+ };
+```
+
+For bonus points, put two spaces after your periods: contrary to popular belief,
+this is not some archaic convention from the days of typewriters, but rather an
+effort to mimic professional typography (take a close look at a book sometime).
+
+### Header Files
+
+Public classes and methods should be thoroughly documented in the header file.
+Important topics to cover include:
+
+ - Description of the problem being solved
+ - Code samples showing how to use the API
+ - Limitations and known problems
+
+Requirements of classes and methods may be documented via a "REQUIRES" comment.
+For example:
+
+```c++
+// Encodes an unsigned integer in little-endian format using "length" bytes.
+// (The client must ensure that the encoder's buffer is large enough.)
+//
+// REQUIRES: T is an unsigned integer type.
+// REQUIRES: 2 <= sizeof(T) <= 8
+// REQUIRES: 0 <= length <= sizeof(T)
+// REQUIRES: value < 256 ** length
+// REQUIRES: encoder->avail() >= length
+template
+void EncodeUintWithLength(T value, int length, Encoder* encoder);
+```
+
+The following callouts may also be useful:
+
+ - `ENSURES:` properties guaranteed by the class/method.
+ - `DEFAULT:` documents the default value of an option.
+ - `CAVEAT:` an unexpected property or potential pitfall.
+ - `NOTE:` to draw special attention to a comment.
+
+### Implementations
+
+Implementation details should be documented where the corresponding types or
+methods are defined, not where they are declared. This means that types and
+member variables declared in the header file should be documented there, whereas
+private non-inline methods should be documented in the .cc file. This
+convention makes it easier to find documentation when looking at code, and also
+makes it more likely that the documentation will be updated when implementations
+are changed.
+
+Here is an example of documenting a private method in a `.cc` file:
+
+```c++
+// 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;
+}
+```
+
+### External Documentation
+
+In addition to header file comments, significant new classes should have
+documentation added in the "g3doc" directory. Typically this will expand
+significantly on what is in the header file, giving more background information
+and examples. Documentation added here will be published on the `s2geometry.io`
+website.
+
+Ideally documentation should be added and/or updated in the same changelist that
+adds the relevant code.
+
+## Namespaces
+
+The S2 library as a whole is not defined within a namespace. (Namespaces did
+not even exist when it was first designed.) Instead most of the global symbols
+include `S2` in their names (except for a few classes that use `S1`, `R2`,
+etc. instead.)
+
+Nevertheless, some parts of the library do use namespaces. In general the names
+of namespaces should be in lowercase with words separated by underscores
+(e.g. `s2polyline_alignment`). Note that the "util" suffix is not considered a
+word, so we have:
+
+ - `namespace s2shapeutil { ... }`
+ - `namespace s2builderutil { ... }`
+
+There is also a namespace called `S2` that contains many of the low-level
+functions defined by the library (e.g. `S2::TurnAngle`, `S2::RobustCrossProd`,
+etc.) Note that while class names often include `S2` as a prefix rather
+than being placed in a namespace, this convention is not typically used for
+global functions or constants and so these entities should be put in a namespace
+instead.
+
+In general S2 does not use nested namespaces, except for internal content which
+may be placed in a nested namespace called `internal`. This technique can be
+used alone to protect internal content (e.g. the file `s2testing.h` defines some
+content in the `S2::internal` namespace) and/or with an `_internal.h` header
+file.
+
+## Using Declarations
+
+`using` declarations are used extensively in `.cc` files in order to reduce code
+verbosity. This includes types and functions from the `std` and `absl`
+namespaces as well as aliases for nested types. For example,
+`s2buffer_operation_test.cc` has the following declarations:
+
+```c++
+using absl::make_unique;
+using std::max;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+using EndCapStyle = S2BufferOperation::EndCapStyle;
+using PolylineSide = S2BufferOperation::PolylineSide;
+```
+
+Note that this is allowable only in `.cc` files, not header files, and that
+`using namespace` declarations are prohibited.
+
+## Classes
+
+Class names in S2 should be nouns with the first letter of each word
+capitalized. Examples:
+
+ - `S2Shape`
+ - `S2LatLngRect`
+ - `S1Angle`
+
+Short, descriptive names are highly preferred. It is reasonable to put as much
+thought into the names of your classes, methods, variables, etc. as you put into
+the code itself. Good names are essential to making APIs easier to understand.
+
+The names of subtypes should be obviously related to their parent class. For
+example, `S2LaxPolygonShape` is a subtype of `S2Shape`, and
+`s2builderutil::S2CellIdSnapFunction` is a subtype of `S2Builder::SnapFunction`.
+
+### Index, Query, Operation
+
+Classes in S2 should be designed so as to separate the data representation from
+the actual operations as much as possible.A class that contains many geometric
+objects for the purpose of performing operations on them should preferentially
+be called an `Index`. Such classes should define only the minimum number of
+functions necessary to access the data in the index. Examples:
+
+ - `S2ShapeIndex`
+ - `S2CellIndex`
+ - `S2PointIndex`
+
+Rather than having a single class with a wide API, prefer to have several
+classes or standalone functions with narrow APIs. For example, rather than
+having a method in `S2ShapeIndex` to count the total number of edges, instead
+there is a separate function to do this defined in `s2shapeutil_count_edges.h`.
+
+A class that does something useful with an index should preferentially be called
+a `Query`, unless it can be viewed as function that maps an index to another
+similar index in which case it may be called an `Operation`. Examples:
+
+ - `S2ClosestPointQuery`
+ - `S2ConvexHullQuery`
+ - `S2CrossingEdgeQuery`
+ - `S2BooleanOperation`
+ - `S2BufferOperation`
+
+`Index` classes should be thread-safe but `Query` and `Operation` classes
+generally are not. Instead the expectation is that each thread should create
+its own `Query` objects (typically on the stack) as needed. The fact that
+`Query` classes are not thread-safe allows them to maintain internal state while
+executing the query and/or to cache information between queries.
+
+`Query` and `Operation` classes typically follow one of two design patterns.
+One is the builder pattern, where data can be added incrementally using several
+method calls, and then a different method is called to compute the result.
+Examples following this pattern include `S2WindingOperation`,
+`S2BufferOperation`, `S2ConvexHullQuery`, etc.
+
+The other common pattern is a pure function pattern where the arguments are
+supplied and the result is returned during a single function call. Examples of
+this pattern include `S2ClosestEdgeQuery`, `S2ClosestPointQuery`,
+`S2ContainsPointQuery`, etc. Note that even though this pattern in theory
+allows the query method to be stateless and therefore thread-safe, S2 classes
+avoid this in favor of rather allowing query objects to maintain whatever state
+they may need in order to make queries as efficient as possible. This means
+that **query methods should not be const** even if the current implementation of
+the method would allow this, since this might prevent future optimizations.
+
+When query methods need to return several values of different types, the query
+method should return a result object. For example, `S2ClosestEdgeQuery` returns
+a `Result` object with methods such as `distance()`, `shape_id()`, and
+`edge_id()`.
+
+This technique is strongly preferred over the alternative technique of having
+the query object store the latest result in its own state and having methods to
+retrieve the result field-by-field. For example, a Result object makes it much
+easier to combine the results of several queries:
+
+```
+ auto a_result = query.GetResult(a);
+ auto b_result = query.GetResult(b);
+ ... do something with both results ...
+```
+
+Note that as long as the optimizer does its job correctly, returning a result by
+value does not involve any actual copying. Instead the caller passes a hidden
+pointer and the result is constructed directly on the caller's stack.
+
+### Options
+
+Classes with options should have a nested `Options` class. For
+example, `S2Builder` has an `S2Builder::Options` class. Options
+classes generally consist of a set of fields with "get" and "set" methods. For
+example:
+
+```c++
+class S2RegionCoverer {
+ public:
+ class Options {
+ public:
+ Options();
+
+ int max_cells() const;
+ void set_max_cells(int max_cells);
+ ...
+ };
+ explicit S2RegionCoverer(const Options& options);
+ const Options& options() const;
+ ...
+};
+```
+
+The set methods generally return `void`, but it is also acceptable to return a
+reference to the Options object in order to allow chained initialization:
+
+```
+ S2Foo foo{S2Foo::Options().set_bar(x).set_baz(y)};
+```
+
+Options are usually read-only once the relevant object has been constructed.
+Classes that allow options to be modified after construction do so via a
+`mutable_options()` method:
+
+```c++
+ Options* mutable_options();
+```
+
+Options classes are copyable and movable.
+
+## Methods
+
+Accessor method names are nouns, written in lowercase with words separated by
+underscores. Examples:
+
+ - `S1Angle::degrees()`
+ - `S2Builder::options()`
+
+Non-accessor method names should be verbs, written in uppercase with the first
+letter of each word capitalized. Examples:
+
+ - `S2Builder::StartLayer(...)`
+ - `S2Builder::Build(...)`
+ - `S2ShapeIndex::Minimize()`
+
+Methods that return something but that don't have an obvious verb should
+typically start with `Get`. For example:
+
+ - `S2ShapeIndex::GetShape(int id)`
+ - `S2::GetPointOnLine(...)`
+
+If the method may return an empty set of results, the verb `Find` can also be
+used. For example:
+
+ - `S2ClosestEdgeQuery::FindClosestEdges(...)`
+
+There are exceptions for well-known geometric operations that yield objects of
+the same type. For example, `S2Cap::Union` computes the union of two `S2Cap`
+objects. This naming convention is generally reserved for low-level geometric
+objects such as `S1Interval`, `S2LatLngRect`, `S2Cap`, etc.
+
+Methods that return boolean values (predicates) should be active verbs
+(e.g. `S2LatLngRect::Contains()`) or start with the word "is"
+(e.g. `S2Shape::is_empty()`, `S2::IsUnitLength()`). (Note that this convention
+differs from `std::vector::empty()` for example.)
+
+### Inline Methods
+
+Except for accessor methods, inline methods should be declared and defined
+separately (just like non-inline methods). Note that only the definition needs
+to be declared inline, not the declaration. Inline definitions should be placed
+at the bottom of the corresponding header file after a comment saying
+"Implementation details follow". For example:
+
+```c++
+class S2PaddedCell {
+ public:
+ // Construct an S2PaddedCell for the given cell id and padding.
+ S2PaddedCell(S2CellId id, double padding);
+
+ S2CellId id() const { return id_; }
+ double padding() const { return padding_; }
+ int level() const { return level_; }
+
+ // Return the bound for this cell (including padding).
+ const R2Rect& bound() const { return bound_; }
+
+ // Return the (i,j) coordinates for the child cell at the given traversal
+ // position. The traversal position corresponds to the order in which child
+ // cells are visited by the Hilbert curve.
+ void GetChildIJ(int pos, int* i, int* j) const;
+
+ ...
+};
+
+
+////////////////// Implementation details follow ////////////////////
+
+
+inline void S2PaddedCell::GetChildIJ(int pos, int* i, int* j) const {
+ int ij = S2::internal::kPosToIJ[orientation_][pos];
+ *i = ij >> 1;
+ *j = ij & 1;
+}
+```
+
+### Constructors, Init, Factory Methods
+
+Virtually all classes should have constructors. If the constructor parameters
+may be ambiguous, static factory methods should be used instead. For example,
+`S2LatLng` has the following factory methods:
+
+```c++
+ static constexpr S2LatLng FromRadians(double lat_radians, double lng_radians);
+ static constexpr S2LatLng FromDegrees(double lat_degrees, double lng_degrees);
+ static constexpr S2LatLng FromE5(int32_t lat_e5, int32_t lng_e5);
+ static constexpr S2LatLng FromE6(int32_t lat_e6, int32_t lng_e6);
+ static constexpr S2LatLng FromE7(int32_t lat_e7, int32_t lnt_e7);
+```
+
+Factory method names should begin with "From", except for simple and frequently
+used classes where brevity takes precedence. For example, `S1Angle` has the
+factory methods `S1Angle::Radians(double radians)` and `S1Angle::Degrees(double
+degrees)`.
+
+Factory methods should also be used when the parameter order may be ambiguous.
+Examples:
+
+```c++
+ static R2Rect FromCenterSize(const R2Point& center, const R2Point& size);
+ static S2CellId FromFacePosLevel(int face, uint64 pos, int level);
+```
+
+Complex classes that allocate memory should also have a default constructor and
+an `Init` method. This allows clients to declare instances of these classes as
+member variables and defer their initialization as necessary. This allows
+clients to avoid allocating objects on the heap and accessing them through
+`unique_ptr<>` just because the arguments needed to initialize them are not
+readily available in their own constructor. This not only reduces memory
+allocation but also increases locality of reference.
+
+So for example, `S2Builder` has the following:
+
+```c++
+class S2Builder {
+ ...
+ // Default constructor; requires Init() to be called.
+ S2Builder();
+
+ // Convenience constructor that calls Init().
+ explicit S2Builder(const Options& options);
+
+ // Initializes an S2Builder with the given options.
+ void Init(const Options& options);
+
+ const Options& options() const { return options_; }
+ ...
+};
+```
+
+Factory methods that return `unique_ptr` should be avoided, since this
+forces clients to allocate memory and also reduces locality of reference.
+
+## Types
+
+### Templates
+
+Templated types and functions are discouraged in the public parts of the API.
+Templated code is necessarily harder to document and understand, and the
+generalization that it offers is frequently overrated and underused.
+
+Templates may be used internally when this substantially reduces code
+duplication or otherwise makes the library easier to maintain. For example, the
+S2 classes `S2ClosestEdgeQuery` and `S2FurtherEdgeQuery` are each wrappers
+around a templated internal class called `S2ClosestEdgeQueryBase`.
+
+### Distances and Angles
+
+Distances and angles should be represented using the classses `S1Angle` or
+`S1ChordAngle`. S2 APIs should always use these classes in preference to
+accepting distances in radians, degrees, meters, etc. since it avoids the need
+to specify units. (Note that the S2 library operates on the unit sphere, not
+the Earth's surface, and therefore the natural unit of distance is the radian.)
+
+Whenever possible, classes should support both `S1Angle` and `S1ChordAngle`.
+`S1ChordAngle` is frequently used internally, since (1) it is much faster to
+compute and (2) it supports the exact distance predicates that are needed to
+implement algorithms robustly (see `s2predicates.h`). Methods that accept an
+`S1Angle` often convert it to `S1ChordAngle` internally.
+
+Note that `S1ChordAngle` is only capable of representing distances up to 180
+degrees, and that its accuracy declines for distances approaching 180 degrees.
+Distances which may be greater than 180 degrees, such as polyline lengths or
+polygon perimeters, should always be represented using `S1Angle`.
+
+### Parameters
+
+Small types (16 bytes or less) should generally be passed by value. This
+includes the following commonly used S2 types:
+
+ - `S2CellId`
+ - `S2LatLng`
+ - `S1Angle`
+ - `S1ChordAngle`
+
+Types larger than 16 bytes should be passed as follows:
+
+ - If ownership is being transferred, use `unique_ptr`.
+
+ - If the callee keeps a pointer to the object that outlives the function call,
+ use `const T*`. For example, `S2ClosestEdgeQuery` keeps a pointer to its
+ `S2ShapeIndex` constructor argument, so it accepts that argument by const
+ pointer:
+
+ ```c++
+ explicit S2ClosestEdgeQuery(const S2ShapeIndex* index,
+ const Options& options = Options());
+ ```
+
+ - Otherwise, the parameter should be passed by const reference. Note in
+ particular that `S2Point` is passed by const reference. Const pointers
+ should not be used unless the callee keeps a pointer as outlined above.
+
+ - The only exception to the rule above is when the callee needs to make a copy
+ of the argument **and*** the given type has an efficient move operator (i.e.,
+ one that transfers ownership of data rather than simply copying it). In this
+ case the parameter may be passed by value rather than by const reference.
+ For example:
+
+ ```c++
+ class S2CellUnion final : public S2Region {
+ ...
+
+ // Constructs a cell union with the given S2CellIds, then calls Normalize()
+ // to sort them, remove duplicates, and merge cells when possible.
+ //
+ // The argument is passed by value, so if you are passing a named variable
+ // and have no further use for it, consider using std::move().
+ //
+ // A cell union containing a single S2CellId may be constructed like this:
+ //
+ // S2CellUnion example({cell_id});
+ explicit S2CellUnion(std::vector cell_ids);
+ ...
+ }
+
+ inline S2CellUnion::S2CellUnion(std::vector cell_ids)
+ : cell_ids_(std::move(cell_ids)) {
+ Normalize();
+ }
+ ```
+
+ Note that this guidance is considerably more strict than advocated by the
+ current Google style guide. Do not pass parameters larger than 16 bytes
+ unless (1) the type has an efficient `std::move` operator and (2) the callee
+ makes a copy of the argument using `std::move`.
+
+#### Const Value Parameters
+
+Parameters passed by value should never be "const" in method declarations. They
+should only be declared const (if desired) in method **definitions**. For
+example:
+
+```c++
+class S2CellId {
+ ...
+ // Return true if the given cell is contained within this one.
+ bool contains(S2CellId other) const;
+ ...
+};
+
+
+////////////////// Implementation details follow ////////////////////
+
+
+inline bool S2CellId::contains(const S2CellId other) const {
+ DCHECK(is_valid());
+ DCHECK(other.is_valid());
+ return other >= range_min() && other <= range_max();
+}
+```
+
+Note that most S2 code does not bother declaring local variables and parameters
+passed by value as "const" even when those values are not modified, since the
+additional documentation benefit often does not outweigh the additional code
+clutter.
+
+### Geometry Output
+
+Classes that construct geometry and return it to the client should do so via an
+`S2Builder::Layer` parameter. For example, the `S2BufferOperation` constructor
+looks like this:
+
+```c++
+ explicit S2BufferOperation(std::unique_ptr result_layer,
+ const Options& options = Options{});
+```
+
+When the client eventually calls the `S2BufferOperation::Build()` method, the
+output geometry is sent to the given `result_layer`. This allows the client to
+have complete control over how the output geometry is represented. There are
+various possible layer types that build different representations such as
+`S2Polyon` or `S2LaxPolygonShape`.
+
+### Obsolete Types
+
+Clients are discouraged from using certain S2 classes for new code. Classes
+that have officially been deprecated are marked as such using the
+`ABSL_DEPRECATED` macro. Currently the only such class is `S2PolygonBuilder`
+which has been superseded by the far more robust and flexible `S2Builder`.
+
+However it is important to note that the classes `S2Polygon` and `S2Polyline`
+are also obsolete and should ideally be deprecated at some point in the future.
+New code should prefer to use their `S2Shape`-based replacements,
+`S2LaxPolygonShape` and `S2LaxPolylineShape`. These new classes can be
+constructed and decoded far more efficiently than `S2Polygon` and `S2Polyline`,
+and are also capable of representing degenerate geometry if desired (which is
+what the `Lax` in their names refers to).
+
+## Error Handling
+
+In general S2 code should try to avoid generating runtime errors.
+
+### Parameter Range Checking
+
+If a parameter has a limited valid range, the preferred technique in S2 is to
+`DCHECK` that the parameter is valid and then clamp it to the
+allowed range. For example:
+
+```c++
+ class S2BufferOperation::Options {
+ ...
+ // Specifies the allowable error when buffering, expressed as a fraction
+ // of buffer_radius().
+ //
+ // 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);
+ ...
+ };
+
+void S2BufferOperation::Options::set_error_fraction(double error_fraction) {
+ DCHECK_GE(error_fraction, kMinErrorFraction);
+ DCHECK_LE(error_fraction, 1.0);
+ error_fraction_ = max(kMinErrorFraction, min(1.0, error_fraction));
+}
+```
+
+### Invariants
+
+Internal invariants should be enforced via `DCHECK`. This is an important
+aspect of both documentation and testing. For example, the following is a
+private method that should only be called when certain conditions are true:
+
+```c++
+void S2BufferOperation::BufferEdgeAndVertex(const S2Point& a, const S2Point& b,
+ const S2Point& c) {
+ DCHECK_NE(a, b);
+ DCHECK_NE(b, c);
+ DCHECK_NE(buffer_sign_, 0);
+ if (!tracker_.ok()) return;
+ ...
+}
+```
+
+Programming errors within the S2 library should not be converted to runtime
+errors if at all possible.
+
+
+### S2Error
+
+For historical reasons, S2 defines its own error class `S2Error` consisting of
+an error code and a status message. This class is used exclusively within the
+library in preference to other error representations such as `absl::Status`.
+While it may be desirable to eventually convert the library to use a different
+error representation, the overriding principle is to maintain consistency within
+the library so that users (including external users of the open source version)
+do not need to deal with two different error representations at once.
+
+Here is a partial definition of the `S2Error` class:
+
+```c++
+class S2Error {
+ public:
+ enum Code {
+ OK = 0, // No error.
+
+ ////////////////////////////////////////////////////////////////////
+ // Generic errors, not specific to geometric objects:
+
+ UNKNOWN = 1000, // Unknown error.
+ UNIMPLEMENTED = 1001, // Operation is not implemented.
+ OUT_OF_RANGE = 1002, // Argument is out of range.
+ INVALID_ARGUMENT = 1003, // Invalid argument (other than a range error).
+ FAILED_PRECONDITION = 1004, // Object is not in the required state.
+ INTERNAL = 1005, // An internal invariant has failed.
+ DATA_LOSS = 1006, // Data loss or corruption.
+ RESOURCE_EXHAUSTED = 1007, // A resource has been exhausted.
+ CANCELLED = 1008, // Operation was cancelled.
+
+ ...
+ };
+ S2Error() : code_(OK), text_() {}
+
+ // Convenience constructor that calls Init().
+ template
+ S2Error(Code code, const absl::FormatSpec& format,
+ const Args&... args);
+ }
+
+ // Set the error to the given code and printf-style message. Note that you
+ // can prepend text to an existing error by calling Init() more than once:
+ //
+ // error->Init(error->code(), "Loop %d: %s", j, error->text().c_str());
+ template
+ void Init(Code code, const absl::FormatSpec& format,
+ const Args&... args);
+
+ bool ok() const { return code_ == OK; }
+ Code code() const { return code_; }
+ std::string text() const { return text_; }
+
+ // Clear the error to contain the OK code and no error message.
+ void Clear();
+
+ private:
+ Code code_;
+ std::string text_;
+};
+```
+
+Errors are typically initialized using `S2Error::Init()`, although they can also
+be constructed and copied. For example:
+
+```c++
+ error->Init(S2Error::RESOURCE_EXHAUSTED,
+ "Memory limit exceeded (tracked usage %d bytes, limit %d bytes)",
+ usage_bytes_, limit_bytes_);
+```
+
+`S2Error` is typically passed as a pointer parameter and is required to be
+non-`nullptr`. (Note that the current Google style recommendation is to pass such
+parameters by reference, which precludes `nullptr` arguments at the expense of
+giving up the visible documentation hint that the argument may be modified.)
+Public methods typically call `error->Clear()` to remove any existing error,
+whereas private methods either set the error or leave its state unchanged.
+
+Methods that would otherwise return `void` often return `error->ok()` instead in
+order to simplify client code. For example:
+
+```c++
+class S2BooleanOperation {
+ ...
+ // Executes the given operation. Returns true on success, and otherwise
+ // sets "error" appropriately. (This class does not generate any errors
+ // itself, but the S2Builder::Layer might.)
+ bool Build(const S2ShapeIndex& a, const S2ShapeIndex& b, S2Error* error);
+}
+```
+
+Client code might then look like this:
+
+```c++
+ bool DoSomething(..., S2Error* error) {
+ ...
+ if (!op.Build(a, b, error)) return false;
+ ...
+ }
+```
+
+If a function has a void or other non-boolean result, the caller must check
+`error->ok()` explicitly:
+
+```c++
+ void DoSomething(S2Builder::Layer* layer, const S2Builder::Graph& graph,
+ S2Error* error) {
+ layer->Build(graph, error);
+ if (!error->ok()) return;
+ ...
+ }
+```
+
+## Testing
+
+Tests should attempt to achieve excellent coverage with a minimum of code. This
+will usually mean writing helper functions and/or test fixtures in order to
+avoid code duplication. Do not be afraid to write test code with loops,
+multiple levels of helpers, etc., if this will result in better testing with
+less code duplication.
+
+S2 tests should always be run **in a debug build** (not the default "fast
+build") since this will give full line number information on all your stack
+traces. In other words, build S2 with `-c dbg` when you are testing new code.
+
+Non-optimized builds also enable various internal consistency checks. This
+includes not only the `DCHECK` macros but also the `--s2debug` flag which
+enables certain internal validity checking. (This flag can also be enabled by
+hand if you are suspect a problem with geometry that is too large or slow to
+process using a debug build.)
+
+Tests should focus on the functionality provided by your class, not the
+functionality of the classes that your class depends on. For example, if your
+class uses `S2Builder` then you should not write tests whose purpose is simply
+to verify the guarantees that `S2Builder` makes (which are thoroughly tested
+elsewhere).
+
+### Allow For Errors
+
+Tests should generally allow for small amounts of error in their results rather
+than expecting an exact match. This helps to make tests less fragile, avoiding
+the need to update them whenever small changes to the underlying algorithms are
+made.
+
+Useful Gunit helper functions include `EXPECT_DOUBLE_EQ` and `EXPECT_NEAR`.
+It can also be useful to snap results to a fixed resolution using
+`s2builderutil::IntLatLngSnapFunction()`.
+
+"Golden" files (which compare the results of a series of operations to a
+"golden" result deemed to be correct) are strictly prohibited.` Such tests are
+inherently fragile and will break whenever the smallest details of S2 algorithms
+are changed.
+
+### S2Testing
+
+The header file `s2testing.h` provides many utility functions and classes that
+are useful when writing tests. In addition the distance-related functions
+mentioned above, there is extensive support for generating random geometry of
+various kinds.
+
+Support code for writing tests can also be found in the following files:
+
+ - `thread_testing.h`
+ - `s2shapeutil_testing.h`
+ - `s2closest_edge_query_testing.h`
+
+### S2Earth
+
+S2 tests are not allowed to depend on `s2earth.h` (which provides basic
+functionality for converting distances on the Earth's surface to S2-compatible
+types). Instead tests should use the even more basic functionality provided in
+`s2testing.h`. For example:
+
+```c++
+ S1Angle tolerance = S2Testing::MetersToAngle(1);
+```
+
+### Randomization and Robustness
+
+S2 makes heavy use of randomized tests. This is particularly useful as a way to
+test robustness guarantees. Typically, a specialized function is written to
+generate test cases that are very likely to trigger problems, and then a large
+number of cases are checked for correctness. Here is an example:
+
+```c++
+// Chooses a random S2Point that is often near the intersection of one of the
+// coodinates planes or coordinate axes with the unit sphere. (It is possible
+// to represent very small perturbations near such points.)
+S2Point ChoosePoint() {
+ S2Point x = S2Testing::RandomPoint();
+ for (int i = 0; i < 3; ++i) {
+ if (S2Testing::rnd.OneIn(3)) {
+ x[i] *= pow(1e-50, S2Testing::rnd.RandDouble());
+ }
+ }
+ return x.Normalize();
+}
+
+TEST(S2, ProjectError) {
+ for (int iter = 0; iter < 1000; ++iter) {
+ SCOPED_TRACE(StrCat("Iteration ", iter));
+ S2Testing::rnd.Reset(iter); // Easier to reproduce a specific case.
+ S2Point a = ChoosePoint();
+ S2Point b = ChoosePoint();
+ S2Point n = S2::RobustCrossProd(a, b).Normalize();
+ S2Point x = S2Testing::SamplePoint(S2Cap(n, S1Angle::Radians(1e-15)));
+ S2Point p = S2::Project(x, a, b);
+ EXPECT_LT(s2pred::CompareEdgeDistance(
+ p, a, b, S1ChordAngle(S2::kProjectPerpendicularError)), 0);
+ }
+}
+```
+
+Note the use of `SCOPED_TRACE` so that any failing iterations can be identified.
+This is obviously only useful if the random number seed is deterministic, which
+is one reason that `S2Testing` defines its own random number generator available
+as `S2Testing::rnd`. Tests **should not** use random number generators with
+non-deterministic seeds, since this makes problems impossible to debug.
+
+Also note the use of `S2Testing::rnd.Reset(iter)` which allows a specific
+failing test case to be reproduced more easily by simply replacing 'iter' by the
+failing iteration number. (Note that most S2 code actually calls
+`rnd.Reset(iter + 1)` because it turns out that `rnd.Reset(0)` and
+`rnd.Reset(1)` have the same effect.)
+
+Observe the use of `S2Testing` helper methods such as `RandomPoint` and
+`SamplePoint`. Also note the use of `pow(10**x, S2Testing::rnd.RandDouble())`,
+which produces a value whose *exponent* is uniformly distributed between "x"
+and 0. This is a useful technique for generating numbers that are
+well-distributed over a wide range of magnitudes.
diff --git a/docs/devguide/cpp/examples/CMakeLists.txt b/docs/devguide/cpp/examples/CMakeLists.txt
new file mode 100644
index 00000000..d5858e01
--- /dev/null
+++ b/docs/devguide/cpp/examples/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_executable(point_index point_index.cc)
+target_link_libraries(point_index LINK_PUBLIC s2testing s2)
+add_executable(term_index term_index.cc)
+target_link_libraries(term_index LINK_PUBLIC s2testing s2)
diff --git a/docs/devguide/cpp/examples/point_index.cc b/docs/devguide/cpp/examples/point_index.cc
new file mode 100644
index 00000000..b924055c
--- /dev/null
+++ b/docs/devguide/cpp/examples/point_index.cc
@@ -0,0 +1,56 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+// Author: ericv@google.com (Eric Veach)
+//
+// This example shows how to build and query an in-memory index of points
+// using S2PointIndex.
+
+#include
+#include
+#include
+
+#include "base/commandlineflags.h"
+#include "base/init_google.h" // MOE:strip_line
+#include "geostore/geomodel/public/s2earth.h"
+#include "third_party/absl/flags/flag.h"
+#include "util/geometry/s1chord_angle.h"
+#include "util/geometry/s2closest_point_query.h"
+#include "util/geometry/s2point_index.h"
+#include "util/geometry/s2testing.h"
+
+DEFINE_int32(num_index_points, 10000, "Number of points to index");
+DEFINE_int32(num_queries, 10000, "Number of queries");
+DEFINE_double(query_radius_km, 100, "Query radius in kilometers");
+
+// MOE:begin_strip
+using geostore::S2Earth;
+
+// MOE:end_strip
+int main(int argc, char **argv) {
+ // MOE:begin_strip
+ // TODO(jrosenstock,b/209396162): We should call
+ // gflags::ParseCommandLineFlags here, but that would break WITH_FLAGS=off.
+ InitGoogle(argv[0], &argc, &argv, true);
+ // MOE:end_strip
+ // Build an index containing random points anywhere on the Earth.
+ S2PointIndex index;
+ for (int i = 0; i < absl::GetFlag(FLAGS_num_index_points); ++i) {
+ index.Add(S2Testing::RandomPoint(), i);
+ }
+
+ // Create a query to search within the given radius of a target point.
+ S2ClosestPointQuery query(&index);
+ query.mutable_options()->set_max_distance(S1Angle::Radians(
+ S2Earth::KmToRadians(absl::GetFlag(FLAGS_query_radius_km))));
+
+ // Repeatedly choose a random target point, and count how many index points
+ // are within the given radius of that point.
+ int64_t num_found = 0;
+ for (int i = 0; i < absl::GetFlag(FLAGS_num_queries); ++i) {
+ S2ClosestPointQuery::PointTarget target(S2Testing::RandomPoint());
+ num_found += query.FindClosestPoints(&target).size();
+ }
+
+ std::printf("Found %" PRId64 " points in %d queries\n", num_found,
+ absl::GetFlag(FLAGS_num_queries));
+ return 0;
+}
diff --git a/docs/devguide/cpp/examples/term_index.cc b/docs/devguide/cpp/examples/term_index.cc
new file mode 100644
index 00000000..d71a1226
--- /dev/null
+++ b/docs/devguide/cpp/examples/term_index.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+// Author: ericv@google.com (Eric Veach)
+//
+// This example shows how to add spatial data to an information retrieval
+// system. Such systems work by converting documents into a collection of
+// "index terms" (e.g., representing words or phrases), and then building an
+// "inverted index" that maps each term to a list of documents (and document
+// positions) where that term occurs.
+//
+// This example shows how to convert spatial data into index terms, which can
+// then be indexed along with the other document information.
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "base/commandlineflags.h"
+#include "base/init_google.h" // MOE:strip_line
+#include "geostore/geomodel/public/s2earth.h"
+#include "third_party/absl/container/btree_set.h"
+#include "third_party/absl/container/flat_hash_map.h"
+#include "third_party/absl/flags/flag.h"
+#include "util/geometry/s2cap.h"
+#include "util/geometry/s2point_index.h"
+#include "util/geometry/s2region_term_indexer.h"
+#include "util/geometry/s2testing.h"
+
+DEFINE_int32(num_documents, 10000, "Number of documents");
+DEFINE_int32(num_queries, 10000, "Number of queries");
+DEFINE_double(query_radius_km, 100, "Query radius in kilometers");
+
+// MOE:begin_strip
+using geostore::S2Earth;
+
+// MOE:end_strip
+// A prefix added to spatial terms to distinguish them from other index terms
+// (e.g. representing words or phrases).
+static const char kPrefix[] = "s2:";
+
+int main(int argc, char** argv) {
+ // MOE:begin_strip
+ // TODO(jrosenstock,b/209396162): We should call
+ // gflags::ParseCommandLineFlags here, but that would break WITH_FLAGS=off.
+ InitGoogle(argv[0], &argc, &argv, true);
+ // MOE:end_strip
+ // Create a set of "documents" to be indexed. Each document consists of a
+ // single point. (You can easily substitute any S2Region type here, or even
+ // index a mixture of region types using std::unique_ptr. Other
+ // region types include polygons, polylines, rectangles, discs, buffered
+ // geometry, etc.)
+ std::vector documents;
+ documents.reserve(absl::GetFlag(FLAGS_num_documents));
+ for (int docid = 0; docid < absl::GetFlag(FLAGS_num_documents); ++docid) {
+ documents.push_back(S2Testing::RandomPoint());
+ }
+
+ // We use a hash map as our inverted index. The key is an index term, and
+ // the value is the set of "document ids" where this index term is present.
+ absl::flat_hash_map> index;
+
+ // Create an indexer suitable for an index that contains points only.
+ // (You may also want to adjust min_level() or max_level() if you plan
+ // on querying very large or very small regions.)
+ S2RegionTermIndexer::Options options;
+ options.set_index_contains_points_only(true);
+ S2RegionTermIndexer indexer(options);
+
+ // Add the documents to the index.
+ for (int docid = 0; docid < documents.size(); ++docid) {
+ S2Point index_region = documents[docid];
+ for (const auto& term : indexer.GetIndexTerms(index_region, kPrefix)) {
+ index[term].push_back(docid);
+ }
+ }
+
+ // Convert the query radius to an angle representation.
+ S1Angle radius = S1Angle::Radians(
+ S2Earth::KmToRadians(absl::GetFlag(FLAGS_query_radius_km)));
+
+ // Count the number of documents (points) found in all queries.
+ int64_t num_found = 0;
+ for (int i = 0; i < absl::GetFlag(FLAGS_num_queries); ++i) {
+ // Choose a random center for query.
+ S2Cap query_region(S2Testing::RandomPoint(), radius);
+
+ // Convert the query region to a set of terms, and compute the union of
+ // the document ids associated with those terms. (An actual information
+ // retrieval system would do something more sophisticated.)
+ absl::btree_set candidates;
+ for (const auto& term : indexer.GetQueryTerms(query_region, kPrefix)) {
+ candidates.insert(index[term].begin(), index[term].end());
+ }
+
+ // "candidates" now contains all documents that intersect the query
+ // region, along with some documents that nearly intersect it. We can
+ // prune the results by retrieving the original "document" and checking
+ // the distance more precisely.
+ std::vector result;
+ for (int docid : candidates) {
+ if (!query_region.Contains(documents[docid])) continue;
+ result.push_back(docid);
+ }
+ // Now do something with the results (in this example we just count them).
+ num_found += result.size();
+ }
+ std::printf("Found %" PRId64 " points in %d queries\n", num_found,
+ absl::GetFlag(FLAGS_num_queries));
+ return 0;
+}
diff --git a/docs/devguide/cpp/quickstart.md b/docs/devguide/cpp/quickstart.md
new file mode 100644
index 00000000..1bea5896
--- /dev/null
+++ b/docs/devguide/cpp/quickstart.md
@@ -0,0 +1,360 @@
+---
+title: Quick Start
+---
+
+Suppose that we have a few terabytes (or petabytes) of geographic data, and we
+want to be able to query it efficiently. This brief tutorial shows how the S2
+library is useful for solving this problem.
+
+## Prerequisites
+
+Before going through this Quickstart, first makes sure your development
+environment satisfies our
+[platform requirements](/about/platforms) and
+follow the installation instructions in the
+[S2 Install Guide](/about/install).
+
+## S2PointIndex
+
+To keep things simple, let's suppose that our input data consists only of points
+(rather than polylines, polygons, etc), and furthermore let's suppose that we
+have only a few million points, so that they all fit in memory. (We will look
+at the more general case below.)
+
+The easiest way to build an in-memory point index is to use an `S2PointIndex`,
+as follows:
+
+ // Build an index containing random points anywhere on the Earth.
+ S2PointIndex index;
+ for (int i = 0; i < FLAGS_num_index_points; ++i) {
+ S2Point point = S2Testing::RandomPoint();
+ index.Add(point, i);
+ }
+
+This example uses `S2Testing::RandomPoint()` to generate points that are
+randomly distributed over the Earth. Note that in the S2 library, [points are
+represented as unit-length
+vectors](/about/overview#unit-vectors-vs-latitudelongitude-pairs)
+(the [`S2Point`](/devguide/basic_types#s2point) class),
+rather than as latitude-longitude pairs. You can convert latitude-longitude
+pairs to `S2Point` values using the
+[`S2LatLng`](/devguide/basic_types#s2latlng) class, like
+this:
+
+ S2Point point(S2LatLng::FromDegrees(lat_degrees, lng_degrees));
+
+Also notice that each point in the index has some auxiliary data associated
+with it (in this case, the integer `i`). You can use this feature to map the
+points back to other data structures.
+
+## S2ClosestPointQuery
+
+Now that we have our index, how do we query it? For example, suppose that we
+want to find all the points within 100km of a target point. This can be done
+using `S2ClosestPointQuery`, which provides methods that find the closest
+point(s) to a given target object (such as a point, polyline, or polygon).
+You can either find the *k* closest points to the target, or all points within
+a given radius of the target, or both.
+
+In this example, we will find all points within 100km of a point target.
+First we construct the `S2ClosestPointQuery` object:
+
+ S2ClosestPointQuery query(&index);
+ query.mutable_options()->set_max_distance(
+ S1Angle::Radians(S2Earth::KmToRadians(FLAGS_query_radius_km)));
+
+Notice that we need to convert the radius in kilometers to an `S1Angle`. This
+is because S2 models the Earth as *unit sphere* (a sphere with radius 1),
+which means that the distance between two points is the same as the angle
+between those points (in radians) measured from the sphere's center. S2
+provides helper function for converting distances to angles, such as the
+`S2Earth::KmToRadians` function used above. (Note that because the Earth is
+not quite spherical, distances computed in this way can have errors of up to
+0.56%. You can use a geodesy library to compute distances more accurately,
+but spherical distances are perfectly adequate for many purposes, such as
+finding nearby restaurants.)
+
+Finally, we can find the points within the given radius of our target point
+like this:
+
+ S2ClosestPointQuery::PointTarget target(S2Testing::RandomPoint());
+ auto results = query.FindClosestPoints(&target);
+
+This returns a vector of `Result` objects, where each result includes the
+following fields:
+
+ S2Point point() const; // The indexed point.
+ Data data() const; // The auxiliary data for this point.
+ S1ChordAngle distance() const; // The distance to this point.
+
+In our case `data()` is the integer value that we passed to `index.Add()`
+above.
+
+Putting this all together, our program for indexing and querying points looks
+like this (see `examples/point_index.cc`):
+
+```c++
+ // Build an index containing random points anywhere on the Earth.
+ S2PointIndex index;
+ for (int i = 0; i < FLAGS_num_index_points; ++i) {
+ index.Add(S2Testing::RandomPoint(), i);
+ }
+
+ // Create a query to search within the given radius of a target point.
+ S2ClosestPointQuery query(&index);
+ query.mutable_options()->set_max_distance(
+ S1Angle::Radians(S2Earth::KmToRadians(FLAGS_query_radius_km)));
+
+ // Repeatedly choose a random target point, and count how many index points
+ // are within the given radius of that point.
+ int64 num_found = 0;
+ for (int i = 0; i < FLAGS_num_queries; ++i) {
+ S2ClosestPointQuery::PointTarget target(S2Testing::RandomPoint());
+ num_found += query.FindClosestPoints(&target).size();
+ }
+
+ printf("Found %lld points in %d queries\n", num_found, FLAGS_num_queries);
+```
+
+## Indexing for Search
+
+Let's suppose again that we have a lot of geographic data, and we want to
+query it efficiently along with other attributes (such as names and
+descriptions) using an *information retrieval system*. Such systems index a
+collection of *documents*, where each document consists of text and other
+attributes. Each document is converted into a collection of *index terms*
+(e.g., significant words or phrases), which are gathered into an *inverted
+index* that maps each term to a list of documents where that term occurs.
+(For a comprehensive introduction to information retrieval, see [Introduction
+to Information Retrieval](https://nlp.stanford.edu/IR-book/){:target="_blank"}
+by Christopher D. Manning et al., Cambridge University Press, 2008.)
+
+Our goal is not to solve the information retrieval problem in this tutorial,
+but rather just to show how to add spatial data to an existing system. S2
+defines a special class called `S2RegionTermIndexer` that is designed to deal
+with the problem of converting spatial data into index terms, which can then
+be indexed along with the other document information. (In fact the method
+described here can be used with any key-value storage system.)
+
+To keep things simple, let's assume that each document consists of a single
+point (with no other information). Each document is assigned a "document id"
+that can be used to retrieve it later:
+
+ std::vector documents;
+ for (int docid = 0; docid < FLAGS_num_documents; ++docid) {
+ documents.push_back(S2Testing::RandomPoint());
+ }
+
+As a substitute for the "inverted index" of an information retrieval system,
+we will use a hash map. The key of the hash map is an index term, and the
+value is the set of document ids where this term is present:
+
+ std::unordered_map> index;
+
+We can now use `S2RegionTermIndexer` to index these documents as follows:
+
+ // Create an indexer with appropriate options.
+ S2RegionTermIndexer::Options options;
+ S2RegionTermIndexer indexer(options);
+
+ // Add the documents to the index.
+ static const char kPrefix[] = "s2:";
+ for (int docid = 0; docid < documents.size(); ++docid) {
+ S2Point index_region = documents[docid];
+ for (const auto& term : indexer.GetIndexTerms(index_region, kPrefix)) {
+ index[term].push_back(docid);
+ }
+ }
+
+For each document, the indexer returns a set of terms that should be
+associated with the document for later retrieval. Each term is simply an
+alphanumeric string. To distinguish these spatial indexing terms from other
+index terms (such as words), the interface allows a prefix to be added (in
+this case "s2:"). For example, an index term might look like "s2:34f14a9".
+
+## Querying for Search
+
+Let's now examine how to query our inverted index. Again, for simplicity
+let's assume that we want to retrieve all documents that are within a given
+radius of a test point. As before, the first step is to convert the query
+radius to an `S1Angle`:
+
+ S1Angle radius =
+ S1Angle::Radians(S2Earth::KmToRadians(FLAGS_query_radius_km));
+
+Our query region is a disc centered around a target point. On the sphere, a
+disc-shaped region such as this one is called a *spherical cap*
+([`S2Cap`](/devguide/basic_types#s2cap)):
+
+ S2Cap query_region(S2Testing::RandomPoint(), radius);
+
+Now we convert the query region to a set of *query terms*. The query terms
+are constructed such that if the query region intersects the document region,
+then the query terms and index terms are guaranteed to have at least one value
+in common. This means that to find the set of intersecting documents, we
+simply need to look up the documents associated with each query term and
+compute their union. (Actual information retrieval systems do something more
+sophisticated, but that doesn't concern us here.)
+
+ std::set candidates;
+ for (const auto& term : indexer.GetQueryTerms(query_region, kPrefix)) {
+ candidates.insert(index[term].begin(), index[term].end());
+ }
+
+"candidates" now contains all documents that intersect the query region, along
+with some documents that nearly intersect it. At this point, an information
+retrieval system would "score" documents by examining their content more
+closely. In our case, for example, we might want to filter the candidates by
+retrieving the original "document" and checking the distance to the target
+more precisely. We can do this as follows:
+
+ std::vector result;
+ for (int docid : candidates) {
+ if (!query_region.Contains(documents[docid])) continue;
+ result.push_back(docid);
+ }
+
+Now the `results` vector contains exactly the set of input documents that
+intersect the query region.
+
+## Optimization for Points
+
+One final detail: when a spatial index contains only points, like this one, it
+turns out that fewer query terms are needed than when the index also contains
+non-point regions (such as polylines or polygons). You can take advantage of
+this optimization by modifying the code above as follows:
+
+ S2RegionTermIndexer::Options options;
+ options.set_index_contains_points_only(true);
+ S2RegionTermIndexer indexer(options);
+
+This reduces the number of query terms by approximately a factor of two, but
+can only be used when an index contains only points.
+
+The complete example program looks like this:
+
+```c++
+ // Create a set of "documents" to be indexed. Each document consists of a
+ // single point.
+ std::vector documents;
+ for (int docid = 0; docid < FLAGS_num_documents; ++docid) {
+ documents.push_back(S2Testing::RandomPoint());
+ }
+
+ // We use a hash map as our inverted index. The key is an index term, and
+ // the value is the set of "document ids" where this index term is present.
+ std::unordered_map> index;
+
+ // Create an indexer suitable for an index that contains points only.
+ S2RegionTermIndexer::Options options;
+ options.set_index_contains_points_only(true);
+ S2RegionTermIndexer indexer(options);
+ static const char kPrefix[] = "s2:";
+
+ // Add the documents to the index.
+ for (int docid = 0; docid < documents.size(); ++docid) {
+ S2Point index_region = documents[docid];
+ for (const auto& term : indexer.GetIndexTerms(index_region, kPrefix)) {
+ index[term].push_back(docid);
+ }
+ }
+
+ // Convert the query radius to an angle representation.
+ S1Angle radius =
+ S1Angle::Radians(S2Earth::KmToRadians(FLAGS_query_radius_km));
+
+ // Count the number of documents (points) found in all queries.
+ int64 num_found = 0;
+ for (int i = 0; i < FLAGS_num_queries; ++i) {
+ // Choose a random center for query.
+ S2Cap query_region(S2Testing::RandomPoint(), radius);
+
+ // Convert the query region to a set of terms, and compute the union of
+ // the document ids associated with those terms.
+ std::set candidates;
+ for (const auto& term : indexer.GetQueryTerms(query_region, kPrefix)) {
+ candidates.insert(index[term].begin(), index[term].end());
+ }
+
+ // "candidates" now contains all documents that intersect the query
+ // region, along with some documents that nearly intersect it. We can
+ // prune the results by retrieving the original "document" and checking
+ // the distance more precisely.
+ std::vector result;
+ for (int docid : candidates) {
+ if (!query_region.Contains(documents[docid])) continue;
+ result.push_back(docid);
+ }
+ // Now do something with the results (in this example we just count them).
+ num_found += result.size();
+ }
+ printf("Found %lld points in %d queries\n", num_found, FLAGS_num_queries);
+```
+
+## Other S2Region Types
+
+As its name implies, `S2RegionTermIndexer` supports indexing and querying any
+type of `S2Region`. In addition to points and discs (`S2Cap`), other useful
+`S2Region` types include:
+
+* [`S2LatLngRect`](/devguide/basic_types#s2latlngrect):
+ a rectangle in latitude-longitude coordinates.
+* [`S2Polyline`](/devguide/basic_types#s2polyline): a
+ polyline.
+* [`S2Polygon`](/devguide/basic_types#s2polygon): a
+ polygon (can have holes and multiple shells).
+* [`S2CellUnion`](/devguide/s2cell_hierarchy#s2cellunion):
+ a region approximated as a collection of `S2CellIds`.
+* `S2ShapeIndexRegion` - an arbitrary collection of points,
+ polylines, and polygons.
+* `S2ShapeIndexBufferedRegion` - like the above, but expanded by a
+ given radius.
+* `S2RegionUnion` - the union of arbitrary other regions.
+* `S2RegionIntersection` - the intersection of arbitrary other regions.
+
+For example, you could use `S2RegionTermIndexer` to index a set of polylines,
+and then query which polylines intersect a given polygon.
+
+## Comparing Indexing Classes
+
+This tutorial gave a brief introduction to two useful indexing classes
+(`S2PointIndex` and `S2RegionTermIndexer`). Here is a brief comparison of
+their features:
+
+`S2PointIndex`:
+
+* Represents a collection of points in memory.
+* Supports incremental updates.
+* Can use `S2ClosestPointQuery` to find the points near a given target:
+ * Target types include points, polylines, polygons, and collections.
+ * Can also restrict results to a given `S2Region`.
+
+`S2RegionTermIndexer`:
+
+* A tool for adding spatial data to an information retrieval system.
+* Can add one or more `S2Region` objects to each document.
+* Queries return all documents that intersect a given `S2Region`.
+* Requires an external system for storing and retrieving index terms.
+
+Another useful indexing class not described here is
+[`S2ShapeIndex`](/devguide/s2shapeindex), which indexes an
+arbitrary collection of points, polylines and polygons (collectively known as
+*shapes*). `S2ShapeIndex` is the most generally useful of these classes for
+working with geometry in memory. It compares to the classes above as follows:
+
+`S2ShapeIndex`:
+
+* Represents a collection of points, polylines, and polygons in memory.
+* Supports incremental updates.
+* Useful query classes include:
+ * `S2ContainsPointQuery`: returns the shape(s) that contain a given
+ point.
+ * [`S2ClosestEdgeQuery`](/devguide/s2closestedgequery):
+ returns the closest edges to a given point, polyline, polygon, or
+ geometry collection.
+ * `S2CrossingEdgeQuery`: returns the edge(s) that cross a given edge.
+ * `S2BooleanOperation`: computes boolean operations such as union, and
+ boolean predicates such as containment.
+ * `S2ShapeIndexRegion`: allows geometry collections to be approximated.
+ * `S2ShapeIndexBufferedRegion`: computes approximations that have been
+ expanded by a given radius.
diff --git a/docs/devguide/degenerate_geometry.md b/docs/devguide/degenerate_geometry.md
new file mode 100644
index 00000000..1f900656
--- /dev/null
+++ b/docs/devguide/degenerate_geometry.md
@@ -0,0 +1,584 @@
+---
+title: Degenerate Geometry in S2
+---
+
+## Overview
+
+Unlike many libraries, S2 fully supports degenerate geometry. Degeneracies not
+only do not cause errors, they have well-defined semantics including support for
+open, semi-open, and closed boundaries. Supported degeneracy types include the
+following:
+
+ - *Point polylines* consisting of a single degenerate edge *AA* (where *A*
+ represents a vertex).
+
+ - *Point loops* consisting of a single vertex *A*. Such loops may represent
+ either *point shells* or *point 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 degenerate shells or degenerate 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 (e.g. the polygon consisting of the loops *ABC* and *CBD*).
+
+Supporting such degeneracies has one big advantage, namely that geometry can be
+simplified without completely losing small details. For example consider a
+polygon representing a land region with many small lakes. Each lake would be
+represented as a hole. When this polygon is simplified, some or even all of the
+lakes may collapse to single points. Without support for degeneracies, such
+lakes would disappear from the simplified result. If we allow degeneracies, on
+the other hand, we can guarantee that for every point in the original region
+there is a nearby corresponding point in the simplified region and vice versa.
+In other words every lake in the input is still near some lake in the output.
+
+## Dimension Reduction: A Poor Substitute {#dimension-reduction}
+
+An alternative technique for handling degeneracies is *dimension reduction*,
+whereby degenerate geometry of dimension 2 or 1 is replaced by non-degenerate
+geometry of dimension 1 or 0. For example, the sibling pair \{*AB*, *BA*\}
+would be replaced by a polyline edge (either *AB* or *BA*, chosen arbitrarily),
+and similarly the degenerate polyline *AA* would be replaced by a single point
+*A*.
+
+This technique has four very significant drawbacks compared to true degeneracy
+support as implemented by the S2 library:
+
+ - **Dimension reduction is only capable of handling** ***positive
+ degeneracies*** (e.g., polygon shells) as opposed to *negative degeneracies*
+ (e.g., polygon holes). Consider again the example of simplifying a land
+ region with many small lakes that collapse to single points. Since these
+ lakes are represented as holes in the polygon rather than shells, they cannot
+ be replaced by points but instead must be simply discarded. Similarly,
+ dimension reduction is not capable of representing negative degeneracies of
+ dimension 1 (e.g., a land polygon containing a river that when simplified has
+ its width collapse to zero).
+
+ - **Dimension reduction changes the boundary of the affected geometry.** In
+ general the boundary of a polygon is defined to consist of its edges, the
+ boundary of a polyline is defined to consist of its two endpoints, and points
+ are defined to have no boundary at all. This implies that when the
+ degenerate sibling pair \{*AB*, *BA*\} is replaced by the polyline edge *AB*,
+ for example, the definition of its boundary changes from the two edges
+ \{*AB*, *BA*\} to the two points \{*A*, *B*\}. This is a very significant
+ difference, since it can change the result of virtually all the spatial
+ relationship predicates defined by OGC Simple Features model (which are based
+ on evaluating whether the interior, boundary, and exterior of one region
+ intersect the interior, boundary, and exterior of the other as summarized by
+ the `DE-9IM` matrix).
+
+ - **Dimension reduction forces clients to deal with heterogeneous geometry as a
+ possible result of virtually any geometric operation.** For example,
+ intersecting two polygons may yield a collection of geometry consisting of
+ polygons, polylines, and points. Similarly, intersecting two polylines may
+ yield a collection of polylines and points. In contrast, the true degeneracy
+ support offered by S2 means that any boolean operation on two polygons is
+ guaranteed to yield a polygon, and any boolean operation on two polylines is
+ guaranteed to yield a polyline. This is not only simpler for clients but
+ also reduces the opportunities for bugs (since the cases resulting in
+ mixed-dimension geometry may be unexpected and rare).
+
+ - **Dimension reduction makes it hard for clients to say how degeneracies
+ should be handled.** Some clients may wish to discard degeneracies, generate
+ errors, or convert degeneracies back into non-degenerate forms (e.g.
+ replacing point shells with tiny triangles). True degeneracy support makes
+ it easy to implement any of these models, and even to delegate this decision
+ to the functions that convert geometry to non-degeneracy-supporting formats
+ (such as geoJSON). Dimension reduction, on the other hand, makes it
+ difficult to distinguish degeneracies from legitimate lower-dimensional
+ objects. For example, imagine intersecting two collections of polylines and
+ polygons. Some output polylines will correspond to portions of input
+ polylines, while others may correspond to intersecting polygons that abut
+ along an edge.
+
+Other alternatives, such as extracting small details into separate objects to
+protect them from simplification and then merging them back in afterwards, can
+best be described as hacks. Not only are such algorithms virtually impossible
+to make robust, they cannot offer the mathematical guarantees that can be
+achieved by simplifying with a uniform error tolerance using degeneracies (see
+below).
+
+## Degeneracies and Hausdorff Distance
+
+The fact that S2 supports both positive and negative degeneracies allows us to
+make a very powerful guarantee when simplifying geometry. Recall that the
+*Hausdorff distance* between two geometries *A* and *B* is defined as
+
+$$H(A, B) = {\rm max} \{ h(A, B), h(B, A) \}$$
+
+where $$h(A, B)$$ is the *directed Hausdorff distance*
+
+$$h(A, B) = {\rm max}_{a \in A} \big\{ {\rm min}_{b \in B} \{ d(a, b) \} \big\}
+. $$
+
+**The Hausdorff distance is a measure of how different two geometries are;** in
+particular it is an upper bound on how far away a point in one geometry can be
+from the other geometry.
+
+The most important property of Hausdorff distance is that it can be used to
+bound the distance measurement errors due to simplifying geometry. Suppose that
+geometry *B* is a simplified version of geometry *A*, and that we wish to
+measure the distance to some third geometry *X*. Then the error due to
+simplification, i.e. the difference between $$d(A, X)$$ and $$d(B, X)$$, is
+bounded by the Hausdorff distance between *A* and *B*:
+
+$$| d(A, X) - d(B, X) | \leq H(A, B) \, .$$
+
+We can now state the guarantee that the S2 library makes when simplifying
+geometry. Let geometry *A* be an arbitrary collection of points, polylines, and
+polygons, and suppose that it is simplified using `S2Builder` with a snap radius
+of *r* to yield a new geometry collection *B*. Then if degeneracies are allowed
+when constructing *B*, we can guarantee that
+
+$$H(A, B) \leq r_{\rm e} \, .$$
+
+The quantity $$r_{\rm e}$$ is called the *maximum edge deviation*, which
+`S2Builder` ensures is at most 10% greater than the given snap radius *r* (i.e.,
+$$r_{\rm e} \leq 1.1 r$$). **In other words, the maximum possible distance
+measurement error due to simplification is only slightly greater than the snap
+radius used during simplification.** (Note that the additional 10% is a
+guaranteed bound rather than a heuristic; it is only necessary because S2 works
+with spherical geometry and would not be needed in a planar version of the
+algorithm.)
+
+In fact, under the view that positive and negative degeneracies have
+infinitesimal areas, the bound above applies not only to the whole geometries *A*
+and *B*, but also to the interiors, boundaries, and exteriors of these
+geometries:
+
+$$H({\rm Int}\, A, {\rm Int}\, B) \leq r_{\rm e}$$
+
+$$H(\partial A, \partial B) \leq r_{\rm e}$$
+
+$$H({\rm Ext}\, A, {\rm Ext}\, B) \leq r_{\rm e} \, .$$
+
+These properties are the holy grail of simplification algorithms; it is a
+mathematical way of saying that no details larger than the maximum edge devation
+$$r_{\rm e}$$ have been lost.
+
+## Representation and Validity
+
+Degenerate geometry is not supported by the legacy classes `S2Polyline` and
+`S2Polygon`. Instead the more modern, efficient, lightweight classes
+`S2LaxPolylineShape` and `S2LaxPolygonShape` must be used. (The `Lax` in the
+names refers to the fact that these classes allow degeneracies.) For example,
+while `S2Polygon` requires that all loops have at least three vertices,
+`S2LaxPolygonShape` supports loops with two, one, or even zero vertices. (The
+zero-vertex case is called the *full loop* and represents the full sphere.)
+
+The `Lax` classes themselves do not have any validity requirements, but the
+geometry they represent must satisfy certain conditions before it can be used in
+S2 operations. Some operations are more restrictive than others; for example,
+distance measurement has fewer requirements than `S2BooleanOperation`. You will
+generally want to construct your geometry to satisfy the most restrictive rules,
+outlined below. This can easily be accomplished by using `S2Builder` with an
+appropriate output layer type (e.g. `S2LaxPolygonShape`).
+
+### Polylines
+
+Polylines must consist of either a single degenerate edge *AA*, or a sequence of
+non-degenerate edges (e.g., *ABC*). Polylines with both degenerate and
+non-degenerate edges are not allowed (e.g., *AABC* or *ABBC*). On the other
+hand, polylines may self-intersect or have duplicate edges.
+
+Note that a point polyline is represented as two vertices (*AA*) whereas a point
+polygon loop is represented as one vertex (*A*). Each of these objects thus
+consists of a single degenerate edge AA, corresponding to the general rule that
+an *n*-vertex loop has *n* edges, whereas an *n*-vertex polyline has *n-1*
+edges.
+
+Here are a few examples of valid and invalid polylines:
+
+ - *AB*: a valid polyline defining a single directed edge.
+
+ - *ABCD*: a valid polyline consisting of three directed edges \{*AB*, *BC*,
+ *CD*\}
+
+ - \[\] (the empty vertex sequence): a valid polyline defining no edges.
+
+ - *AA*: a valid polyline defining a single degenerate edge at point *A*.
+
+ - *ABBC*: invalid because degenerate and non-degenerate edges are used in the
+ same polyline.
+
+ - *A*: invalid because it defines a vertex but no edges. (A degenerate edge at
+ *A* is represented as *AA*, while an empty polyline is represented as \[\].)
+
+### Polygons
+
+Polygons must consist of a set of oriented loops such that the interior of the
+polygon is always on the left. Polygon edges may intersect only at their
+endpoint vertices. A polygon may contain both an edge and its reverse edge
+(i.e., *AB* and *BA*), but duplicate edges are not allowed (*AB* and *AB*).
+Loops also must not contain repeated adjacent vertices (e.g., *ABBC*).
+
+Point loops consisting of a single vertex (*A*) are allowed, but such loops
+cannot be incident to any other edges. The *full loop* (i.e., the loop with
+zero vertices mentioned above) is allowed only if all the remaining loops (taken
+together) define only degeneracies.
+
+Here are a few examples of valid and invalid polygons:
+
+ - {} (the empty set of loops): a valid polygon containing no points (the empty
+ polygon).
+
+ - \{*A*, *BC*\}: a valid polygon consisting of one point shell and one sibling
+ pair shell.
+
+ - \{*AB*, *BAC*\}: invalid because the edge *BA* occurs twice (note that loop
+ *AB* consists of the two edges *AB* and *BA*).
+
+ - \{*ABC*, *A*\}: invalid because the point shell *A* is incident to other
+ edges.
+
+ - {full}: a valid polygon containing the entire sphere (the full polygon).
+
+ - {full, *ABCB*\}: a valid polygon consisting of the entire sphere except for
+ two sibling pair holes (*AB* and *BC*).
+
+ - {full, *ABC*\}: invalid because the full loop is used together with
+ non-degenerate geometry.
+
+ - {full, *ABC*, *ACB*\}: a valid polygon consisting of the entire sphere except
+ for three sibling pair holes (*AB*, *BC*, and *CA*\}. Note that even though
+ the loops *ABC* and *ACB* are themselves non-degenerate, together they define
+ only degenerate geometry and therefore can be used with the full loop.
+
+### Mixed-Dimensional Geometry {#mixed-dimensional}
+
+The `S2ShapeIndex` class represents an arbitrary collection of points,
+polylines, and polygons. Most S2 operations (e.g. `S2ClosestEdgeQuery`,
+`S2RegionCoverer`, `S2ContainsPointQuery`) do not impose any additional validity
+requirements on `S2ShapeIndex` beyond requiring that its component shapes are
+valid. The notable exception to this rule is `S2BooleanOperation`, which
+requires that **polygon interiors must be disjoint from all other geometry**
+(including other polygon interiors). So for example, polygon interiors must not
+intersect any point or polyline geometry.
+
+In order to support degeneracies, `S2BooleanOperation` requires in addition that
+**duplicate polygon edges are not allowed.** Note that this rule is only
+necessary when an `S2ShapeIndex` contains more than one polygon, since duplicate
+edges are already disallowed within individual polygons. This additional rule
+essentially requires that the collection of all polygons in each `S2ShapeIndex`
+must satisfy the same validity requirements as a single polygon. (Also note
+that more than one polygon is never needed, because polygons in S2 can have more
+than one outer shell and therefore can represent an arbitrary subset of the
+sphere.)
+
+Note that although duplicate polygon edges are not allowed, duplicate points and
+polyline edges are permitted. So for example, an `S2ShapeIndex` containing the
+points \{*A*, *A*\}, the polyline ABCABC, and the degenerate loop *AB* is a
+valid input to `S2BooleanOperation`. This is true whether the boundary model is
+open, semi-open, or closed. However an `S2ShapeIndex` input containing two
+polygons each consisting of the degenerate loop *AB* would be invalid.
+
+Supporting points and polyline edges as multisets makes it much easier to
+support time series such as GPS tracks. For example, a car driving around a
+track might easily generate a polyline such as ABCABCABC. Similarly, this makes
+it easier to reconstruct geometry that has been snapped and/or simplified, which
+can cause distinct vertices to merge together. Note that if duplicate values
+are not desired, they can easily be removed by choosing the appropriate
+`S2Builder` output layer options.
+
+## Semantics of Degeneracies {#semantics}
+
+In general **degeneracies are modeled as infinitesimal regions of the
+appropriate dimension.** So for example, a point shell *A* should be thought of
+as a tiny closed loop in the vicinity of the vertex *A*, and a degenerate shell
+loop *AB* (consisting of the two edges \{*AB*, *BA*\}) should be thought of as a
+narrow sliver in the vicinity of edge *AB*. Similarly, the polyline *AA* should
+be thought of as a tiny polyline in the vicinity of vertex *A*. We say "in the
+vicinity of" because under certain boundary models, degeneracies may not contain
+their nominal vertices and edges. For example, under the open boundary model
+the point shell *A* does not contain the point *A*. Instead it should be
+thought of as a tiny loop near the point *A*, so tiny that it does not contain
+any representable points along its edges or in its interior.
+
+This model implies that **lower-dimensional degeneracies are infinitesimal
+subsets of higher-dimensional degeneracies.** For example, the point *A* is
+vanishingly small compared to the degenerate polyline *AA*. This is because the
+point *A* is truly zero-dimensional, whereas the polyline *AA* represents a very
+small one-dimensional set. As another example, consider a point *A*, a closed
+polyline *AA*, and a closed point shell *A*. The intersection of the point *A*
+and the closed polyline *AA* consists only of the point *A*, because it is an
+infinitesimal subset of the polyline, and similarly the closed polyline *AA*
+intersected with the closed polygon *A* yields only the polyline *AA*.
+
+It is also worth recalling that in the S2 library, **polyline and polygon edges
+do not contain any interior points.** For example, the closed polygon shell
+loop *AB* (consisting of the sibling edge pair \{*AB*, *BA*\}) does not contain
+any points except *A* and *B*. This is because the S2 library models all
+geometry as being infinitesimally perturbed such that edges do not pass through
+any representable points on the sphere. This technique is known as *simulation
+of simplicity* (Edelsbrunner and Muecke, 1990) and greatly reduces the number of
+special cases that need to be handled when implementing robust geometric
+algorithms.
+
+## Boundary Models
+
+The S2 library supports modeling polylines and polygons as having open,
+semi-open, or closed boundaries. Here we briefly review these models and
+discuss the treatment of degeneracies within them.
+
+Note that the polyline and polygon boundary models are specified independently.
+The `PolylineModel` class specifies whether polylines contain their start and/or
+end vertex, whereas the `PolygonModel` class specifies whether polygons contain
+their vertices and edges.
+
+Also recall that the boundary model is specified for each operation rather than
+being a property of the geometry itself. This allows more flexibility; for
+example, if two geometries *A* and *B* intersect under the CLOSED boundary model
+but not under the OPEN boundary model, this would mean that the boundaries of
+the two geometries intersect but not their interiors.
+
+### Closed Boundary Model
+
+**A closed polyline contains all of its vertices.** For example, the closed
+polyline *ABC* intersected with the three points \{*A*, *B*, *C*\} yields \{*A*,
+*B*, *C*\}.
+
+Similarly, **a closed polygon contains all of its vertices, edges, and reversed
+edges.** So for example, the closed polygon *ABC* intersected with the points
+\{*A*, *B*, *C*\} yields \{*A*, *B*, *C*\}, and the closed polygon *ABC*
+intersected with the six polylines \{*AB*, *BA*, *BC*, *CB*, *AC*, *CA*\} yields
+\{*AB*, *BA*, *BC*, *CB*, *AC*, *CA*\}.
+
+These rules also apply to degeneracies. So for example, a closed point polyline
+*AA* contains its vertex *A*, and a closed sibling pair shell *AB* contains its
+vertices \{*A*, *B*\} and its edges \{*AB*, *BA*\}.
+
+Note that **certain degeneracies have no effect on point or edge containment; we
+call such degeneracies *redundant*.** In the case of the closed boundary model,
+**degenerate holes are redundant** because they do not exclude any points or
+edges. For example, consider a polygon *ABC* containing a point hole *D*.
+Since the boundary is closed, the polygon contains the point *D* whether or not
+the hole is present. (The degenerate hole is still considered to exclude an
+infinitesimal region from the polygon, it's simply that this region does not
+contain any representable points.)
+
+Redundant degeneracies are considered to be part of the polygon's boundary and
+therefore still affect distance measurement to the boundary. In fact, they may
+be viewed as a method of expanding the polygon's boundary without affecting its
+interior. They are treated in exactly the same way as non-degenerate regions in
+boolean operations; for example, a redundant degeneracy intersected or unioned
+with itself yields the original degeneracy, whereas a redundant degeneracy
+subtracted from itself yields the empty set.
+
+### Open Boundary Model
+
+**An open polyline contain all of its vertices except the first and last.** For
+example, the open polyline *ABC* intersected with the three points \{*A*, *B*,
+*C*\} yields \{*B*\}. The only exception is if the
+`polyline_loops_have_boundaries()` option is false and the first and last
+vertices of a polyline are the same (e.g. *ABCDA*); in this case the first/last
+vertex of the polyline loop is defined to be contained and its boundary is
+defined to be empty. So for example, with this option the open polyline *ABCA*
+intersected with \{*A*, *B*, *C*\} yields \{*A*, *B*, *C*\}.
+
+**An open polygon contains none of its vertices, edges, or reversed edges.** For
+example, the open polygon *ABC* intersected with the points \{*A*, *B*, *C*\}
+yields the empty set, and similarly the open polygon *ABC* intersected with the
+six polylines \{*AB*, *BA*, *BC*, *CB*, *AC*, *CA*\} yields the empty set.
+
+These rules also apply to degeneracies. So for example, an open point polyline
+*AA* contains no points at all, and an open sibling pair shell *AB* contains no
+points or edges. In other words, just as degenerate polygon holes are redundant
+in the closed boundary model, **degenerate polylines and degenerate polygon
+shells are redundant in the open boundary model.** These types of degeneracies
+do not affect point or edge containment, but simply add to the boundary of the
+affected geometry. The geometry contains exactly the same set of points and
+edges whether these degeneracies are present or not, however since the
+degeneracies change the boundary they can still affect distance measurement.
+
+Also note that **geometric objects have exactly the same boundary under all
+three boundary models.** So for example, the boundary of an open degenerate
+polyline *AA* is the point *A*, even though the polyline is defined not to
+contain that point.
+
+### Semi-Open Boundary Model
+
+The purpose of the semi-open boundary model is to allow geometry to **cover a
+region without gaps or overlaps.**
+
+In order to achieve this, **a semi-open polyline contains all of its vertices
+except the last.** For example, the semi-open polyline *ABC* intersected with
+the points \{*A*, *B*, *C*\} yields \{*A*, *B*\}. The major advantage of this
+model is that polylines can be split into pieces without affecting vertex
+containment. For example, the single polyline *ABCDEFG* contains exactly the
+same set of vertices as the three polylines \{*ABC*, *CDE*, *EFG*\}, and
+furthermore each vertex is contained by exactly one of these polylines.
+
+Similarly, semi-open polygon point containment is defined such that **if several
+semi-open polygons tile the region around a vertex, then exactly one of those
+polygons contains that vertex.** This implies that a triangle *ABC* may or may
+not contain each of its vertices \{*A*, *B*, *C*\}. However it ensures the
+important property that **if a set of polygons tiles the entire S2 sphere, then
+every point on the sphere is contained by exactly one polygon.** This property
+can be very useful for certain algorithms.
+
+**Semi-open polygons contain all of their edges, but none of their reversed
+edges.** For example, consider two abutting triangles *ABC* and *CBD*. The
+edge *BC* is contained by *ABC* but not *CBD*, and the edge *CB* is contained
+by *CBD* but not *ABC*. This property ensures **when a set of polygons tiles
+the sphere, each polygon edge is contained by exactly one polygon.**
+
+This rule also ensures that if a polyline is intersected with a set of polygons
+that tile the sphere, the resulting polylines can be unioned to obtain the
+original polyline without gaps or overlaps. For example, let *ABCD* be a small
+square polygon and consider its complement *DCBA*. These two polygons do not
+intersect and their union is the entire S2 sphere. Now consider the zig-zag
+polyline *ABDC*: its intersection with the polygon *ABCD* is the polyline *ABCD*
+(, whereas its intersection with the polygon *DCBA* is *DC*. Together these two
+polylines \{*ABD*, *DC*\} can be unioned to obtain the original polyline *ABDC*
+without gaps or overlaps.
+
+**All types of degeneracies are redundant in the semi-open model.** In other
+words, degeneracies never affect point or edge containment but simply add to the
+boundary of the geometry. For example, a semi-open point polyline *AA* does not
+contain the point *A* (just like the open model), semi-open degenerate polygon
+shells do not contain any points or edges (just like the open model), and
+semi-open degenerate polygon holes do not exclude any points or edges (just like
+the closed model). All of these degeneracies simply add to the boundary of the
+geometry without affecting its interior or exterior. The semi-open model is
+purest of the three models in that shells and holes are treated symmetrically.
+
+## Boolean Operations
+
+Many properties of boolean operations involving degeneracies follow naturally
+from the definitions above. Here we summarize the additional principles that
+define the results of such operations (intersection, union, difference, and
+symmetric difference).
+
+**Operations on geometry of a given dimension yields geometry of the same
+dimension.** So for example, a polyline *AB* intersected with a polyline *BC*
+under the closed model yields the degenerate polyline *BB* rather than a point.
+Similarly a polygon *ABC* intersected with an abutting polygon *CBD* under the
+closed model yields the degenerate polygon shell *BC*, not a polyline. And
+symmetrically, a polygon hole *ACB* unioned with an abutting polygon hole *BCD*
+under the open model yields a degenerate polygon hole *BC* (which has no
+alternative representation). This behavior is exactly what allows S2 to avoid
+the serious problems of [dimension reduction](#dimension-reduction).
+
+**In all boundary models, subtracting an object from itself yields the empty
+set.** This is true even for redundant degeneracies that contain no points or
+edges, such as subtracting a point polyline *AA* from itself in the open
+boundary model.
+
+**In all boundary models, intersecting or unioning an object with itself yields
+the original object(s).** The reason for the plural is that points and polyline
+edges are treated as multisets. For example, if a point *A* is intersected with
+another point *A*, the result is two points \{*A*, *A*\}. (Note that such
+duplicates can easily be filtered away by choosing appropriate options in the
+`S2Builder` output layer.) Again, this is true even for redundant degeneracies
+that contain no points or edges, so for example a point shell *A* intersected
+with itself in the open boundary model yields the same point shell *A*, and a
+point polyline *AA* intesected with itself in the open boundary model yields two
+point polylines \{*AA*, *AA*\} (neither of which contains the point *A*).
+
+**Intersecting geometry of different dimensions yields only the
+lower-dimensional geometry.** This is consistent with the principles described
+under [Semantics of Degeneracies](#semantics) above. For example, intersecting
+a point *A* with a closed polygon *ABC* yields only the point *A*, not a point
+polygon shell *A* as well. Of course, if the input geometries do not intersect
+then the result is empty. For example intersecting a point *A* with an open
+polygon *ABC* yields nothing at all.
+
+**Unioning geometry of different dimensions yields only the higher-dimensional
+geometry.** So for example, unioning a point *A* with a closed polyline *AA*
+yields only the polyline *AA*. Of course if the input geometries do not
+intersect then the result is simply the collection of both inputs. For example
+the union of a point *A* with an open or semi-open polyline *AA* is the
+collection of both objects \{*A*, *AA*\} since they do not intersect.
+
+**Unioning points with other points, or polylines with other polylines, yields a
+multiset.** For example, the union of two identical points *A* is the multiset
+\{*A*, *A*\}, and the union of two identical polylines *ABC* is the multiset
+\{*ABC*, *ABC*\}. As [noted earlier](#mixed-dimensional) this behavior makes it
+possible to reconstruct time series such as GPS tracks accurately. Unwanted
+duplicates can be filtered by choosing appropriate options in the `S2Builder`
+output layer.
+
+### Semi-Open Semantics
+
+The semi-open boundary model has a few properties that deserve further
+explanation.
+
+**In the semi-open model, complementary degeneracies do not intersect.** So for
+example, a point shell *A* does not intersect a point hole *A*, and a sibling
+pair shell *AB* does not intersect a sibling pair hole *AB*.
+
+**In the semi-open model, a point polyline *AA* or point shell *A* intersects
+another different geometry if and only if that other geometry contains the point
+*A*.** So for example, a point shell *A* intersects a semi-open polygon *ABC* if
+and only if *ABC* contains its vertex *A* under the usual semi-open rules. In
+other words, positive point degeneracies are treated exactly like points (except
+when a degeneracy is intersected with itself---see above).
+
+The purpose of this rule is to ensure that the defining property of the
+semi-open model (i.e. the ability to cover a region without overlaps or gaps)
+applies to point degeneracies as well as points. For example, if a degenerate
+polyline *AA* or degenerate shell *A* is intersected with a set of semi-open
+polygons that tile the region around a shared vertex *A*, then exactly one of
+those polygons contains that degeneracy. Similarly, if a semi-open polyline
+*ABC* is expressed as the disjoint union of sub-polylines \{*AB*, *BC*\} then
+exactly one of those polylines contains the degenerate polyline *BB* (in
+particular, *BC* contains *BB* while *AB* does not).
+
+As noted above, the only way that point degeneracies are handled differently
+ than points is with respect to self-intersection. Specifically, in the
+ semi-open model:
+
+ - A point *A* intersected with a polyline *AA* or point shell *A* is empty
+ (since the latter objects do not contain any points).
+ - A polyline *AA* intersected with a point shell *A* is empty (since the point
+ shell contains no points or polylines).
+ - And yet, a point *A*, polyline *AA*, or point shell *A* intersected with
+ itself yields the original object.
+
+These statements are not contradictory; there is in fact a valid representation
+of these objects as infinitesimal regions that yields this behavior.
+
+The complementary property is that **the union of a point hole *A* with another
+different geometry eliminates the hole if and only if that other geometry
+contains the point *A*.** So for example, a point hole *A* unioned with the
+semi-open polygon *ABC* eliminates the hole from the result if and only if *ABC*
+contains its vertex *A*.
+
+A similar property for edge degeneracies is that **sibling pair shells do not
+intersect abutting polygons.** So for example, a shell *BC* does not intersect
+the polygon *ABC* or *CBD*. The best way to think about this is that the
+intersection region is only one-half of the sibling pair degeneracy region and
+therefore cannot be represented. The complementary property is that **the union
+of a sibling pair hole with an abutting polygon eliminates the hole**. So for
+example, the union of the polygon *ABC* with the full sphere except for the hole
+*BC* is the full sphere.
+
+### Limitations
+
+It is important to realize that in some cases the true mathematical result of an
+operation cannot be represented. This is true even when no degeneracies are
+present.
+
+For example, consider a triangle *ABC* that contains a nested triangle *DEF*,
+and suppose that *DEF* is subtracted from *ABC* in the CLOSED boundary model.
+Ideally the result would contain the edges \{*AB*, *BC*, *CA*\} but not the
+edges \{*DE*, *EF*, *FD*\} (since the latter three edges belong to *DEF*, which
+has been subtracted). However it is not possible to represent this in the
+CLOSED model since all of these edges are on the boundary of the result.
+
+Similarly, results involving degeneracies sometimes cannot be exactly
+represented. In such cases the preference is always to correctly represent the
+**interior** of the result (including infinitesimal interiors) even when the
+boundary of the result cannot be perfectly represented.
+
+For example, consider subtracting a point shell *D* from the interior of a
+closed polygon *ABC*. The result is defined to be the polygon \{*ABC*, *D*\}
+where *D* is now a point hole. This may seem strange since (1) the result
+still contains the point *D* because the boundary is closed, and (2) there is
+no obvious reason to include *D* in the result because point holes exclude no
+points under the closed model (i.e., they are redundant). And yet this is the
+**only** correct result under the S2 degeneracy model, because the original
+point shell *D* is considered to include an infinitesimal two-dimensional area
+near the point *D* in addition to *D* itself. The only way to exclude this
+infinitesimal two-dimensional area from the result is to include *D* as a point
+hole.
diff --git a/docs/devguide/examples/coverings.md b/docs/devguide/examples/coverings.md
new file mode 100644
index 00000000..73e2ace2
--- /dev/null
+++ b/docs/devguide/examples/coverings.md
@@ -0,0 +1,152 @@
+---
+title: S2 Covering Examples
+---
+
+Here are some examples of the "cell coverage" feature of the new
+spherical geometry package. We pick some point on the sphere, and create
+a circular cap of some size around that point. We then cover the cap
+with some number of "cells". The result is then projected onto a plane
+tangent to the center of the cap and parallel to the surface of the
+sphere. The cells are drawn in blue and the cap itself is drawn in red.
+
+This point happens to be centered on the Google offices in Kirkland,
+with a 5 km radius cap. When you try to cover that cap with just one
+cell, you often end up with a large amount of extra space. Notice the
+large cell compared to the circle.
+
+
+
+Same configuration, except we allow the use of 2 cells.
+
+
+
+Same configuration, except we allow the use of 3 cells. One of them is
+quite small.
+
+
+
+Same configuration, except we allow the use of 4 cells. I've changed the
+scale factor so we can see more detail.
+
+
+
+Same configuration, except we allow the use of 5 cells.
+
+
+
+Same configuration, except we allow the use of 6 cells.
+
+
+
+Same configuration, except we allow the use of 10 cells.
+
+
+
+Same configuration, except we allow the use of 20 cells.
+
+
+
+Same configuration, except we allow the use of 50 cells.
+
+
+
+Same configuration, except we allow the use of 500 cells. The scale has
+been changed again to show more detail.
+
+
+
+This is a point at an edge between two faces of the cubic tiling, with a
+cap size of about 500 km and limit of 4 cells. The ratio of "area
+covered by the cells" to "area covered by the cap" is about 10, which is
+about the worst case value when you have at least 4 cells. (The ratio
+can be quite large, on the order of 10\^15, if you limit the covering to
+fewer than 4 cells.)
+
+
+
+Same configuration, except we allow the use of 6 cells.
+
+
+
+Same configuration, except we allow the use of 50 cells.
+
+
+
+Same configuration, except we allow the use of 500 cells.
+
+
+
+This is a point at the corner where three faces meet. The cap radius is
+10 km, and we're using 10 cells.
+
+
+
+Same configuration, except we allow the use of 20 cells.
+
+
+
+Same configuration, except we allow the use of 50 cells.
+
+
+
+Same configuration, except we allow the use of 200 cells.
+
+
+
+Same configuration, except we allow the use of 1000 cells. Note that in
+fact the code did not use all 1000 cells: this covering only uses 706
+cells. There was a point where the code had 1000 candidate cells, but
+after pruning the code merged some adjacent sets of cells into a smaller
+number of larger cells. The details of the covering did not change, it
+is merely a more efficient representation.
+
+
+
+This shows a latitude/longtitude rectangle (instead of a circular cap)
+which is roughly placed over Washington state. (The coordinate system
+that we're using for the project is not oriented to put North up, which
+is why it looks a bit weird.) Here is a covering with 10 cells..
+
+
+
+Same configuration, except we allow the use of 20 cells.
+
+
+
+Same configuration, except we allow the use of 100 cells.
+
+
+
+This shows a latitude/longtitude rectangle extends from 60 degrees north
+to 80 degrees north, and from -170 degrees to +170 degrees longitude.
+The covering is limited to 8 cells. Notice that the hole in the middle
+is completely covered.
+
+
+
+Same configuration, except we allow the use of 20 cells.
+
+
+
+Same configuration, except we allow the use of 100 cells. Now we have
+sufficient numbers of cells to model the hole, but not the gap near the
+date line.
+
+
+
+Same configuration, except we allow the use of 500 cells.
+
+
+
+Finally, here are some examples of geographic coverings. This is a covering of
+Hawaii using 25 cells.
+
+
+
+Here is a covering of Florida using 22 cells.
+
+
+
+And Florida using 152 cells.
+
+
diff --git a/docs/devguide/examples/img/corner_10.gif b/docs/devguide/examples/img/corner_10.gif
new file mode 100644
index 0000000000000000000000000000000000000000..ea19c14e1b25c34f0ae89c38d9eeba8b8e359834
GIT binary patch
literal 6204
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
Ee=fv6F#rGn
literal 0
HcmV?d00001
diff --git a/docs/devguide/examples/img/corner_20.gif b/docs/devguide/examples/img/corner_20.gif
new file mode 100644
index 0000000000000000000000000000000000000000..f7127cd39ecb900ad4f579d9424b5cfc60f5360a
GIT binary patch
literal 6052
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
Yw~B2YPm{L;s=^_wpXmPu8G!cx0gp!7W&i*H
literal 0
HcmV?d00001
diff --git a/docs/devguide/examples/img/corner_50.gif b/docs/devguide/examples/img/corner_50.gif
new file mode 100644
index 0000000000000000000000000000000000000000..ca6cb4ab8b6464c24585a75b3f7114b153e6b75e
GIT binary patch
literal 6623
zcmV<586f6INk%w1VPOGS0i^%{8yo*3BL6Hb|1&fHI5_`#HoHSZ|4B*zP*DF{TmNxT
z-DXzqV`KkqZU1{~?sRnje0=}^cXx*(nVlzyoj942KB}lPh=)YGszsT&Q<NE41ejE#)^$YctnJovQbGZkSOZ_^a!P-5`HOTvYGK9
zBFQT>9#oXcb4>n5EH0aL>`By1!7V|Z94*S!%21?Sem0%jBq_=yn6M;Al4bhztWOd1*2bh*25l9Ha#89aLE?;ED+O8R%{TVSju$K4djTNWIA2|h$
z`zpMzjDUx({!95M^I6o7&x5Y}Pg3#6!+F>Fbp~(Z4KYny|78-3G}R@T#(h(;p
zK_eB$M5rEvJQ~uTaFIzPgCP_$Jhym&3h)^0)-IO{!aGRBUST#g!TCUgS
z8XMto+7K?1xyLhVC6*>SYpUTA4iT_0#0dt;i3dPKz;-7+c~0m=bt2}XmY{Jw1zn1)
zC5i@GANsiqT#&vYOgp?$`oxeoaT&!91@dsqj-7_FkPtgWuwSL7vQeH55u_Mct7ZUt
z4RkYFYG4(>?oz^|yn1nu5TRZ{sz$;-fhuoboVj7SBe}ZA)1+t7nw-CN^XI3!-ri%vV