├── .gitattributes ├── .gitignore ├── Geometry on the Sphere_ Google's S2 Library.pptx ├── LICENSE ├── README.md ├── S2Geometry.Tests ├── GeometryTestCase.cs ├── JavaAssert.cs ├── Properties │ └── AssemblyInfo.cs ├── R1IntervalTest.cs ├── S1AngleTest.cs ├── S2CapTest.cs ├── S2CellIdTest.cs ├── S2CellTest.cs ├── S2CellUnionTest.cs ├── S2EdgeIndexTest.cs ├── S2EdgeUtilTest.cs ├── S2Geometry.Tests.csproj ├── S2LatLngRectTest.cs ├── S2LatLngTest.cs ├── S2LoopTest.cs ├── S2PolygonBuilderTest.cs ├── S2PolygonTest.cs ├── S2PolylineTest.cs ├── S2RegionCovererTest.cs ├── S2Test.cs └── project.json ├── S2Geometry.nuspec ├── S2Geometry.sln ├── S2Geometry ├── AssemblyTFMAttribute.cs ├── DataStructures │ ├── HashBag.cs │ ├── IMultiMap.cs │ ├── MultiMap.cs │ ├── MultiMapEnumerator.cs │ ├── PriorityQueue.cs │ └── SortedMultiMapEnumerator.cs ├── FpUtils.cs ├── IS2Region.cs ├── NullObject.cs ├── Preconditions.cs ├── Properties │ └── AssemblyInfo.cs ├── R1Interval.cs ├── R2Vector.cs ├── S1Angle.cs ├── S1Interval.cs ├── S2.cs ├── S2AreaCentroid.cs ├── S2Cap.cs ├── S2Cell.cs ├── S2CellId.cs ├── S2CellUnion.cs ├── S2Edge.cs ├── S2EdgeIndex.cs ├── S2EdgeUtil.cs ├── S2Geometry.csproj ├── S2LatLng.cs ├── S2LatLngRect.cs ├── S2Loop.cs ├── S2Point.cs ├── S2Polygon.cs ├── S2PolygonBuilder.cs ├── S2Polyline.cs ├── S2Projections.cs ├── S2RegionCoverer.cs └── project.json └── pack.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | .vs/ 18 | 19 | *.nuget.props 20 | *.nuget.targets 21 | *.lock.json 22 | 23 | 24 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 25 | #packages/*/build/ 26 | 27 | # MSTest test Results 28 | [Tt]est[Rr]esult*/ 29 | [Bb]uild[Ll]og.* 30 | 31 | *_i.c 32 | *_p.c 33 | *.ilk 34 | *.meta 35 | *.obj 36 | *.pch 37 | *.pdb 38 | *.pgc 39 | *.pgd 40 | *.rsp 41 | *.sbr 42 | *.tlb 43 | *.tli 44 | *.tlh 45 | *.tmp 46 | *.tmp_proj 47 | *.log 48 | *.vspscc 49 | *.vssscc 50 | .builds 51 | *.pidb 52 | *.log 53 | *.scc 54 | 55 | # Visual C++ cache files 56 | ipch/ 57 | *.aps 58 | *.ncb 59 | *.opensdf 60 | *.sdf 61 | *.cachefile 62 | 63 | # Visual Studio profiler 64 | *.psess 65 | *.vsp 66 | *.vspx 67 | 68 | # Guidance Automation Toolkit 69 | *.gpState 70 | 71 | # ReSharper is a .NET coding add-in 72 | _ReSharper*/ 73 | *.[Rr]e[Ss]harper 74 | 75 | # TeamCity is a build add-in 76 | _TeamCity* 77 | 78 | # DotCover is a Code Coverage Tool 79 | *.dotCover 80 | 81 | # NCrunch 82 | *.ncrunch* 83 | .*crunch*.local.xml 84 | 85 | # Installshield output folder 86 | [Ee]xpress/ 87 | 88 | # DocProject is a documentation generator add-in 89 | DocProject/buildhelp/ 90 | DocProject/Help/*.HxT 91 | DocProject/Help/*.HxC 92 | DocProject/Help/*.hhc 93 | DocProject/Help/*.hhk 94 | DocProject/Help/*.hhp 95 | DocProject/Help/Html2 96 | DocProject/Help/html 97 | 98 | # Click-Once directory 99 | publish/ 100 | 101 | # Publish Web Output 102 | *.Publish.xml 103 | *.pubxml 104 | 105 | # NuGet Packages Directory 106 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 107 | packages/ 108 | 109 | # Windows Azure Build Output 110 | csx 111 | *.build.csdef 112 | 113 | # Windows Store app package directory 114 | AppPackages/ 115 | 116 | # Others 117 | sql/ 118 | *.Cache 119 | ClientBin/ 120 | [Ss]tyle[Cc]op.* 121 | ~$* 122 | *~ 123 | *.dbmdl 124 | *.[Pp]ublish.xml 125 | *.pfx 126 | *.publishsettings 127 | 128 | # RIA/Silverlight projects 129 | Generated_Code/ 130 | 131 | # Backup & report files from converting an old project file to a newer 132 | # Visual Studio version. Backup files are not needed, because we have git ;-) 133 | _UpgradeReport_Files/ 134 | Backup*/ 135 | UpgradeLog*.XML 136 | UpgradeLog*.htm 137 | 138 | # SQL Server files 139 | App_Data/*.mdf 140 | App_Data/*.ldf 141 | 142 | # ========================= 143 | # Windows detritus 144 | # ========================= 145 | 146 | # Windows image file caches 147 | Thumbs.db 148 | ehthumbs.db 149 | 150 | # Folder config file 151 | Desktop.ini 152 | 153 | # Recycle Bin used on file shares 154 | $RECYCLE.BIN/ 155 | 156 | # Mac crap 157 | .DS_Store 158 | 159 | *.nupkg 160 | -------------------------------------------------------------------------------- /Geometry on the Sphere_ Google's S2 Library.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novotnyllc/s2-geometry-library-csharp/880deeff88f8b8b0fe67ed888efeffbbdc460a7b/Geometry on the Sphere_ Google's S2 Library.pptx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | s2-geometry-library-csharp 2 | ========================== 3 | 4 | This is a port of Google's S2 Geometry Library from both Java and C++ 5 | https://code.google.com/p/s2-geometry-library-java/ 6 | 7 | https://code.google.com/p/s2-geometry-library/ 8 | 9 | This library is can be used to create GeoHashes for fast querying. The Java version is used by AWS for 10 | GeoSpatial queries in DynamoDB. 11 | 12 | S2 uses Hilbert Curves extensivly. 13 | For more info, see the original google presentation https://docs.google.com/presentation/d/1Hl4KapfAENAOf4gv-pSngKwvS_jwNVHRPZTTDzXXn6Q/view 14 | 15 | 16 | Current status 17 | --- 18 | Ready on NuGet, have fun! 19 | `Install-Package S2Geometry` -------------------------------------------------------------------------------- /S2Geometry.Tests/GeometryTestCase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Google.Common.Geometry; 8 | using NUnit.Framework; 9 | 10 | namespace S2Geometry.Tests 11 | { 12 | [TestFixture] 13 | public abstract class GeometryTestCase 14 | { 15 | public Random rand { get; private set; } 16 | 17 | [TestFixtureSetUp] 18 | protected virtual void SetUp() 19 | { 20 | rand = new Random(123456); 21 | } 22 | 23 | protected long LongRandom() 24 | { 25 | // This is how Java's nextLong works 26 | var bytes = new byte[4]; 27 | rand.NextBytes(bytes); 28 | var bytes1 = new byte[4]; 29 | rand.NextBytes(bytes1); 30 | 31 | var i1 = BitConverter.ToInt32(bytes, 0); 32 | var i2 = BitConverter.ToInt32(bytes1, 0); 33 | return ((long)(i1 << 32)) + i2; 34 | } 35 | 36 | public void assertDoubleNear(double a, double b) 37 | { 38 | assertDoubleNear(a, b, 1e-9); 39 | } 40 | 41 | public void assertDoubleNear(double a, double b, double error) 42 | { 43 | Assert.True(a + error > b); 44 | Assert.True(a < b + error); 45 | } 46 | 47 | // maybe these should be put in a special testing util class 48 | /** Return a random unit-length vector. */ 49 | 50 | public S2Point randomPoint() 51 | { 52 | return S2Point.Normalize(new S2Point( 53 | 2*rand.NextDouble() - 1, 54 | 2*rand.NextDouble() - 1, 55 | 2*rand.NextDouble() - 1)); 56 | } 57 | 58 | 59 | /** 60 | * Return a right-handed coordinate frame (three orthonormal vectors). Returns 61 | * an array of three points: x,y,z 62 | */ 63 | 64 | public IReadOnlyList getRandomFrame() 65 | { 66 | var p0 = randomPoint(); 67 | var p1 = S2Point.Normalize(S2Point.CrossProd(p0, randomPoint())); 68 | var p2 = S2Point.Normalize(S2Point.CrossProd(p0, p1)); 69 | return new List(new[] {p0, p1, p2}); 70 | } 71 | 72 | /** 73 | * Return a random cell id at the given level or at a randomly chosen level. 74 | * The distribution is uniform over the space of cell ids, but only 75 | * approximately uniform over the surface of the sphere. 76 | */ 77 | 78 | public S2CellId getRandomCellId(int level) 79 | { 80 | var face = random(S2CellId.NumFaces); 81 | 82 | var pos = (ulong)LongRandom() & ((1L << (2*S2CellId.MaxLevel)) - 1); 83 | return S2CellId.FromFacePosLevel(face, pos, level); 84 | } 85 | 86 | public S2CellId getRandomCellId() 87 | { 88 | return getRandomCellId(random(S2CellId.MaxLevel + 1)); 89 | } 90 | 91 | protected int random(int n) 92 | { 93 | if (n == 0) 94 | { 95 | return 0; 96 | } 97 | return rand.Next(n); 98 | } 99 | 100 | 101 | // Pick "base" uniformly from range [0,maxLog] and then return 102 | // "base" random bits. The effect is to pick a number in the range 103 | // [0,2^maxLog-1] with bias towards smaller numbers. 104 | protected int skewed(int maxLog) 105 | { 106 | var @base = Math.Abs(rand.Next())%(maxLog + 1); 107 | // if (!base) return 0; // if 0==base, we & with 0 below. 108 | // 109 | // this distribution differs slightly from ACMRandom's Skewed, 110 | // since 0 occurs approximately 3 times more than 1 here, and 111 | // ACMRandom's Skewed never outputs 0. 112 | return rand.Next() & ((1 << @base) - 1); 113 | } 114 | 115 | /** 116 | * Checks that "covering" completely covers the given region. If "check_tight" 117 | * is true, also checks that it does not contain any cells that do not 118 | * intersect the given region. ("id" is only used internally.) 119 | */ 120 | 121 | protected void checkCovering(IS2Region region, S2CellUnion covering, bool checkTight, S2CellId id) 122 | { 123 | if (!id.IsValid) 124 | { 125 | for (var face = 0; face < 6; ++face) 126 | { 127 | checkCovering(region, covering, checkTight, S2CellId.FromFacePosLevel(face, 0, 0)); 128 | } 129 | return; 130 | } 131 | 132 | if (!region.MayIntersect(new S2Cell(id))) 133 | { 134 | // If region does not intersect id, then neither should the covering. 135 | if (checkTight) 136 | { 137 | Assert.True(!covering.Intersects(id)); 138 | } 139 | } 140 | else if (!covering.Contains(id)) 141 | { 142 | // The region may intersect id, but we can't assert that the covering 143 | // intersects id because we may discover that the region does not actually 144 | // intersect upon further subdivision. (MayIntersect is not exact.) 145 | Assert.True(!region.Contains(new S2Cell(id))); 146 | var result = !id.IsLeaf; 147 | Assert.True(result); 148 | var end = id.ChildEnd; 149 | for (var child = id.ChildBegin; !child.Equals(end); child = child.Next) 150 | { 151 | checkCovering(region, covering, checkTight, child); 152 | } 153 | } 154 | } 155 | 156 | protected S2Cap getRandomCap(double minArea, double maxArea) 157 | { 158 | var capArea = maxArea 159 | *Math.Pow(minArea/maxArea, rand.NextDouble()); 160 | Assert.True(capArea >= minArea && capArea <= maxArea); 161 | 162 | // The surface area of a cap is 2*Pi times its height. 163 | return S2Cap.FromAxisArea(randomPoint(), capArea); 164 | } 165 | 166 | protected S2Point samplePoint(S2Cap cap) 167 | { 168 | // We consider the cap axis to be the "z" axis. We choose two other axes to 169 | // complete the coordinate frame. 170 | 171 | var z = cap.Axis; 172 | var x = z.Ortho; 173 | var y = S2Point.CrossProd(z, x); 174 | 175 | // The surface area of a spherical cap is directly proportional to its 176 | // height. First we choose a random height, and then we choose a random 177 | // point along the circle at that height. 178 | 179 | var h = rand.NextDouble()*cap.Height; 180 | var theta = 2*S2.Pi*rand.NextDouble(); 181 | var r = Math.Sqrt(h*(2 - h)); // Radius of circle. 182 | 183 | // (cos(theta)*r*x + sin(theta)*r*y + (1-h)*z).Normalize() 184 | return S2Point.Normalize(((x * Math.Cos(theta)*r) + (y * Math.Sin(theta)*r)) + (z * (1 - h))); 185 | } 186 | 187 | private static void parseVertices(String str, List vertices) 188 | { 189 | if (str == null) 190 | { 191 | return; 192 | } 193 | 194 | foreach (var token in str.Split(',')) 195 | { 196 | var colon = token.IndexOf(':'); 197 | if (colon == -1) 198 | { 199 | throw new ArgumentException( 200 | "Illegal string:" + token + ". Should look like '35:20'"); 201 | } 202 | var lat = Double.Parse(token.Substring(0, colon)); 203 | var lng = Double.Parse(token.Substring(colon + 1)); 204 | vertices.Add(S2LatLng.FromDegrees(lat, lng).ToPoint()); 205 | } 206 | } 207 | 208 | protected static S2Point makePoint(String str) 209 | { 210 | var vertices = new List(); 211 | parseVertices(str, vertices); 212 | return vertices.Single(); 213 | } 214 | 215 | protected static S2Loop makeLoop(String str) 216 | { 217 | var vertices = new List(); 218 | parseVertices(str, vertices); 219 | return new S2Loop(vertices); 220 | } 221 | 222 | protected static S2Polygon makePolygon(String str) 223 | { 224 | var loops = new List(); 225 | 226 | foreach (var token in str.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)) 227 | { 228 | //Splitter.on(';').omitEmptyStrings().split(str)) { 229 | var loop = makeLoop(token); 230 | loop.Normalize(); 231 | loops.Add(loop); 232 | } 233 | 234 | return new S2Polygon(loops); 235 | } 236 | 237 | [DebuggerNonUserCode] 238 | [DebuggerStepThrough] 239 | protected static void assertEquals(object actual, object expected) 240 | { 241 | JavaAssert.Equal(actual, expected); 242 | } 243 | 244 | [DebuggerNonUserCode] 245 | [DebuggerStepThrough] 246 | protected static void assertEquals(double actual, double expected, double delta) 247 | { 248 | Assert.AreEqual(expected, actual, delta); 249 | } 250 | 251 | [DebuggerNonUserCode] 252 | [DebuggerStepThrough] 253 | protected static void assertTrue(bool value) 254 | { 255 | Assert.True(value); 256 | } 257 | 258 | [DebuggerNonUserCode] 259 | [DebuggerStepThrough] 260 | protected static void assertFalse(bool value) 261 | { 262 | Assert.False(value); 263 | } 264 | 265 | [DebuggerNonUserCode] 266 | [DebuggerStepThrough] 267 | protected static void assertFalse(string message, bool value) 268 | { 269 | Assert.False(value, message); 270 | } 271 | 272 | [DebuggerNonUserCode] 273 | [DebuggerStepThrough] 274 | protected static void assertTrue(string message, bool value) 275 | { 276 | Assert.True(value, message); 277 | } 278 | 279 | protected static S2Polyline makePolyline(String str) 280 | { 281 | var vertices = new List(); 282 | parseVertices(str, vertices); 283 | return new S2Polyline(vertices); 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/JavaAssert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public static class JavaAssert 12 | { 13 | [DebuggerNonUserCode] 14 | [DebuggerStepThrough] 15 | public static void Equal(object actual, object expected) 16 | { 17 | Assert.AreEqual(expected, actual); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("S2Geometry.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("S2Geometry.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6677d24d-23c9-446f-be81-8251f37361bd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /S2Geometry.Tests/R1IntervalTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class R1IntervalTest : GeometryTestCase 12 | { 13 | /** 14 | * Test all of the interval operations on the given pair of intervals. 15 | * "expected_relation" is a sequence of "T" and "F" characters corresponding 16 | * to the expected results of contains(), interiorContains(), Intersects(), 17 | * and InteriorIntersects() respectively. 18 | */ 19 | 20 | private void testIntervalOps(R1Interval x, R1Interval y, String expectedRelation) 21 | { 22 | JavaAssert.Equal(x.Contains(y), expectedRelation[0] == 'T'); 23 | JavaAssert.Equal(x.InteriorContains(y), expectedRelation[1] == 'T'); 24 | JavaAssert.Equal(x.Intersects(y), expectedRelation[2] == 'T'); 25 | JavaAssert.Equal(x.InteriorIntersects(y), expectedRelation[3] == 'T'); 26 | 27 | JavaAssert.Equal(x.Contains(y), x.Union(y).Equals(x)); 28 | JavaAssert.Equal(x.Intersects(y), !x.Intersection(y).IsEmpty); 29 | } 30 | 31 | [Test] 32 | public void R1IntervalBasicTest() 33 | { 34 | // Constructors and accessors. 35 | var unit = new R1Interval(0, 1); 36 | var negunit = new R1Interval(-1, 0); 37 | JavaAssert.Equal(unit.Lo, 0.0); 38 | JavaAssert.Equal(unit.Hi, 1.0); 39 | JavaAssert.Equal(negunit.Lo, -1.0); 40 | JavaAssert.Equal(negunit.Hi, 0.0); 41 | 42 | // is_empty() 43 | var half = new R1Interval(0.5, 0.5); 44 | Assert.True(!unit.IsEmpty); 45 | Assert.True(!half.IsEmpty); 46 | var empty = R1Interval.Empty; 47 | Assert.True(empty.IsEmpty); 48 | 49 | // GetCenter(), GetLength() 50 | JavaAssert.Equal(unit.Center, 0.5); 51 | JavaAssert.Equal(half.Center, 0.5); 52 | JavaAssert.Equal(negunit.Length, 1.0); 53 | JavaAssert.Equal(half.Length, 0.0); 54 | Assert.True(empty.Length < 0); 55 | 56 | // contains(double), interiorContains(double) 57 | Assert.True(unit.Contains(0.5)); 58 | Assert.True(unit.InteriorContains(0.5)); 59 | Assert.True(unit.Contains(0)); 60 | Assert.True(!unit.InteriorContains(0)); 61 | Assert.True(unit.Contains(1)); 62 | Assert.True(!unit.InteriorContains(1)); 63 | 64 | // contains(R1Interval), interiorContains(R1Interval) 65 | // Intersects(R1Interval), InteriorIntersects(R1Interval) 66 | testIntervalOps(empty, empty, "TTFF"); 67 | testIntervalOps(empty, unit, "FFFF"); 68 | testIntervalOps(unit, half, "TTTT"); 69 | testIntervalOps(unit, unit, "TFTT"); 70 | testIntervalOps(unit, empty, "TTFF"); 71 | testIntervalOps(unit, negunit, "FFTF"); 72 | testIntervalOps(unit, new R1Interval(0, 0.5), "TFTT"); 73 | testIntervalOps(half, new R1Interval(0, 0.5), "FFTF"); 74 | 75 | // addPoint() 76 | R1Interval r; 77 | r = empty.AddPoint(5); 78 | Assert.True(r.Lo == 5.0 && r.Hi == 5.0); 79 | r = r.AddPoint(-1); 80 | Assert.True(r.Lo == -1.0 && r.Hi == 5.0); 81 | r = r.AddPoint(0); 82 | Assert.True(r.Lo == -1.0 && r.Hi == 5.0); 83 | 84 | // fromPointPair() 85 | JavaAssert.Equal(R1Interval.FromPointPair(4, 4), new R1Interval(4, 4)); 86 | JavaAssert.Equal(R1Interval.FromPointPair(-1, -2), new R1Interval(-2, -1)); 87 | JavaAssert.Equal(R1Interval.FromPointPair(-5, 3), new R1Interval(-5, 3)); 88 | 89 | // expanded() 90 | JavaAssert.Equal(empty.Expanded(0.45), empty); 91 | JavaAssert.Equal(unit.Expanded(0.5), new R1Interval(-0.5, 1.5)); 92 | 93 | // union(), intersection() 94 | Assert.True(new R1Interval(99, 100).Union(empty).Equals(new R1Interval(99, 100))); 95 | Assert.True(empty.Union(new R1Interval(99, 100)).Equals(new R1Interval(99, 100))); 96 | Assert.True(new R1Interval(5, 3).Union(new R1Interval(0, -2)).IsEmpty); 97 | Assert.True(new R1Interval(0, -2).Union(new R1Interval(5, 3)).IsEmpty); 98 | Assert.True(unit.Union(unit).Equals(unit)); 99 | Assert.True(unit.Union(negunit).Equals(new R1Interval(-1, 1))); 100 | Assert.True(negunit.Union(unit).Equals(new R1Interval(-1, 1))); 101 | Assert.True(half.Union(unit).Equals(unit)); 102 | Assert.True(unit.Intersection(half).Equals(half)); 103 | Assert.True(unit.Intersection(negunit).Equals(new R1Interval(0, 0))); 104 | Assert.True(negunit.Intersection(half).IsEmpty); 105 | Assert.True(unit.Intersection(empty).IsEmpty); 106 | Assert.True(empty.Intersection(unit).IsEmpty); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S1AngleTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | [TestFixture] 12 | public class S1AngleTest 13 | { 14 | [Test] 15 | public void S1AngleBasicTest() 16 | { 17 | // Check that the conversion between Pi radians and 180 degrees is exact. 18 | JavaAssert.Equal(S1Angle.FromRadians(Math.PI).Radians, Math.PI); 19 | JavaAssert.Equal(S1Angle.FromRadians(Math.PI).Degrees, 180.0); 20 | JavaAssert.Equal(S1Angle.FromDegrees(180).Radians, Math.PI); 21 | JavaAssert.Equal(S1Angle.FromDegrees(180).Degrees, 180.0); 22 | 23 | JavaAssert.Equal(S1Angle.FromRadians(Math.PI/2).Degrees, 90.0); 24 | 25 | // Check negative angles. 26 | JavaAssert.Equal(S1Angle.FromRadians(-Math.PI/2).Degrees, -90.0); 27 | JavaAssert.Equal(S1Angle.FromDegrees(-45).Radians, -Math.PI/4); 28 | 29 | // Check that E5/E6/E7 representations work as expected. 30 | JavaAssert.Equal(S1Angle.E5(2000000), S1Angle.FromDegrees(20)); 31 | JavaAssert.Equal(S1Angle.E6(-60000000), S1Angle.FromDegrees(-60)); 32 | JavaAssert.Equal(S1Angle.E7(750000000), S1Angle.FromDegrees(75)); 33 | JavaAssert.Equal(S1Angle.FromDegrees(12.34567).E5(), 1234567); 34 | JavaAssert.Equal(S1Angle.FromDegrees(12.345678).E6(), 12345678); 35 | JavaAssert.Equal(S1Angle.FromDegrees(-12.3456789).E7(), -123456789); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2CapTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2CapTest : GeometryTestCase 12 | { 13 | public S2Point getLatLngPoint(double latDegrees, double lngDegrees) 14 | { 15 | return S2LatLng.FromDegrees(latDegrees, lngDegrees).ToPoint(); 16 | } 17 | 18 | // About 9 times the double-precision roundoff relative error. 19 | public const double EPS = 1e-15; 20 | 21 | public void testRectBound() 22 | { 23 | // Empty and full caps. 24 | Assert.True(S2Cap.Empty.RectBound.IsEmpty); 25 | Assert.True(S2Cap.Full.RectBound.IsFull); 26 | 27 | var kDegreeEps = 1e-13; 28 | // Maximum allowable error for latitudes and longitudes measured in 29 | // degrees. (assertDoubleNear uses a fixed tolerance that is too small.) 30 | 31 | // Cap that includes the south pole. 32 | var rect = 33 | S2Cap.FromAxisAngle(getLatLngPoint(-45, 57), S1Angle.FromDegrees(50)).RectBound; 34 | assertDoubleNear(rect.LatLo.Degrees, -90, kDegreeEps); 35 | assertDoubleNear(rect.LatHi.Degrees, 5, kDegreeEps); 36 | Assert.True(rect.Lng.IsFull); 37 | 38 | // Cap that is tangent to the north pole. 39 | rect = S2Cap.FromAxisAngle(S2Point.Normalize(new S2Point(1, 0, 1)), S1Angle.FromRadians(S2.PiOver4)).RectBound; 40 | assertDoubleNear(rect.Lat.Lo, 0); 41 | assertDoubleNear(rect.Lat.Hi, S2.PiOver2); 42 | Assert.True(rect.Lng.IsFull); 43 | 44 | rect = S2Cap 45 | .FromAxisAngle(S2Point.Normalize(new S2Point(1, 0, 1)), S1Angle.FromDegrees(45)).RectBound; 46 | assertDoubleNear(rect.LatLo.Degrees, 0, kDegreeEps); 47 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps); 48 | Assert.True(rect.Lng.IsFull); 49 | 50 | // The eastern hemisphere. 51 | rect = S2Cap 52 | .FromAxisAngle(new S2Point(0, 1, 0), S1Angle.FromRadians(S2.PiOver2 + 5e-16)).RectBound; 53 | assertDoubleNear(rect.LatLo.Degrees, -90, kDegreeEps); 54 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps); 55 | Assert.True(rect.Lng.IsFull); 56 | 57 | // A cap centered on the equator. 58 | rect = S2Cap.FromAxisAngle(getLatLngPoint(0, 50), S1Angle.FromDegrees(20)).RectBound; 59 | assertDoubleNear(rect.LatLo.Degrees, -20, kDegreeEps); 60 | assertDoubleNear(rect.LatHi.Degrees, 20, kDegreeEps); 61 | assertDoubleNear(rect.LngLo.Degrees, 30, kDegreeEps); 62 | assertDoubleNear(rect.LngHi.Degrees, 70, kDegreeEps); 63 | 64 | // A cap centered on the north pole. 65 | rect = S2Cap.FromAxisAngle(getLatLngPoint(90, 123), S1Angle.FromDegrees(10)).RectBound; 66 | assertDoubleNear(rect.LatLo.Degrees, 80, kDegreeEps); 67 | assertDoubleNear(rect.LatHi.Degrees, 90, kDegreeEps); 68 | Assert.True(rect.Lng.IsFull); 69 | } 70 | 71 | public void testCells() 72 | { 73 | // For each cube face, we construct some cells on 74 | // that face and some caps whose positions are relative to that face, 75 | // and then check for the expected intersection/containment results. 76 | 77 | // The distance from the center of a face to one of its vertices. 78 | var kFaceRadius = Math.Atan(S2.Sqrt2); 79 | 80 | for (var face = 0; face < 6; ++face) 81 | { 82 | // The cell consisting of the entire face. 83 | var rootCell = S2Cell.FromFacePosLevel(face, (byte)0, 0); 84 | 85 | // A leaf cell at the midpoint of the v=1 edge. 86 | var edgeCell = new S2Cell(S2Projections.FaceUvToXyz(face, 0, 1 - EPS)); 87 | 88 | // A leaf cell at the u=1, v=1 corner. 89 | var cornerCell = new S2Cell(S2Projections.FaceUvToXyz(face, 1 - EPS, 1 - EPS)); 90 | 91 | // Quick check for full and empty caps. 92 | Assert.True(S2Cap.Full.Contains(rootCell)); 93 | Assert.True(!S2Cap.Empty.MayIntersect(rootCell)); 94 | 95 | // Check intersections with the bounding caps of the leaf cells that are 96 | // adjacent to 'corner_cell' along the Hilbert curve. Because this corner 97 | // is at (u=1,v=1), the curve stays locally within the same cube face. 98 | var first = cornerCell.Id.Previous.Previous.Previous; 99 | var last = cornerCell.Id.Next.Next.Next.Next; 100 | for (var id = first; id 0.1); 117 | JavaAssert.Equal(covering.Contains(edgeCell), covering.MayIntersect(edgeCell)); 118 | JavaAssert.Equal(covering.Contains(cornerCell), capFace == face); 119 | JavaAssert.Equal( 120 | covering.MayIntersect(cornerCell), center.DotProd(cornerCell.Center) > 0); 121 | 122 | // A cap that barely intersects the edges of 'cap_face'. 123 | var bulging = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(S2.PiOver4 + EPS)); 124 | Assert.True(!bulging.Contains(rootCell)); 125 | JavaAssert.Equal(bulging.MayIntersect(rootCell), capFace != antiFace); 126 | JavaAssert.Equal(bulging.Contains(edgeCell), capFace == face); 127 | JavaAssert.Equal(bulging.MayIntersect(edgeCell), center.DotProd(edgeCell.Center) > 0.1); 128 | Assert.True(!bulging.Contains(cornerCell)); 129 | Assert.True(!bulging.MayIntersect(cornerCell)); 130 | 131 | // A singleton cap. 132 | var singleton = S2Cap.FromAxisAngle(center, S1Angle.FromRadians(0)); 133 | JavaAssert.Equal(singleton.MayIntersect(rootCell), capFace == face); 134 | Assert.True(!singleton.MayIntersect(edgeCell)); 135 | Assert.True(!singleton.MayIntersect(cornerCell)); 136 | } 137 | } 138 | } 139 | 140 | [Test] 141 | public void S2CapBasicTest() 142 | { 143 | // Test basic properties of empty and full caps. 144 | var empty = S2Cap.Empty; 145 | var full = S2Cap.Full; 146 | Assert.True(empty.IsValid); 147 | Assert.True(empty.IsEmpty); 148 | Assert.True(empty.Complement.IsFull); 149 | Assert.True(full.IsValid); 150 | Assert.True(full.IsFull); 151 | Assert.True(full.Complement.IsEmpty); 152 | JavaAssert.Equal(full.Height, 2.0); 153 | assertDoubleNear(full.Angle.Degrees, 180); 154 | 155 | // Containment and intersection of empty and full caps. 156 | Assert.True(empty.Contains(empty)); 157 | Assert.True(full.Contains(empty)); 158 | Assert.True(full.Contains(full)); 159 | Assert.True(!empty.InteriorIntersects(empty)); 160 | Assert.True(full.InteriorIntersects(full)); 161 | Assert.True(!full.InteriorIntersects(empty)); 162 | 163 | // Singleton cap containing the x-axis. 164 | var xaxis = S2Cap.FromAxisHeight(new S2Point(1, 0, 0), 0); 165 | Assert.True(xaxis.Contains(new S2Point(1, 0, 0))); 166 | Assert.True(!xaxis.Contains(new S2Point(1, 1e-20, 0))); 167 | JavaAssert.Equal(xaxis.Angle.Radians, 0.0); 168 | 169 | // Singleton cap containing the y-axis. 170 | var yaxis = S2Cap.FromAxisAngle(new S2Point(0, 1, 0), S1Angle.FromRadians(0)); 171 | Assert.True(!yaxis.Contains(xaxis.Axis)); 172 | JavaAssert.Equal(xaxis.Height, 0.0); 173 | 174 | // Check that the complement of a singleton cap is the full cap. 175 | var xcomp = xaxis.Complement; 176 | Assert.True(xcomp.IsValid); 177 | Assert.True(xcomp.IsFull); 178 | Assert.True(xcomp.Contains(xaxis.Axis)); 179 | 180 | // Check that the complement of the complement is *not* the original. 181 | Assert.True(xcomp.Complement.IsValid); 182 | Assert.True(xcomp.Complement.IsEmpty); 183 | Assert.True(!xcomp.Complement.Contains(xaxis.Axis)); 184 | 185 | // Check that very small caps can be represented accurately. 186 | // Here "kTinyRad" is small enough that unit vectors perturbed by this 187 | // amount along a tangent do not need to be renormalized. 188 | var kTinyRad = 1e-10; 189 | var tiny = 190 | S2Cap.FromAxisAngle(S2Point.Normalize(new S2Point(1, 2, 3)), S1Angle.FromRadians(kTinyRad)); 191 | var tangent = S2Point.Normalize(S2Point.CrossProd(tiny.Axis, new S2Point(3, 2, 1))); 192 | Assert.True(tiny.Contains(tiny.Axis + (tangent* 0.99*kTinyRad))); 193 | Assert.True(!tiny.Contains(tiny.Axis + (tangent* 1.01*kTinyRad))); 194 | 195 | // Basic tests on a hemispherical cap. 196 | var hemi = S2Cap.FromAxisHeight(S2Point.Normalize(new S2Point(1, 0, 1)), 1); 197 | JavaAssert.Equal(hemi.Complement.Axis, -hemi.Axis); 198 | JavaAssert.Equal(hemi.Complement.Height, 1.0); 199 | Assert.True(hemi.Contains(new S2Point(1, 0, 0))); 200 | Assert.True(!hemi.Complement.Contains(new S2Point(1, 0, 0))); 201 | Assert.True(hemi.Contains(S2Point.Normalize(new S2Point(1, 0, -(1 - EPS))))); 202 | Assert.True(!hemi.InteriorContains(S2Point.Normalize(new S2Point(1, 0, -(1 + EPS))))); 203 | 204 | // A concave cap. 205 | var concave = S2Cap.FromAxisAngle(getLatLngPoint(80, 10), S1Angle.FromDegrees(150)); 206 | Assert.True(concave.Contains(getLatLngPoint(-70*(1 - EPS), 10))); 207 | Assert.True(!concave.Contains(getLatLngPoint(-70*(1 + EPS), 10))); 208 | Assert.True(concave.Contains(getLatLngPoint(-50*(1 - EPS), -170))); 209 | Assert.True(!concave.Contains(getLatLngPoint(-50*(1 + EPS), -170))); 210 | 211 | // Cap containment tests. 212 | Assert.True(!empty.Contains(xaxis)); 213 | Assert.True(!empty.InteriorIntersects(xaxis)); 214 | Assert.True(full.Contains(xaxis)); 215 | Assert.True(full.InteriorIntersects(xaxis)); 216 | Assert.True(!xaxis.Contains(full)); 217 | Assert.True(!xaxis.InteriorIntersects(full)); 218 | Assert.True(xaxis.Contains(xaxis)); 219 | Assert.True(!xaxis.InteriorIntersects(xaxis)); 220 | Assert.True(xaxis.Contains(empty)); 221 | Assert.True(!xaxis.InteriorIntersects(empty)); 222 | Assert.True(hemi.Contains(tiny)); 223 | Assert.True(hemi.Contains( 224 | S2Cap.FromAxisAngle(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.PiOver4 - EPS)))); 225 | Assert.True(!hemi.Contains( 226 | S2Cap.FromAxisAngle(new S2Point(1, 0, 0), S1Angle.FromRadians(S2.PiOver4 + EPS)))); 227 | Assert.True(concave.Contains(hemi)); 228 | Assert.True(concave.InteriorIntersects(hemi.Complement)); 229 | Assert.True(!concave.Contains(S2Cap.FromAxisHeight(-concave.Axis, 0.1))); 230 | } 231 | } 232 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2CellIdTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Google.Common.Geometry; 8 | using NUnit.Framework; 9 | 10 | namespace S2Geometry.Tests 11 | { 12 | public class S2CellIdTest : GeometryTestCase 13 | { 14 | private S2CellId getCellId(double latDegrees, double lngDegrees) 15 | { 16 | var id = S2CellId.FromLatLng(S2LatLng.FromDegrees(latDegrees, lngDegrees)); 17 | Trace.WriteLine(Convert.ToString(unchecked ((long)id.Id), 16)); 18 | return id; 19 | } 20 | 21 | public void testInverses() 22 | { 23 | Trace.WriteLine("TestInverses"); 24 | // Check the conversion of random leaf cells to S2LatLngs and back. 25 | for (var i = 0; i < 200000; ++i) 26 | { 27 | var id = getRandomCellId(S2CellId.MaxLevel); 28 | Assert.True(id.IsLeaf && id.Level == S2CellId.MaxLevel); 29 | var center = id.ToLatLng(); 30 | JavaAssert.Equal(S2CellId.FromLatLng(center).Id, id.Id); 31 | } 32 | } 33 | 34 | private const int kMaxExpandLevel = 3; 35 | 36 | private void expandCell( 37 | S2CellId parent, List cells, IDictionary parentMap) 38 | { 39 | cells.Add(parent); 40 | if (parent.Level == kMaxExpandLevel) 41 | { 42 | return; 43 | } 44 | var i = 0; 45 | var j = 0; 46 | int? orientation = 0; 47 | var face = parent.ToFaceIjOrientation(ref i, ref j, ref orientation); 48 | JavaAssert.Equal(face, parent.Face); 49 | 50 | var pos = 0; 51 | for (var child = parent.ChildBegin; !child.Equals(parent.ChildEnd); 52 | child = child.Next) 53 | { 54 | // Do some basic checks on the children 55 | JavaAssert.Equal(child.Level, parent.Level + 1); 56 | Assert.True(!child.IsLeaf); 57 | int? childOrientation = 0; 58 | JavaAssert.Equal(child.ToFaceIjOrientation(ref i, ref j, ref childOrientation), face); 59 | JavaAssert.Equal( 60 | childOrientation.Value, orientation.Value ^ S2.PosToOrientation(pos)); 61 | 62 | parentMap.Add(child, parent); 63 | expandCell(child, cells, parentMap); 64 | ++pos; 65 | } 66 | } 67 | 68 | private const int MAX_WALK_LEVEL = 8; 69 | 70 | public void testAllNeighbors(S2CellId id, int level) 71 | { 72 | Assert.True(level >= id.Level && level < S2CellId.MaxLevel); 73 | 74 | // We compute GetAllNeighbors, and then add in all the children of "id" 75 | // at the given level. We then compare this against the result of finding 76 | // all the vertex neighbors of all the vertices of children of "id" at the 77 | // given level. These should give the same result. 78 | var all = new List(); 79 | var expected = new List(); 80 | id.GetAllNeighbors(level, all); 81 | var end = id.ChildEndForLevel(level + 1); 82 | for (var c = id.ChildBeginForLevel(level + 1); !c.Equals(end); c = c.Next) 83 | { 84 | all.Add(c.Parent); 85 | c.GetVertexNeighbors(level, expected); 86 | } 87 | // Sort the results and eliminate duplicates. 88 | all.Sort(); 89 | expected.Sort(); 90 | ISet allSet = new HashSet(all); 91 | ISet expectedSet = new HashSet(expected); 92 | var result = allSet.SetEquals(expectedSet); 93 | Assert.True(result); 94 | } 95 | 96 | [Test] 97 | public void S2CellIdTestBasic() 98 | { 99 | Trace.WriteLine("TestBasic"); 100 | // Check default constructor. 101 | var id = new S2CellId(); 102 | //JavaAssert.Equal(id.id(), 0); 103 | //Assert.True(!id.isValid()); 104 | 105 | // Check basic accessor methods. 106 | id = S2CellId.FromFacePosLevel(3, 0x12345678, S2CellId.MaxLevel - 4); 107 | //Assert.True(id.isValid()); 108 | //JavaAssert.Equal(id.face(), 3); 109 | // JavaAssert.Equal(id.pos(), 0x12345700); 110 | //JavaAssert.Equal(id.level(), S2CellId.MAX_LEVEL - 4); 111 | //Assert.True(!id.isLeaf()); 112 | 113 | //// Check face definitions 114 | //JavaAssert.Equal(getCellId(0, 0).face(), 0); 115 | //JavaAssert.Equal(getCellId(0, 90).face(), 1); 116 | //JavaAssert.Equal(getCellId(90, 0).face(), 2); 117 | //JavaAssert.Equal(getCellId(0, 180).face(), 3); 118 | //JavaAssert.Equal(getCellId(0, -90).face(), 4); 119 | //JavaAssert.Equal(getCellId(-90, 0).face(), 5); 120 | 121 | //// Check parent/child relationships. 122 | //JavaAssert.Equal(id.childBegin(id.level() + 2).pos(), 0x12345610); 123 | //JavaAssert.Equal(id.childBegin().pos(), 0x12345640); 124 | //JavaAssert.Equal(id.parent().pos(), 0x12345400); 125 | //JavaAssert.Equal(id.parent(id.level() - 2).pos(), 0x12345000); 126 | 127 | //// Check ordering of children relative to parents. 128 | //Assert.True(id.childBegin().lessThan(id)); 129 | //var childEnd = id.childEnd(); 130 | //var childId = childEnd.id(); 131 | //var id1 = id.id(); 132 | 133 | //Assert.True(id.childEnd().greaterThan(id)); 134 | //JavaAssert.Equal(id.childBegin().next().next().next().next(), id.childEnd()); 135 | //JavaAssert.Equal(id.childBegin(S2CellId.MAX_LEVEL), id.rangeMin()); 136 | //JavaAssert.Equal(id.childEnd(S2CellId.MAX_LEVEL), id.rangeMax().next()); 137 | 138 | // Check wrapping from beginning of Hilbert curve to end and vice versa. 139 | // JavaAssert.Equal(S2CellId.begin(0).prevWrap(), S2CellId.end(0).prev()); 140 | 141 | JavaAssert.Equal(S2CellId.Begin(S2CellId.MaxLevel).PreviousWithWrap, 142 | S2CellId.FromFacePosLevel(5, ~0UL >> S2CellId.FaceBits, S2CellId.MaxLevel)); 143 | 144 | JavaAssert.Equal(S2CellId.End(4).Previous.NextWithWrap, S2CellId.Begin(4)); 145 | JavaAssert.Equal(S2CellId.End(S2CellId.MaxLevel).Previous.NextWithWrap, 146 | S2CellId.FromFacePosLevel(0, 0, S2CellId.MaxLevel)); 147 | 148 | // Check that cells are represented by the position of their center 149 | // along the Hilbert curve. 150 | JavaAssert.Equal(id.RangeMin.Id + id.RangeMax.Id, 2*id.Id); 151 | } 152 | 153 | [Test] 154 | public void testContainment() 155 | { 156 | Trace.WriteLine("TestContainment"); 157 | IDictionary parentMap = new Dictionary(); 158 | var cells = new List(); 159 | for (var face = 0; face < 6; ++face) 160 | { 161 | expandCell(S2CellId.FromFacePosLevel(face, 0, 0), cells, parentMap); 162 | } 163 | for (var i = 0; i < cells.Count; ++i) 164 | { 165 | for (var j = 0; j < cells.Count; ++j) 166 | { 167 | var contained = true; 168 | for (var id = cells[j]; id != cells[i]; id = parentMap[id]) 169 | { 170 | if (!parentMap.ContainsKey(id)) 171 | { 172 | contained = false; 173 | break; 174 | } 175 | } 176 | JavaAssert.Equal(cells[i].Contains(cells[j]), contained); 177 | JavaAssert.Equal(cells[j] >= cells[i].RangeMin 178 | && cells[j] <= cells[i].RangeMax, contained); 179 | JavaAssert.Equal(cells[i].Intersects(cells[j]), 180 | cells[i].Contains(cells[j]) || cells[j].Contains(cells[i])); 181 | } 182 | } 183 | } 184 | 185 | [Test] 186 | public void testContinuity() 187 | { 188 | Trace.WriteLine("TestContinuity"); 189 | // Make sure that sequentially increasing cell ids form a continuous 190 | // path over the surface of the sphere, i.e. there are no 191 | // discontinuous jumps from one region to another. 192 | 193 | var maxDist = S2Projections.MaxEdge.GetValue(MAX_WALK_LEVEL); 194 | var end = S2CellId.End(MAX_WALK_LEVEL); 195 | var id = S2CellId.Begin(MAX_WALK_LEVEL); 196 | for (; !id.Equals(end); id = id.Next) 197 | { 198 | Assert.True(id.ToPointRaw().Angle(id.NextWithWrap.ToPointRaw()) <= maxDist); 199 | 200 | // Check that the ToPointRaw() returns the center of each cell 201 | // in (s,t) coordinates. 202 | var p = id.ToPointRaw(); 203 | var face = S2Projections.XyzToFace(p); 204 | var uv = S2Projections.ValidFaceXyzToUv(face, p); 205 | assertDoubleNear(Math.IEEERemainder( 206 | S2Projections.UvToSt(uv.X), 1.0/(1 << MAX_WALK_LEVEL)), 0); 207 | assertDoubleNear(Math.IEEERemainder( 208 | S2Projections.UvToSt(uv.Y), 1.0/(1 << MAX_WALK_LEVEL)), 0); 209 | } 210 | } 211 | 212 | [Test] 213 | public void testCoverage() 214 | { 215 | Trace.WriteLine("TestCoverage"); 216 | // Make sure that random points on the sphere can be represented to the 217 | // expected level of accuracy, which in the worst case is sqrt(2/3) times 218 | // the maximum arc length between the points on the sphere associated with 219 | // adjacent values of "i" or "j". (It is sqrt(2/3) rather than 1/2 because 220 | // the cells at the corners of each face are stretched -- they have 60 and 221 | // 120 degree angles.) 222 | 223 | var maxDist = 0.5*S2Projections.MaxDiag.GetValue(S2CellId.MaxLevel); 224 | for (var i = 0; i < 1000000; ++i) 225 | { 226 | // randomPoint(); 227 | var p = new S2Point(0.37861576725894824, 0.2772406863275093, 0.8830558887338725); 228 | var q = S2CellId.FromPoint(p).ToPointRaw(); 229 | 230 | Assert.True(p.Angle(q) <= maxDist); 231 | } 232 | } 233 | 234 | //[Test] 235 | [Ignore("Not necessrily valid values. Fails on Java too.")] 236 | public void testNeighborLevel29() 237 | { 238 | // Note: These parameters fail on the Java version too. Not sure if this is a valid Cell anyway 239 | testAllNeighbors(new S2CellId(0x6000000000000004UL), 29); 240 | } 241 | 242 | [Test] 243 | public void testNeighbors() 244 | { 245 | Trace.WriteLine("TestNeighbors"); 246 | 247 | // Check the edge neighbors of face 1. 248 | int[] outFaces = {5, 3, 2, 0}; 249 | 250 | var faceNbrs = S2CellId.FromFacePosLevel(1, 0, 0).GetEdgeNeighbors(); 251 | for (var i = 0; i < 4; ++i) 252 | { 253 | Assert.True(faceNbrs[i].IsFace); 254 | JavaAssert.Equal(faceNbrs[i].Face, outFaces[i]); 255 | } 256 | 257 | // Check the vertex neighbors of the center of face 2 at level 5. 258 | var nbrs = new List(); 259 | S2CellId.FromPoint(new S2Point(0, 0, 1)).GetVertexNeighbors(5, nbrs); 260 | nbrs.Sort(); 261 | for (var i = 0; i < 4; ++i) 262 | { 263 | JavaAssert.Equal(nbrs[i], S2CellId.FromFaceIj( 264 | 2, (1 << 29) - (i < 2 ? 1 : 0), (1 << 29) - ((i == 0 || i == 3) ? 1 : 0)).ParentForLevel(5)); 265 | } 266 | nbrs.Clear(); 267 | 268 | // Check the vertex neighbors of the corner of faces 0, 4, and 5. 269 | var id = S2CellId.FromFacePosLevel(0, 0, S2CellId.MaxLevel); 270 | id.GetVertexNeighbors(0, nbrs); 271 | nbrs.Sort(); 272 | 273 | JavaAssert.Equal(nbrs.Count, 3); 274 | JavaAssert.Equal(nbrs[0], S2CellId.FromFacePosLevel(0, 0, 0)); 275 | JavaAssert.Equal(nbrs[1], S2CellId.FromFacePosLevel(4, 0, 0)); 276 | JavaAssert.Equal(nbrs[2], S2CellId.FromFacePosLevel(5, 0, 0)); 277 | 278 | // Check that GetAllNeighbors produces results that are consistent 279 | // with GetVertexNeighbors for a bunch of random cells. 280 | for (var i = 0; i < 1000; ++i) 281 | { 282 | var id1 = getRandomCellId(); 283 | var toTest = id1; 284 | if (id1.IsLeaf) 285 | { 286 | toTest = id1.Parent; 287 | } 288 | 289 | // TestAllNeighbors computes approximately 2**(2*(diff+1)) cell id1s, 290 | // so it's not reasonable to use large values of "diff". 291 | var maxDiff = Math.Min(6, S2CellId.MaxLevel - toTest.Level - 1); 292 | var level = toTest.Level + random(maxDiff); 293 | testAllNeighbors(toTest, level); 294 | } 295 | } 296 | 297 | [Test] 298 | public void testToToken() 299 | { 300 | JavaAssert.Equal("000000000000010a", new S2CellId(266).ToToken()); 301 | JavaAssert.Equal("80855c", new S2CellId(unchecked ((ulong)-9185834709882503168L)).ToToken()); 302 | } 303 | 304 | [Test] 305 | public void testTokens() 306 | { 307 | Trace.WriteLine("TestTokens"); 308 | 309 | // Test random cell ids at all levels. 310 | for (var i = 0; i < 10000; ++i) 311 | { 312 | var id = getRandomCellId(); 313 | if (!id.IsValid) 314 | { 315 | continue; 316 | } 317 | var token = id.ToToken(); 318 | Assert.True(token.Length <= 16); 319 | JavaAssert.Equal(S2CellId.FromToken(token), id); 320 | } 321 | // Check that invalid cell ids can be encoded. 322 | var token1 = S2CellId.None.ToToken(); 323 | JavaAssert.Equal(S2CellId.FromToken(token1), S2CellId.None); 324 | } 325 | } 326 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2CellUnionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2CellUnionTest : GeometryTestCase 12 | { 13 | // Test helper methods for testing the traversal order. 14 | private static int swapAxes(int ij) 15 | { 16 | return ((ij >> 1) & 1) + ((ij & 1) << 1); 17 | } 18 | 19 | private static int invertBits(int ij) 20 | { 21 | return ij ^ 3; 22 | } 23 | 24 | // Note: obviously, I could have defined a bundle of metrics like this in the 25 | // S2 class itself rather than just for testing. However, it's not clear that 26 | // this is useful other than for testing purposes, and I find 27 | // S2.kMinWidth.GetMaxLevel(width) to be slightly more readable than 28 | // than S2.kWidth.Min().GetMaxLevel(width). Also, there is no fundamental 29 | // reason that we need to analyze the minimum, maximum, and average values of 30 | // every metric; it would be perfectly reasonable to just define one of these. 31 | 32 | public class MetricBundle 33 | { 34 | public S2CellMetric avg_; 35 | public S2CellMetric max_; 36 | public S2CellMetric min_; 37 | 38 | public MetricBundle(S2CellMetric Min, S2CellMetric Max, S2CellMetric avg) 39 | { 40 | min_ = Min; 41 | max_ = Max; 42 | avg_ = avg; 43 | } 44 | } 45 | 46 | public void testMinMaxAvg(MetricBundle bundle) 47 | { 48 | assertTrue(bundle.min_.Deriv() < bundle.avg_.Deriv()); 49 | assertTrue(bundle.avg_.Deriv() < bundle.max_.Deriv()); 50 | } 51 | 52 | public void testLessOrEqual(MetricBundle a, MetricBundle b) 53 | { 54 | assertTrue(a.min_.Deriv() <= b.min_.Deriv()); 55 | assertTrue(a.max_.Deriv() <= b.max_.Deriv()); 56 | assertTrue(a.avg_.Deriv() <= b.avg_.Deriv()); 57 | } 58 | 59 | [Test] 60 | public void testAngleArea() 61 | { 62 | var pz = new S2Point(0, 0, 1); 63 | var p000 = new S2Point(1, 0, 0); 64 | var p045 = new S2Point(1, 1, 0); 65 | var p090 = new S2Point(0, 1, 0); 66 | var p180 = new S2Point(-1, 0, 0); 67 | assertDoubleNear(S2.Angle(p000, pz, p045), S2.PiOver4); 68 | assertDoubleNear(S2.Angle(p045, pz, p180), 3*S2.PiOver4); 69 | assertDoubleNear(S2.Angle(p000, pz, p180), S2.Pi); 70 | assertDoubleNear(S2.Angle(pz, p000, pz), 0); 71 | assertDoubleNear(S2.Angle(pz, p000, p045), S2.PiOver2); 72 | 73 | assertDoubleNear(S2.Area(p000, p090, pz), S2.PiOver2); 74 | assertDoubleNear(S2.Area(p045, pz, p180), 3*S2.PiOver4); 75 | 76 | // Make sure that area() has good *relative* accuracy even for 77 | // very small areas. 78 | var eps = 1e-10; 79 | var pepsx = new S2Point(eps, 0, 1); 80 | var pepsy = new S2Point(0, eps, 1); 81 | var expected1 = 0.5*eps*eps; 82 | assertDoubleNear(S2.Area(pepsx, pepsy, pz), expected1, 1e-14*expected1); 83 | 84 | // Make sure that it can handle degenerate triangles. 85 | var pr = new S2Point(0.257, -0.5723, 0.112); 86 | var pq = new S2Point(-0.747, 0.401, 0.2235); 87 | assertEquals(S2.Area(pr, pr, pr), 0.0); 88 | // TODO: The following test is not exact in optimized mode because the 89 | // compiler chooses to mix 64-bit and 80-bit intermediate results. 90 | assertDoubleNear(S2.Area(pr, pq, pr), 0); 91 | assertEquals(S2.Area(p000, p045, p090), 0.0); 92 | 93 | double maxGirard = 0; 94 | for (var i = 0; i < 10000; ++i) 95 | { 96 | var p0 = randomPoint(); 97 | var d1 = randomPoint(); 98 | var d2 = randomPoint(); 99 | var p1 = p0 + (d1 * 1e-15); 100 | var p2 = p0 + (d2 * 1e-15); 101 | // The actual displacement can be as much as 1.2e-15 due to roundoff. 102 | // This yields a maximum triangle area of about 0.7e-30. 103 | assertTrue(S2.Area(p0, p1, p2) < 0.7e-30); 104 | maxGirard = Math.Max(maxGirard, S2.GirardArea(p0, p1, p2)); 105 | } 106 | Console.WriteLine("Worst case Girard for triangle area 1e-30: " + maxGirard); 107 | 108 | // Try a very long and skinny triangle. 109 | var p045eps = new S2Point(1, 1, eps); 110 | var expected2 = 5.8578643762690495119753e-11; // Mathematica. 111 | assertDoubleNear(S2.Area(p000, p045eps, p090), expected2, 1e-9*expected2); 112 | 113 | // Triangles with near-180 degree edges that sum to a quarter-sphere. 114 | var eps2 = 1e-10; 115 | var p000eps2 = new S2Point(1, 0.1*eps2, eps2); 116 | var quarterArea1 = 117 | S2.Area(p000eps2, p000, p090) + S2.Area(p000eps2, p090, p180) + S2.Area(p000eps2, p180, pz) 118 | + S2.Area(p000eps2, pz, p000); 119 | assertDoubleNear(quarterArea1, S2.Pi); 120 | 121 | // Four other triangles that sum to a quarter-sphere. 122 | var p045eps2 = new S2Point(1, 1, eps2); 123 | var quarterArea2 = 124 | S2.Area(p045eps2, p000, p090) + S2.Area(p045eps2, p090, p180) + S2.Area(p045eps2, p180, pz) 125 | + S2.Area(p045eps2, pz, p000); 126 | assertDoubleNear(quarterArea2, S2.Pi); 127 | } 128 | 129 | [Test] 130 | public void testCCW() 131 | { 132 | var a = new S2Point(0.72571927877036835, 0.46058825605889098, 0.51106749730504852); 133 | var b = new S2Point(0.7257192746638208, 0.46058826573818168, 0.51106749441312738); 134 | var c = new S2Point(0.72571927671709457, 0.46058826089853633, 0.51106749585908795); 135 | assertTrue(S2.RobustCcw(a, b, c) != 0); 136 | } 137 | 138 | [Test] 139 | public void testExp() 140 | { 141 | for (var i = 0; i < 10; ++i) 142 | { 143 | assertEquals(i + 1, S2.Exp(Math.Pow(2, i))); 144 | } 145 | 146 | for (var i = 0; i < 10; ++i) 147 | { 148 | assertEquals(i + 1, S2.Exp(-Math.Pow(2, i))); 149 | } 150 | 151 | assertEquals(0, S2.Exp(0)); 152 | assertEquals(2, S2.Exp(3)); 153 | assertEquals(3, S2.Exp(5)); 154 | } 155 | 156 | [Test] 157 | public void testFaceUVtoXYZ() 158 | { 159 | // Check that each face appears exactly once. 160 | var sum = new S2Point(); 161 | for (var face = 0; face < 6; ++face) 162 | { 163 | var center = S2Projections.FaceUvToXyz(face, 0, 0); 164 | assertEquals(S2Projections.GetNorm(face), center); 165 | assertEquals(Math.Abs(center[center.LargestAbsComponent]), 1.0); 166 | sum = sum + S2Point.Fabs(center); 167 | } 168 | assertEquals(sum, new S2Point(2, 2, 2)); 169 | 170 | // Check that each face has a right-handed coordinate system. 171 | for (var face = 0; face < 6; ++face) 172 | { 173 | assertEquals( 174 | S2Point.CrossProd(S2Projections.GetUAxis(face), S2Projections.GetVAxis(face)).DotProd( 175 | S2Projections.FaceUvToXyz(face, 0, 0)), 1.0); 176 | } 177 | 178 | // Check that the Hilbert curves on each face combine to form a 179 | // continuous curve over the entire cube. 180 | for (var face = 0; face < 6; ++face) 181 | { 182 | // The Hilbert curve on each face starts at (-1,-1) and terminates 183 | // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped). 184 | var sign = ((face & S2.SwapMask) != 0) ? -1 : 1; 185 | assertEquals(S2Projections.FaceUvToXyz(face, sign, -sign), 186 | S2Projections.FaceUvToXyz((face + 1)%6, -1, -1)); 187 | } 188 | } 189 | 190 | [Test] 191 | public void testMetrics() 192 | { 193 | var angleSpan = new MetricBundle( 194 | S2Projections.MinAngleSpan, S2Projections.MaxAngleSpan, S2Projections.AvgAngleSpan); 195 | var width = 196 | new MetricBundle(S2Projections.MinWidth, S2Projections.MaxWidth, S2Projections.AvgWidth); 197 | var edge = 198 | new MetricBundle(S2Projections.MinEdge, S2Projections.MaxEdge, S2Projections.AvgEdge); 199 | var diag = 200 | new MetricBundle(S2Projections.MinDiag, S2Projections.MaxDiag, S2Projections.AvgDiag); 201 | var area = 202 | new MetricBundle(S2Projections.MinArea, S2Projections.MaxArea, S2Projections.AvgArea); 203 | 204 | // First, check that Min <= avg <= Max for each metric. 205 | testMinMaxAvg(angleSpan); 206 | testMinMaxAvg(width); 207 | testMinMaxAvg(edge); 208 | testMinMaxAvg(diag); 209 | testMinMaxAvg(area); 210 | 211 | // Check that the maximum aspect ratio of an individual cell is consistent 212 | // with the global minimums and maximums. 213 | assertTrue(S2Projections.MaxEdgeAspect >= 1.0); 214 | assertTrue(S2Projections.MaxEdgeAspect 215 | < S2Projections.MaxEdge.Deriv()/S2Projections.MinEdge.Deriv()); 216 | assertTrue(S2Projections.MaxDiagAspect >= 1); 217 | assertTrue(S2Projections.MaxDiagAspect 218 | < S2Projections.MaxDiag.Deriv()/S2Projections.MinDiag.Deriv()); 219 | 220 | // Check various conditions that are provable mathematically. 221 | testLessOrEqual(width, angleSpan); 222 | testLessOrEqual(width, edge); 223 | testLessOrEqual(edge, diag); 224 | 225 | assertTrue(S2Projections.MinArea.Deriv() 226 | >= S2Projections.MinWidth.Deriv()*S2Projections.MinEdge.Deriv() - 1e-15); 227 | assertTrue(S2Projections.MaxArea.Deriv() 228 | < S2Projections.MaxWidth.Deriv()*S2Projections.MaxEdge.Deriv() + 1e-15); 229 | 230 | // GetMinLevelForLength() and friends have built-in assertions, we just need 231 | // to call these functions to test them. 232 | // 233 | // We don't actually check that the metrics are correct here, e.g. that 234 | // GetMinWidth(10) is a lower bound on the width of cells at level 10. 235 | // It is easier to check these properties in s2cell_unittest, since 236 | // S2Cell has methods to compute the cell vertices, etc. 237 | 238 | for (var level = -2; level <= S2CellId.MaxLevel + 3; ++level) 239 | { 240 | var dWidth = (2*S2Projections.MinWidth.Deriv())*Math.Pow(2, -level); 241 | if (level >= S2CellId.MaxLevel + 3) 242 | { 243 | dWidth = 0; 244 | } 245 | 246 | // Check boundary cases (exactly equal to a threshold value). 247 | var expectedLevel = Math.Max(0, Math.Min(S2CellId.MaxLevel, level)); 248 | assertEquals(S2Projections.MinWidth.GetMinLevel(dWidth), expectedLevel); 249 | assertEquals(S2Projections.MinWidth.GetMaxLevel(dWidth), expectedLevel); 250 | assertEquals(S2Projections.MinWidth.GetClosestLevel(dWidth), expectedLevel); 251 | 252 | // Also check non-boundary cases. 253 | assertEquals(S2Projections.MinWidth.GetMinLevel(1.2*dWidth), expectedLevel); 254 | assertEquals(S2Projections.MinWidth.GetMaxLevel(0.8*dWidth), expectedLevel); 255 | assertEquals(S2Projections.MinWidth.GetClosestLevel(1.2*dWidth), expectedLevel); 256 | assertEquals(S2Projections.MinWidth.GetClosestLevel(0.8*dWidth), expectedLevel); 257 | 258 | // Same thing for area1. 259 | var area1 = (4*S2Projections.MinArea.Deriv())*Math.Pow(4, -level); 260 | if (level <= -3) 261 | { 262 | area1 = 0; 263 | } 264 | assertEquals(S2Projections.MinArea.GetMinLevel(area1), expectedLevel); 265 | assertEquals(S2Projections.MinArea.GetMaxLevel(area1), expectedLevel); 266 | assertEquals(S2Projections.MinArea.GetClosestLevel(area1), expectedLevel); 267 | assertEquals(S2Projections.MinArea.GetMinLevel(1.2*area1), expectedLevel); 268 | assertEquals(S2Projections.MinArea.GetMaxLevel(0.8*area1), expectedLevel); 269 | assertEquals(S2Projections.MinArea.GetClosestLevel(1.2*area1), expectedLevel); 270 | assertEquals(S2Projections.MinArea.GetClosestLevel(0.8*area1), expectedLevel); 271 | } 272 | } 273 | 274 | [Test] 275 | public void testSTUV() 276 | { 277 | // Check boundary conditions. 278 | for (double x = -1; x <= 1; ++x) 279 | { 280 | assertEquals(S2Projections.StToUv(x), x); 281 | assertEquals(S2Projections.UvToSt(x), x); 282 | } 283 | // Check that UVtoST and STtoUV are inverses. 284 | for (double x = -1; x <= 1; x += 0.0001) 285 | { 286 | assertDoubleNear(S2Projections.UvToSt(S2Projections.StToUv(x)), x); 287 | assertDoubleNear(S2Projections.StToUv(S2Projections.UvToSt(x)), x); 288 | } 289 | } 290 | 291 | [Test] 292 | public void testTraversalOrder() 293 | { 294 | for (var r = 0; r < 4; ++r) 295 | { 296 | for (var i = 0; i < 4; ++i) 297 | { 298 | // Check consistency with respect to swapping axes. 299 | assertEquals(S2.IjToPos(r, i), 300 | S2.IjToPos(r ^ S2.SwapMask, swapAxes(i))); 301 | assertEquals(S2.PosToIj(r, i), 302 | swapAxes(S2.PosToIj(r ^ S2.SwapMask, i))); 303 | 304 | // Check consistency with respect to reversing axis directions. 305 | assertEquals(S2.IjToPos(r, i), 306 | S2.IjToPos(r ^ S2.InvertMask, invertBits(i))); 307 | assertEquals(S2.PosToIj(r, i), 308 | invertBits(S2.PosToIj(r ^ S2.InvertMask, i))); 309 | 310 | // Check that the two tables are inverses of each other. 311 | assertEquals(S2.IjToPos(r, S2.PosToIj(r, i)), i); 312 | assertEquals(S2.PosToIj(r, S2.IjToPos(r, i)), i); 313 | } 314 | } 315 | } 316 | 317 | [Test] 318 | public void testUVAxes() 319 | { 320 | // Check that axes are consistent with FaceUVtoXYZ. 321 | for (var face = 0; face < 6; ++face) 322 | { 323 | assertEquals(S2Projections.GetUAxis(face), 324 | S2Projections.FaceUvToXyz(face, 1, 0) - S2Projections.FaceUvToXyz(face, 0, 0)); 325 | assertEquals(S2Projections.GetVAxis(face), 326 | S2Projections.FaceUvToXyz(face, 0, 1) - S2Projections.FaceUvToXyz(face, 0, 0)); 327 | } 328 | } 329 | 330 | [Test] 331 | public void testUVNorms() 332 | { 333 | // Check that GetUNorm and GetVNorm compute right-handed normals for 334 | // an edge in the increasing U or V direction. 335 | for (var face = 0; face < 6; ++face) 336 | { 337 | for (double x = -1; x <= 1; x += 1/1024.0) 338 | { 339 | assertDoubleNear( 340 | S2Point.CrossProd( 341 | S2Projections.FaceUvToXyz(face, x, -1), S2Projections.FaceUvToXyz(face, x, 1)) 342 | .Angle(S2Projections.GetUNorm(face, x)), 0); 343 | assertDoubleNear( 344 | S2Point.CrossProd( 345 | S2Projections.FaceUvToXyz(face, -1, x), S2Projections.FaceUvToXyz(face, 1, x)) 346 | .Angle(S2Projections.GetVNorm(face, x)), 0); 347 | } 348 | } 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2EdgeIndexTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2EdgeIndexTest : GeometryTestCase 12 | { 13 | public class EdgeVectorIndex : S2EdgeIndex 14 | { 15 | private readonly List edges; 16 | 17 | public EdgeVectorIndex(List edges) 18 | { 19 | this.edges = edges; 20 | } 21 | 22 | 23 | protected override int NumEdges 24 | { 25 | get { return edges.Count; } 26 | } 27 | 28 | 29 | protected override S2Point EdgeFrom(int index) 30 | { 31 | return edges[index].Start; 32 | } 33 | 34 | 35 | protected override S2Point EdgeTo(int index) 36 | { 37 | return edges[index].End; 38 | } 39 | } 40 | 41 | /** 42 | * Generates a random edge whose center is in the given cap. 43 | */ 44 | 45 | private S2Edge randomEdgeCrossingCap(double maxLengthMeters, S2Cap cap) 46 | { 47 | // Pick the edge center at random. 48 | var edgeCenter = samplePoint(cap); 49 | // Pick two random points in a suitably sized cap about the edge center. 50 | var edgeCap = S2Cap.FromAxisAngle( 51 | edgeCenter, S1Angle.FromRadians(maxLengthMeters/S2LatLng.EarthRadiusMeters/2)); 52 | var p1 = samplePoint(edgeCap); 53 | var p2 = samplePoint(edgeCap); 54 | return new S2Edge(p1, p2); 55 | } 56 | 57 | /* 58 | * Generates "numEdges" random edges, of length at most "edgeLengthMetersMax" 59 | * and each of whose center is in a randomly located cap with radius 60 | * "capSpanMeters", and puts results into "edges". 61 | */ 62 | 63 | private void generateRandomEarthEdges( 64 | double edgeLengthMetersMax, double capSpanMeters, int numEdges, List edges) 65 | { 66 | var cap = S2Cap.FromAxisAngle( 67 | randomPoint(), S1Angle.FromRadians(capSpanMeters/S2LatLng.EarthRadiusMeters)); 68 | for (var i = 0; i < numEdges; ++i) 69 | { 70 | edges.Add(randomEdgeCrossingCap(edgeLengthMetersMax, cap)); 71 | } 72 | } 73 | 74 | private void checkAllCrossings( 75 | List allEdges, int minCrossings, int maxChecksCrossingsRatio) 76 | { 77 | var index = new EdgeVectorIndex(allEdges); 78 | index.ComputeIndex(); 79 | var it = new S2EdgeIndex.DataEdgeIterator(index); 80 | double totalCrossings = 0; 81 | double totalIndexChecks = 0; 82 | 83 | for (var @in = 0; @in < allEdges.Count; ++@in) 84 | { 85 | var e = allEdges[@in]; 86 | 87 | var candidateSet = new HashSet(); 88 | 89 | var sb = new StringBuilder(); 90 | it.GetCandidates(e.Start, e.End); 91 | foreach (var i in it)// it.GetCandidates(e.Start, e.End); it.HasNext; it.Next()) 92 | { 93 | candidateSet.Add(i); 94 | sb.Append(i).Append("/"); 95 | ++totalIndexChecks; 96 | } 97 | 98 | for (var i = 0; i < allEdges.Count; ++i) 99 | { 100 | var crossing = S2EdgeUtil.RobustCrossing( 101 | e.Start, e.End, allEdges[i].Start, allEdges[i].End); 102 | if (crossing >= 0) 103 | { 104 | var sbError = new StringBuilder(); 105 | sbError 106 | .Append("\n==CHECK_ERROR===================================\n") 107 | .Append("CandidateSet: ") 108 | .Append(sb) 109 | .Append("\nin=") 110 | .Append(@in) 111 | .Append(" i=") 112 | .Append(i) 113 | .Append(" robustCrossing=") 114 | .Append(crossing) 115 | .Append("\nfrom:\n") 116 | .Append(e) 117 | .Append("\nto:\n") 118 | .Append(allEdges[i]) 119 | .Append("\n=================================================="); 120 | assertTrue(sbError.ToString(), candidateSet.Contains(i)); 121 | ++totalCrossings; 122 | } 123 | } 124 | } 125 | 126 | Console.WriteLine( 127 | "Pairs/num crossings/check crossing ratio: " 128 | + (allEdges.Count*allEdges.Count) + "/" 129 | + totalCrossings + "/" 130 | + (totalIndexChecks/totalCrossings)); 131 | assertTrue(minCrossings <= totalCrossings); 132 | assertTrue(totalCrossings*maxChecksCrossingsRatio >= totalIndexChecks); 133 | } 134 | 135 | /* 136 | * Generates random edges and tests, for each edge, that all those that cross 137 | * are candidates. 138 | */ 139 | 140 | private void tryCrossingsRandomInCap(int numEdges, double edgeLengthMax, double capSpanMeters, 141 | int minCrossings, int maxChecksCrossingsRatio) 142 | { 143 | var allEdges = new List(); 144 | generateRandomEarthEdges(edgeLengthMax, capSpanMeters, numEdges, allEdges); 145 | checkAllCrossings(allEdges, minCrossings, maxChecksCrossingsRatio); 146 | } 147 | 148 | [Test] 149 | public void testLoopCandidateOfItself() 150 | { 151 | var ps = new List(); // A diamond loop around 0,180. 152 | ps.Add(makePoint("0:178")); 153 | ps.Add(makePoint("-1:180")); 154 | ps.Add(makePoint("0:-179")); 155 | ps.Add(makePoint("1:-180")); 156 | var allEdges = new List(); 157 | for (var i = 0; i < 4; ++i) 158 | { 159 | allEdges.Add(new S2Edge(ps[i], ps[(i + 1)%4])); 160 | } 161 | checkAllCrossings(allEdges, 0, 16); 162 | } 163 | 164 | [Test] 165 | public void testRandomEdgeCrossings() 166 | { 167 | tryCrossingsRandomInCap(2000, 30, 5000, 500, 2); 168 | tryCrossingsRandomInCap(1000, 100, 5000, 500, 3); 169 | tryCrossingsRandomInCap(1000, 1000, 5000, 1000, 40); 170 | tryCrossingsRandomInCap(500, 5000, 5000, 5000, 20); 171 | } 172 | 173 | [Test] 174 | public void testRandomEdgeCrossingsSparse() 175 | { 176 | for (var i = 0; i < 5; ++i) 177 | { 178 | tryCrossingsRandomInCap(2000, 100, 5000, 500, 8); 179 | tryCrossingsRandomInCap(2000, 300, 50000, 1000, 10); 180 | } 181 | } 182 | 183 | [Test] 184 | public void testSpecificEdges() 185 | { 186 | var ps = new List(); 187 | ps.Add(new S2Point(0.8088625416501157, -0.40633615485481134, 0.4250086092929434)); 188 | ps.Add(new S2Point(0.8088939911085784, -0.40631384442755236, 0.4249700824469155)); 189 | ps.Add(new S2Point(0.8088088971141814, -0.40642839367135375, 0.425022503835579)); 190 | ps.Add(new S2Point(0.8088643962606756, -0.406333410696549, 0.4250077032402616)); 191 | var allEdges = new List(); 192 | allEdges.Add(new S2Edge(ps[0], ps[1])); 193 | allEdges.Add(new S2Edge(ps[2], ps[3])); 194 | checkAllCrossings(allEdges, 0, 16); 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2Geometry.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30} 7 | Library 8 | Properties 9 | S2Geometry.Tests 10 | S2Geometry.Tests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\ 20 | true 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {5e82f402-f876-4a29-b59e-1ba21591a44a} 74 | S2Geometry 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | False 85 | 86 | 87 | False 88 | 89 | 90 | False 91 | 92 | 93 | False 94 | 95 | 96 | 97 | 98 | 99 | 100 | 107 | -------------------------------------------------------------------------------- /S2Geometry.Tests/S2LatLngTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2LatLngTest : GeometryTestCase 12 | { 13 | [Test] 14 | public void testBasic() 15 | { 16 | var llRad = S2LatLng.FromRadians(S2.PiOver4, S2.PiOver2); 17 | assertTrue(llRad.Lat.Radians == S2.PiOver4); 18 | assertTrue(llRad.Lng.Radians == S2.PiOver2); 19 | assertTrue(llRad.IsValid); 20 | var llDeg = S2LatLng.FromDegrees(45, 90); 21 | assertEquals(llDeg, llRad); 22 | assertTrue(llDeg.IsValid); 23 | assertTrue(!S2LatLng.FromDegrees(-91, 0).IsValid); 24 | assertTrue(!S2LatLng.FromDegrees(0, 181).IsValid); 25 | 26 | var bad = S2LatLng.FromDegrees(120, 200); 27 | assertTrue(!bad.IsValid); 28 | var better = bad.Normalized; 29 | assertTrue(better.IsValid); 30 | assertEquals(better.Lat, S1Angle.FromDegrees(90)); 31 | assertDoubleNear(better.Lng.Radians, S1Angle.FromDegrees(-160).Radians); 32 | 33 | bad = S2LatLng.FromDegrees(-100, -360); 34 | assertTrue(!bad.IsValid); 35 | better = bad.Normalized; 36 | assertTrue(better.IsValid); 37 | assertEquals(better.Lat, S1Angle.FromDegrees(-90)); 38 | assertDoubleNear(better.Lng.Radians, 0); 39 | 40 | assertTrue((S2LatLng.FromDegrees(10, 20) + S2LatLng.FromDegrees(20, 30)).ApproxEquals( 41 | S2LatLng.FromDegrees(30, 50))); 42 | assertTrue((S2LatLng.FromDegrees(10, 20) - S2LatLng.FromDegrees(20, 30)).ApproxEquals( 43 | S2LatLng.FromDegrees(-10, -10))); 44 | assertTrue((S2LatLng.FromDegrees(10, 20)*0.5).ApproxEquals(S2LatLng.FromDegrees(5, 10))); 45 | } 46 | 47 | [Test] 48 | public void testConversion() 49 | { 50 | // Test special cases: poles, "date line" 51 | assertDoubleNear( 52 | new S2LatLng(S2LatLng.FromDegrees(90.0, 65.0).ToPoint()).Lat.Degrees, 90.0); 53 | assertEquals( 54 | new S2LatLng(S2LatLng.FromRadians(-S2.PiOver2, 1).ToPoint()).Lat.Radians, -S2.PiOver2); 55 | assertDoubleNear( 56 | Math.Abs(new S2LatLng(S2LatLng.FromDegrees(12.2, 180.0).ToPoint()).Lng.Degrees), 180.0); 57 | assertEquals( 58 | Math.Abs(new S2LatLng(S2LatLng.FromRadians(0.1, -S2.Pi).ToPoint()).Lng.Radians), 59 | S2.Pi); 60 | 61 | // Test a bunch of random points. 62 | for (var i = 0; i < 100000; ++i) 63 | { 64 | var p = randomPoint(); 65 | assertTrue(S2.ApproxEquals(p, new S2LatLng(p).ToPoint())); 66 | } 67 | 68 | // Test generation from E5 69 | var test = S2LatLng.FromE5(123456, 98765); 70 | assertDoubleNear(test.Lat.Degrees, 1.23456); 71 | assertDoubleNear(test.Lng.Degrees, 0.98765); 72 | } 73 | 74 | [Test] 75 | public void testDistance() 76 | { 77 | assertEquals( 78 | S2LatLng.FromDegrees(90, 0).GetDistance(S2LatLng.FromDegrees(90, 0)).Radians, 0.0); 79 | assertDoubleNear( 80 | S2LatLng.FromDegrees(-37, 25).GetDistance(S2LatLng.FromDegrees(-66, -155)).Degrees, 77, 81 | 1e-13); 82 | assertDoubleNear( 83 | S2LatLng.FromDegrees(0, 165).GetDistance(S2LatLng.FromDegrees(0, -80)).Degrees, 115, 84 | 1e-13); 85 | assertDoubleNear( 86 | S2LatLng.FromDegrees(47, -127).GetDistance(S2LatLng.FromDegrees(-47, 53)).Degrees, 180, 87 | 2e-6); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2PolylineTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2PolylineTest : GeometryTestCase 12 | { 13 | private static void checkEqualsAndHashCodeMethods(Object lhs, Object rhs, 14 | bool expectedResult) 15 | { 16 | if ((lhs == null) && (rhs == null)) 17 | { 18 | assertTrue( 19 | "Your check is dubious...why would you expect null != null?", 20 | expectedResult); 21 | return; 22 | } 23 | 24 | if ((lhs == null) || (rhs == null)) 25 | { 26 | assertFalse( 27 | "Your check is dubious...why would you expect an object " 28 | + "to be equal to null?", expectedResult); 29 | } 30 | 31 | if (lhs != null) 32 | { 33 | assertEquals(expectedResult, lhs.Equals(rhs)); 34 | } 35 | if (rhs != null) 36 | { 37 | assertEquals(expectedResult, rhs.Equals(lhs)); 38 | } 39 | 40 | if (expectedResult) 41 | { 42 | var hashMessage = 43 | "hashCode() values for equal objects should be the same"; 44 | assertTrue(hashMessage, lhs.GetHashCode() == rhs.GetHashCode()); 45 | } 46 | } 47 | 48 | [Test] 49 | public void testBasic() 50 | { 51 | var vertices = new List(); 52 | var empty = new S2Polyline(vertices); 53 | assertEquals(empty.RectBound, S2LatLngRect.Empty); 54 | } 55 | 56 | [Test] 57 | public void testEqualsAndHashCode() 58 | { 59 | var vertices = new List(); 60 | vertices.Add(new S2Point(1, 0, 0)); 61 | vertices.Add(new S2Point(0, 1, 0)); 62 | vertices.Add(S2Point.Normalize(new S2Point(0, 1, 1))); 63 | vertices.Add(new S2Point(0, 0, 1)); 64 | 65 | 66 | var line1 = new S2Polyline(vertices); 67 | var line2 = new S2Polyline(vertices); 68 | 69 | checkEqualsAndHashCodeMethods(line1, line2, true); 70 | 71 | var moreVertices = new List(vertices); 72 | moreVertices.RemoveAt(0); 73 | 74 | var line3 = new S2Polyline(moreVertices); 75 | 76 | checkEqualsAndHashCodeMethods(line1, line3, false); 77 | checkEqualsAndHashCodeMethods(line1, null, false); 78 | checkEqualsAndHashCodeMethods(line1, "", false); 79 | } 80 | 81 | [Test] 82 | public void testGetLengthCentroid() 83 | { 84 | // Construct random great circles and divide them randomly into segments. 85 | // Then make sure that the length and centroid are correct. Note that 86 | // because of the way the centroid is computed, it does not matter how 87 | // we split the great circle into segments. 88 | 89 | for (var i = 0; i < 100; ++i) 90 | { 91 | // Choose a coordinate frame for the great circle. 92 | var x = randomPoint(); 93 | var y = S2Point.Normalize(S2Point.CrossProd(x, randomPoint())); 94 | var z = S2Point.Normalize(S2Point.CrossProd(x, y)); 95 | 96 | var vertices = new List(); 97 | for (double theta = 0; theta < 2*S2.Pi; theta += Math.Pow(rand.NextDouble(), 10)) 98 | { 99 | var p = (x * Math.Cos(theta)) + (y * Math.Sin(theta)); 100 | if (vertices.Count == 0 || !p.Equals(vertices[vertices.Count - 1])) 101 | { 102 | vertices.Add(p); 103 | } 104 | } 105 | // Close the circle. 106 | vertices.Add(vertices[0]); 107 | var line = new S2Polyline(vertices); 108 | var length = line.ArcLengthAngle; 109 | assertTrue(Math.Abs(length.Radians - 2*S2.Pi) < 2e-14); 110 | } 111 | } 112 | 113 | [Test] 114 | public void testInterpolate() 115 | { 116 | var vertices = new List(); 117 | vertices.Add(new S2Point(1, 0, 0)); 118 | vertices.Add(new S2Point(0, 1, 0)); 119 | vertices.Add(S2Point.Normalize(new S2Point(0, 1, 1))); 120 | vertices.Add(new S2Point(0, 0, 1)); 121 | var line = new S2Polyline(vertices); 122 | 123 | assertEquals(line.Interpolate(-0.1), vertices[0]); 124 | assertTrue(S2.ApproxEquals( 125 | line.Interpolate(0.1), S2Point.Normalize(new S2Point(1, Math.Tan(0.2*S2.Pi/2), 0)))); 126 | assertTrue(S2.ApproxEquals(line.Interpolate(0.25), S2Point.Normalize(new S2Point(1, 1, 0)))); 127 | 128 | assertTrue(S2.ApproxEquals(line.Interpolate(0.5), vertices[1])); 129 | assertTrue(S2.ApproxEquals(line.Interpolate(0.75), vertices[2])); 130 | assertTrue(S2.ApproxEquals(line.Interpolate(1.1), vertices[3])); 131 | } 132 | 133 | [Test] 134 | public void testMayIntersect() 135 | { 136 | var vertices = new List(); 137 | vertices.Add(S2Point.Normalize(new S2Point(1, -1.1, 0.8))); 138 | vertices.Add(S2Point.Normalize(new S2Point(1, -0.8, 1.1))); 139 | var line = new S2Polyline(vertices); 140 | for (var face = 0; face < 6; ++face) 141 | { 142 | var cell = S2Cell.FromFacePosLevel(face, (byte)0, 0); 143 | assertEquals(line.MayIntersect(cell), (face & 1) == 0); 144 | } 145 | } 146 | 147 | [Test] 148 | public void testProject() 149 | { 150 | var latLngs = new List(); 151 | latLngs.Add(S2LatLng.FromDegrees(0, 0).ToPoint()); 152 | latLngs.Add(S2LatLng.FromDegrees(0, 1).ToPoint()); 153 | latLngs.Add(S2LatLng.FromDegrees(0, 2).ToPoint()); 154 | latLngs.Add(S2LatLng.FromDegrees(1, 2).ToPoint()); 155 | var line = new S2Polyline(latLngs); 156 | 157 | var edgeIndex = -1; 158 | S2Point testPoint = default(S2Point); 159 | 160 | testPoint = S2LatLng.FromDegrees(0.5, -0.5).ToPoint(); 161 | edgeIndex = line.GetNearestEdgeIndex(testPoint); 162 | assertTrue(S2.ApproxEquals( 163 | line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0).ToPoint())); 164 | assertEquals(0, edgeIndex); 165 | 166 | testPoint = S2LatLng.FromDegrees(0.5, 0.5).ToPoint(); 167 | edgeIndex = line.GetNearestEdgeIndex(testPoint); 168 | assertTrue(S2.ApproxEquals( 169 | line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 0.5).ToPoint())); 170 | assertEquals(0, edgeIndex); 171 | 172 | testPoint = S2LatLng.FromDegrees(0.5, 1).ToPoint(); 173 | edgeIndex = line.GetNearestEdgeIndex(testPoint); 174 | assertTrue(S2.ApproxEquals( 175 | line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 1).ToPoint())); 176 | assertEquals(0, edgeIndex); 177 | 178 | testPoint = S2LatLng.FromDegrees(-0.5, 2.5).ToPoint(); 179 | edgeIndex = line.GetNearestEdgeIndex(testPoint); 180 | assertTrue(S2.ApproxEquals( 181 | line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(0, 2).ToPoint())); 182 | assertEquals(1, edgeIndex); 183 | 184 | testPoint = S2LatLng.FromDegrees(2, 2).ToPoint(); 185 | edgeIndex = line.GetNearestEdgeIndex(testPoint); 186 | assertTrue(S2.ApproxEquals( 187 | line.ProjectToEdge(testPoint, edgeIndex), S2LatLng.FromDegrees(1, 2).ToPoint())); 188 | assertEquals(2, edgeIndex); 189 | } 190 | 191 | /** 192 | * Utility for testing equals() and hashCode() results at once. 193 | * Tests that lhs.equals(rhs) matches expectedResult, as well as 194 | * rhs.equals(lhs). Also tests that hashCode() return values are 195 | * equal if expectedResult is true. (hashCode() is not tested if 196 | * expectedResult is false, as unequal objects can have equal hashCodes.) 197 | * 198 | * @param lhs An Object for which equals() and hashCode() are to be tested. 199 | * @param rhs As lhs. 200 | * @param expectedResult True if the objects should compare equal, 201 | * false if not. 202 | */ 203 | } 204 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2RegionCovererTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2RegionCovererTest : GeometryTestCase 12 | { 13 | public void checkCovering( 14 | S2RegionCoverer coverer, IS2Region region, List covering, bool interior) 15 | { 16 | // Keep track of how many cells have the same coverer.min_level() ancestor. 17 | IDictionary minLevelCells = new Dictionary(); 18 | for (var i = 0; i < covering.Count; ++i) 19 | { 20 | var level = covering[i].Level; 21 | assertTrue(level >= coverer.MinLevel); 22 | assertTrue(level <= coverer.MaxLevel); 23 | assertEquals((level - coverer.MinLevel)%coverer.LevelMod, 0); 24 | var key = covering[i].ParentForLevel(coverer.MinLevel); 25 | if (!minLevelCells.ContainsKey(key)) 26 | { 27 | minLevelCells.Add(key, 1); 28 | } 29 | else 30 | { 31 | minLevelCells[key] = minLevelCells[key] + 1; 32 | } 33 | } 34 | if (covering.Count > coverer.MaxCells) 35 | { 36 | // If the covering has more than the requested number of cells, then check 37 | // that the cell count cannot be reduced by using the parent of some cell. 38 | foreach (var i in minLevelCells.Values) 39 | { 40 | assertEquals(i, 1); 41 | } 42 | } 43 | 44 | if (interior) 45 | { 46 | for (var i = 0; i < covering.Count; ++i) 47 | { 48 | assertTrue(region.Contains(new S2Cell(covering[i]))); 49 | } 50 | } 51 | else 52 | { 53 | var cellUnion = new S2CellUnion(); 54 | cellUnion.InitFromCellIds(covering); 55 | checkCovering(region, cellUnion, true, new S2CellId()); 56 | } 57 | } 58 | 59 | [Test] 60 | public void testRandomCaps() 61 | { 62 | Console.WriteLine("TestRandomCaps"); 63 | 64 | var kMaxLevel = S2CellId.MaxLevel; 65 | var coverer = new S2RegionCoverer(); 66 | for (var i = 0; i < 1000; ++i) 67 | { 68 | do 69 | { 70 | coverer.MinLevel = random(kMaxLevel + 1); 71 | coverer.MaxLevel = random(kMaxLevel + 1); 72 | } while (coverer.MinLevel > coverer.MaxLevel); 73 | coverer.MaxCells = skewed(10); 74 | coverer.LevelMod = 1 + random(3); 75 | var maxArea = Math.Min( 76 | 4*S2.Pi, (3*coverer.MaxCells + 1)*S2Cell.AverageArea(coverer.MinLevel)); 77 | var cap = getRandomCap(0.1*S2Cell.AverageArea(kMaxLevel), maxArea); 78 | var covering = new List(); 79 | var interior = new List(); 80 | 81 | coverer.GetCovering(cap, covering); 82 | checkCovering(coverer, cap, covering, false); 83 | 84 | coverer.GetInteriorCovering(cap, interior); 85 | checkCovering(coverer, cap, interior, true); 86 | 87 | 88 | // Check that GetCovering is deterministic. 89 | var covering2 = new List(); 90 | coverer.GetCovering(cap, covering2); 91 | assertTrue(covering.SequenceEqual(covering2)); 92 | 93 | // Also check S2CellUnion.denormalize(). The denormalized covering 94 | // may still be different and smaller than "covering" because 95 | // S2RegionCoverer does not guarantee that it will not output all four 96 | // children of the same parent. 97 | var cells = new S2CellUnion(); 98 | cells.InitFromCellIds(covering); 99 | var denormalized = new List(); 100 | cells.Denormalize(coverer.MinLevel, coverer.LevelMod, denormalized); 101 | checkCovering(coverer, cap, denormalized, false); 102 | } 103 | } 104 | 105 | [Test] 106 | public void testRandomCells() 107 | { 108 | Console.WriteLine("TestRandomCells"); 109 | 110 | var coverer = new S2RegionCoverer(); 111 | coverer.MaxCells = 1; 112 | 113 | // Test random cell ids at all levels. 114 | for (var i = 0; i < 10000; ++i) 115 | { 116 | var id = getRandomCellId(); 117 | var covering = new S2CellUnion(); 118 | coverer.GetCovering(new S2Cell(id), covering.CellIds); 119 | assertEquals(covering.Count, 1); 120 | assertEquals(covering.CellId(0), id); 121 | } 122 | } 123 | 124 | [Test] 125 | public void testSimpleCoverings() 126 | { 127 | Console.WriteLine("TestSimpleCoverings"); 128 | 129 | var kMaxLevel = S2CellId.MaxLevel; 130 | var coverer = new S2RegionCoverer(); 131 | coverer.MaxCells = int.MaxValue; 132 | for (var i = 0; i < 1000; ++i) 133 | { 134 | var level = random(kMaxLevel + 1); 135 | coverer.MinLevel = level; 136 | coverer.MaxLevel = level; 137 | var maxArea = Math.Min(4*S2.Pi, 1000*S2Cell.AverageArea(level)); 138 | var cap = getRandomCap(0.1*S2Cell.AverageArea(kMaxLevel), maxArea); 139 | var covering = new List(); 140 | S2RegionCoverer.GetSimpleCovering(cap, cap.Axis, level, covering); 141 | checkCovering(coverer, cap, covering, false); 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/S2Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry; 7 | using NUnit.Framework; 8 | 9 | namespace S2Geometry.Tests 10 | { 11 | public class S2Test : GeometryTestCase 12 | { 13 | // Test helper methods for testing the traversal order. 14 | private static int swapAxes(int ij) 15 | { 16 | return ((ij >> 1) & 1) + ((ij & 1) << 1); 17 | } 18 | 19 | private static int invertBits(int ij) 20 | { 21 | return ij ^ 3; 22 | } 23 | 24 | // Note: obviously, I could have defined a bundle of metrics like this in the 25 | // S2 class itself rather than just for testing. However, it's not clear that 26 | // this is useful other than for testing purposes, and I find 27 | // S2.kMinWidth.GetMaxLevel(width) to be slightly more readable than 28 | // than S2.kWidth.min().GetMaxLevel(width). Also, there is no fundamental 29 | // reason that we need to analyze the minimum, maximum, and average values of 30 | // every metric; it would be perfectly reasonable to just define one of these. 31 | 32 | public class MetricBundle 33 | { 34 | public S2CellMetric avg_; 35 | public S2CellMetric max_; 36 | public S2CellMetric min_; 37 | 38 | public MetricBundle(S2CellMetric min, S2CellMetric max, S2CellMetric avg) 39 | { 40 | min_ = min; 41 | max_ = max; 42 | avg_ = avg; 43 | } 44 | } 45 | 46 | public void testMinMaxAvg(MetricBundle bundle) 47 | { 48 | assertTrue(bundle.min_.Deriv() < bundle.avg_.Deriv()); 49 | assertTrue(bundle.avg_.Deriv() < bundle.max_.Deriv()); 50 | } 51 | 52 | public void testLessOrEqual(MetricBundle a, MetricBundle b) 53 | { 54 | assertTrue(a.min_.Deriv() <= b.min_.Deriv()); 55 | assertTrue(a.max_.Deriv() <= b.max_.Deriv()); 56 | assertTrue(a.avg_.Deriv() <= b.avg_.Deriv()); 57 | } 58 | 59 | [Test] 60 | public void testAngleArea() 61 | { 62 | var pz = new S2Point(0, 0, 1); 63 | var p000 = new S2Point(1, 0, 0); 64 | var p045 = new S2Point(1, 1, 0); 65 | var p090 = new S2Point(0, 1, 0); 66 | var p180 = new S2Point(-1, 0, 0); 67 | assertDoubleNear(S2.Angle(p000, pz, p045), S2.PiOver4); 68 | assertDoubleNear(S2.Angle(p045, pz, p180), 3*S2.PiOver4); 69 | assertDoubleNear(S2.Angle(p000, pz, p180), S2.Pi); 70 | assertDoubleNear(S2.Angle(pz, p000, pz), 0); 71 | assertDoubleNear(S2.Angle(pz, p000, p045), S2.PiOver2); 72 | 73 | assertDoubleNear(S2.Area(p000, p090, pz), S2.PiOver2); 74 | assertDoubleNear(S2.Area(p045, pz, p180), 3*S2.PiOver4); 75 | 76 | // Make sure that area() has good *relative* accuracy even for 77 | // very small areas. 78 | var eps = 1e-10; 79 | var pepsx = new S2Point(eps, 0, 1); 80 | var pepsy = new S2Point(0, eps, 1); 81 | var expected1 = 0.5*eps*eps; 82 | assertDoubleNear(S2.Area(pepsx, pepsy, pz), expected1, 1e-14*expected1); 83 | 84 | // Make sure that it can handle degenerate triangles. 85 | var pr = new S2Point(0.257, -0.5723, 0.112); 86 | var pq = new S2Point(-0.747, 0.401, 0.2235); 87 | assertEquals(S2.Area(pr, pr, pr), 0.0); 88 | // TODO: The following test is not exact in optimized mode because the 89 | // compiler chooses to mix 64-bit and 80-bit intermediate results. 90 | assertDoubleNear(S2.Area(pr, pq, pr), 0); 91 | assertEquals(S2.Area(p000, p045, p090), 0.0); 92 | 93 | double maxGirard = 0; 94 | for (var i = 0; i < 10000; ++i) 95 | { 96 | var p0 = randomPoint(); 97 | var d1 = randomPoint(); 98 | var d2 = randomPoint(); 99 | var p1 = p0 + (d1 * 1e-15); 100 | var p2 = p0 + (d2 * 1e-15); 101 | // The actual displacement can be as much as 1.2e-15 due to roundoff. 102 | // This yields a maximum triangle area of about 0.7e-30. 103 | assertTrue(S2.Area(p0, p1, p2) < 0.7e-30); 104 | maxGirard = Math.Max(maxGirard, S2.GirardArea(p0, p1, p2)); 105 | } 106 | Console.WriteLine("Worst case Girard for triangle area 1e-30: " + maxGirard); 107 | 108 | // Try a very long and skinny triangle. 109 | var p045eps = new S2Point(1, 1, eps); 110 | var expected2 = 5.8578643762690495119753e-11; // Mathematica. 111 | assertDoubleNear(S2.Area(p000, p045eps, p090), expected2, 1e-9*expected2); 112 | 113 | // Triangles with near-180 degree edges that sum to a quarter-sphere. 114 | var eps2 = 1e-10; 115 | var p000eps2 = new S2Point(1, 0.1*eps2, eps2); 116 | var quarterArea1 = 117 | S2.Area(p000eps2, p000, p090) + S2.Area(p000eps2, p090, p180) + S2.Area(p000eps2, p180, pz) 118 | + S2.Area(p000eps2, pz, p000); 119 | assertDoubleNear(quarterArea1, S2.Pi); 120 | 121 | // Four other triangles that sum to a quarter-sphere. 122 | var p045eps2 = new S2Point(1, 1, eps2); 123 | var quarterArea2 = 124 | S2.Area(p045eps2, p000, p090) + S2.Area(p045eps2, p090, p180) + S2.Area(p045eps2, p180, pz) 125 | + S2.Area(p045eps2, pz, p000); 126 | assertDoubleNear(quarterArea2, S2.Pi); 127 | } 128 | 129 | [Test] 130 | public void testCCW() 131 | { 132 | var a = new S2Point(0.72571927877036835, 0.46058825605889098, 0.51106749730504852); 133 | var b = new S2Point(0.7257192746638208, 0.46058826573818168, 0.51106749441312738); 134 | var c = new S2Point(0.72571927671709457, 0.46058826089853633, 0.51106749585908795); 135 | assertTrue(S2.RobustCcw(a, b, c) != 0); 136 | } 137 | 138 | [Test] 139 | public void testExp() 140 | { 141 | for (var i = 0; i < 10; ++i) 142 | { 143 | assertEquals(i + 1, S2.Exp(Math.Pow(2, i))); 144 | } 145 | 146 | for (var i = 0; i < 10; ++i) 147 | { 148 | assertEquals(i + 1, S2.Exp(-Math.Pow(2, i))); 149 | } 150 | 151 | assertEquals(0, S2.Exp(0)); 152 | assertEquals(2, S2.Exp(3)); 153 | assertEquals(3, S2.Exp(5)); 154 | } 155 | 156 | [Test] 157 | public void testFaceUVtoXYZ() 158 | { 159 | // Check that each face appears exactly once. 160 | var sum = new S2Point(); 161 | for (var face = 0; face < 6; ++face) 162 | { 163 | var center = S2Projections.FaceUvToXyz(face, 0, 0); 164 | assertEquals(S2Projections.GetNorm(face), center); 165 | assertEquals(Math.Abs(center[center.LargestAbsComponent]), 1.0); 166 | sum = sum + S2Point.Fabs(center); 167 | } 168 | assertEquals(sum, new S2Point(2, 2, 2)); 169 | 170 | // Check that each face has a right-handed coordinate system. 171 | for (var face = 0; face < 6; ++face) 172 | { 173 | assertEquals( 174 | S2Point.CrossProd(S2Projections.GetUAxis(face), S2Projections.GetVAxis(face)).DotProd( 175 | S2Projections.FaceUvToXyz(face, 0, 0)), 1.0); 176 | } 177 | 178 | // Check that the Hilbert curves on each face combine to form a 179 | // continuous curve over the entire cube. 180 | for (var face = 0; face < 6; ++face) 181 | { 182 | // The Hilbert curve on each face starts at (-1,-1) and terminates 183 | // at either (1,-1) (if axes not swapped) or (-1,1) (if swapped). 184 | var sign = ((face & S2.SwapMask) != 0) ? -1 : 1; 185 | assertEquals(S2Projections.FaceUvToXyz(face, sign, -sign), 186 | S2Projections.FaceUvToXyz((face + 1)%6, -1, -1)); 187 | } 188 | } 189 | 190 | [Test] 191 | public void testMetrics() 192 | { 193 | var angleSpan = new MetricBundle( 194 | S2Projections.MinAngleSpan, S2Projections.MaxAngleSpan, S2Projections.AvgAngleSpan); 195 | var width = 196 | new MetricBundle(S2Projections.MinWidth, S2Projections.MaxWidth, S2Projections.AvgWidth); 197 | var edge = 198 | new MetricBundle(S2Projections.MinEdge, S2Projections.MaxEdge, S2Projections.AvgEdge); 199 | var diag = 200 | new MetricBundle(S2Projections.MinDiag, S2Projections.MaxDiag, S2Projections.AvgDiag); 201 | var area = 202 | new MetricBundle(S2Projections.MinArea, S2Projections.MaxArea, S2Projections.AvgArea); 203 | 204 | // First, check that min <= avg <= max for each metric. 205 | testMinMaxAvg(angleSpan); 206 | testMinMaxAvg(width); 207 | testMinMaxAvg(edge); 208 | testMinMaxAvg(diag); 209 | testMinMaxAvg(area); 210 | 211 | // Check that the maximum aspect ratio of an individual cell is consistent 212 | // with the global minimums and maximums. 213 | assertTrue(S2Projections.MaxEdgeAspect >= 1.0); 214 | assertTrue(S2Projections.MaxEdgeAspect 215 | < S2Projections.MaxEdge.Deriv()/S2Projections.MinEdge.Deriv()); 216 | assertTrue(S2Projections.MaxDiagAspect >= 1); 217 | assertTrue(S2Projections.MaxDiagAspect 218 | < S2Projections.MaxDiag.Deriv()/S2Projections.MinDiag.Deriv()); 219 | 220 | // Check various conditions that are provable mathematically. 221 | testLessOrEqual(width, angleSpan); 222 | testLessOrEqual(width, edge); 223 | testLessOrEqual(edge, diag); 224 | 225 | assertTrue(S2Projections.MinArea.Deriv() 226 | >= S2Projections.MinWidth.Deriv()*S2Projections.MinEdge.Deriv() - 1e-15); 227 | assertTrue(S2Projections.MaxArea.Deriv() 228 | < S2Projections.MaxWidth.Deriv()*S2Projections.MaxEdge.Deriv() + 1e-15); 229 | 230 | // GetMinLevelForLength() and friends have built-in assertions, we just need 231 | // to call these functions to test them. 232 | // 233 | // We don't actually check that the metrics are correct here, e.g. that 234 | // GetMinWidth(10) is a lower bound on the width of cells at level 10. 235 | // It is easier to check these properties in s2cell_unittest, since 236 | // S2Cell has methods to compute the cell vertices, etc. 237 | 238 | for (var level = -2; level <= S2CellId.MaxLevel + 3; ++level) 239 | { 240 | var dWidth = (2*S2Projections.MinWidth.Deriv())*Math.Pow(2, -level); 241 | if (level >= S2CellId.MaxLevel + 3) 242 | { 243 | dWidth = 0; 244 | } 245 | 246 | // Check boundary cases (exactly equal to a threshold value). 247 | var expectedLevel = Math.Max(0, Math.Min(S2CellId.MaxLevel, level)); 248 | assertEquals(S2Projections.MinWidth.GetMinLevel(dWidth), expectedLevel); 249 | assertEquals(S2Projections.MinWidth.GetMaxLevel(dWidth), expectedLevel); 250 | assertEquals(S2Projections.MinWidth.GetClosestLevel(dWidth), expectedLevel); 251 | 252 | // Also check non-boundary cases. 253 | assertEquals(S2Projections.MinWidth.GetMinLevel(1.2*dWidth), expectedLevel); 254 | assertEquals(S2Projections.MinWidth.GetMaxLevel(0.8*dWidth), expectedLevel); 255 | assertEquals(S2Projections.MinWidth.GetClosestLevel(1.2*dWidth), expectedLevel); 256 | assertEquals(S2Projections.MinWidth.GetClosestLevel(0.8*dWidth), expectedLevel); 257 | 258 | // Same thing for area1. 259 | var area1 = (4*S2Projections.MinArea.Deriv())*Math.Pow(4, -level); 260 | if (level <= -3) 261 | { 262 | area1 = 0; 263 | } 264 | assertEquals(S2Projections.MinArea.GetMinLevel(area1), expectedLevel); 265 | assertEquals(S2Projections.MinArea.GetMaxLevel(area1), expectedLevel); 266 | assertEquals(S2Projections.MinArea.GetClosestLevel(area1), expectedLevel); 267 | assertEquals(S2Projections.MinArea.GetMinLevel(1.2*area1), expectedLevel); 268 | assertEquals(S2Projections.MinArea.GetMaxLevel(0.8*area1), expectedLevel); 269 | assertEquals(S2Projections.MinArea.GetClosestLevel(1.2*area1), expectedLevel); 270 | assertEquals(S2Projections.MinArea.GetClosestLevel(0.8*area1), expectedLevel); 271 | } 272 | } 273 | 274 | [Test] 275 | public void testSTUV() 276 | { 277 | // Check boundary conditions. 278 | for (double x = -1; x <= 1; ++x) 279 | { 280 | assertEquals(S2Projections.StToUv(x), x); 281 | assertEquals(S2Projections.UvToSt(x), x); 282 | } 283 | // Check that UVtoST and STtoUV are inverses. 284 | for (double x = -1; x <= 1; x += 0.0001) 285 | { 286 | assertDoubleNear(S2Projections.UvToSt(S2Projections.StToUv(x)), x); 287 | assertDoubleNear(S2Projections.StToUv(S2Projections.UvToSt(x)), x); 288 | } 289 | } 290 | 291 | [Test] 292 | public void testTraversalOrder() 293 | { 294 | for (var r = 0; r < 4; ++r) 295 | { 296 | for (var i = 0; i < 4; ++i) 297 | { 298 | // Check consistency with respect to swapping axes. 299 | assertEquals(S2.IjToPos(r, i), 300 | S2.IjToPos(r ^ S2.SwapMask, swapAxes(i))); 301 | assertEquals(S2.PosToIj(r, i), 302 | swapAxes(S2.PosToIj(r ^ S2.SwapMask, i))); 303 | 304 | // Check consistency with respect to reversing axis directions. 305 | assertEquals(S2.IjToPos(r, i), 306 | S2.IjToPos(r ^ S2.InvertMask, invertBits(i))); 307 | assertEquals(S2.PosToIj(r, i), 308 | invertBits(S2.PosToIj(r ^ S2.InvertMask, i))); 309 | 310 | // Check that the two tables are inverses of each other. 311 | assertEquals(S2.IjToPos(r, S2.PosToIj(r, i)), i); 312 | assertEquals(S2.PosToIj(r, S2.IjToPos(r, i)), i); 313 | } 314 | } 315 | } 316 | 317 | [Test] 318 | public void testUVAxes() 319 | { 320 | // Check that axes are consistent with FaceUVtoXYZ. 321 | for (var face = 0; face < 6; ++face) 322 | { 323 | assertEquals(S2Projections.GetUAxis(face), 324 | S2Projections.FaceUvToXyz(face, 1, 0) - S2Projections.FaceUvToXyz(face, 0, 0)); 325 | assertEquals(S2Projections.GetVAxis(face), 326 | S2Projections.FaceUvToXyz(face, 0, 1) - S2Projections.FaceUvToXyz(face, 0, 0)); 327 | } 328 | } 329 | 330 | [Test] 331 | public void testUVNorms() 332 | { 333 | // Check that GetUNorm and GetVNorm compute right-handed normals for 334 | // an edge in the increasing U or V direction. 335 | for (var face = 0; face < 6; ++face) 336 | { 337 | for (double x = -1; x <= 1; x += 1/1024.0) 338 | { 339 | assertDoubleNear( 340 | S2Point.CrossProd( 341 | S2Projections.FaceUvToXyz(face, x, -1), S2Projections.FaceUvToXyz(face, x, 1)) 342 | .Angle(S2Projections.GetUNorm(face, x)), 0); 343 | assertDoubleNear( 344 | S2Point.CrossProd( 345 | S2Projections.FaceUvToXyz(face, -1, x), S2Projections.FaceUvToXyz(face, 1, x)) 346 | .Angle(S2Projections.GetVNorm(face, x)), 0); 347 | } 348 | } 349 | } 350 | } 351 | } -------------------------------------------------------------------------------- /S2Geometry.Tests/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "NUnit": "2.6.4", 4 | "NUnitTestAdapter": "2.0.0" 5 | }, 6 | "frameworks": { 7 | "net45": {} 8 | }, 9 | "supports": {}, 10 | "runtimes": { 11 | "win": {} 12 | } 13 | } -------------------------------------------------------------------------------- /S2Geometry.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | S2Geometry 6 | 1.0.3 7 | S2 Geometry Library 8 | Oren Novotny 9 | onovotny 10 | https://raw.github.com/onovotny/s2-geometry-library-csharp/master/LICENSE 11 | https://github.com/onovotny/s2-geometry-library-csharp 12 | false 13 | 14 | The Google S2 Geometry Library is a spherical geometry library, very useful for manipulating regions on the sphere (commonly on Earth) and indexing geographic data. 15 | This version targets NET Standard 1.0 16 | 17 | 18 | 19 | Google S2 Geo GeoSpatial geometry Hilbert portable pcl wp wp8 netstandard 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /S2Geometry.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S2Geometry", "S2Geometry\S2Geometry.csproj", "{5E82F402-F876-4A29-B59E-1BA21591A44A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S2Geometry.Tests", "S2Geometry.Tests\S2Geometry.Tests.csproj", "{FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{43196EF9-B75F-4ABB-8967-DAFD621A7D05}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | S2Geometry.nuspec = S2Geometry.nuspec 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Debug|Mixed Platforms = Debug|Mixed Platforms 20 | Debug|Win32 = Debug|Win32 21 | Release|Any CPU = Release|Any CPU 22 | Release|Mixed Platforms = Release|Mixed Platforms 23 | Release|Win32 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 29 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 30 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Debug|Win32.ActiveCfg = Debug|Any CPU 31 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 34 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Release|Mixed Platforms.Build.0 = Release|Any CPU 35 | {5E82F402-F876-4A29-B59E-1BA21591A44A}.Release|Win32.ActiveCfg = Release|Any CPU 36 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 39 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 40 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Debug|Win32.ActiveCfg = Debug|Any CPU 41 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 44 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Release|Mixed Platforms.Build.0 = Release|Any CPU 45 | {FB7E934F-DEA5-4A8A-A3FD-10B32D449A30}.Release|Win32.ActiveCfg = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | EndGlobal 51 | -------------------------------------------------------------------------------- /S2Geometry/AssemblyTFMAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using System.Reflection; 4 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETStandard,Version=v1.0", FrameworkDisplayName = ".NETStandard,Version=v1.0")] 5 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/HashBag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Google.Common.Geometry.DataStructures 9 | { 10 | // Adapted from https://github.com/sestoft/C5/blob/master/C5/hashing/HashBag.cs 11 | class HashBag : ICollection 12 | { 13 | readonly Dictionary dict; 14 | int size; 15 | 16 | public HashBag() : this(EqualityComparer.Default) 17 | { 18 | } 19 | 20 | public HashBag(IEqualityComparer itemEqualityComparer) 21 | { 22 | dict = new Dictionary(itemEqualityComparer); 23 | } 24 | 25 | public IEnumerator GetEnumerator() 26 | { 27 | foreach (var item in dict) 28 | { 29 | for (var i = 0; i < item.Value; i++) 30 | yield return item.Key; 31 | } 32 | } 33 | 34 | IEnumerator IEnumerable.GetEnumerator() 35 | { 36 | return GetEnumerator(); 37 | } 38 | 39 | public void Add(T item) 40 | { 41 | int val; 42 | if (dict.TryGetValue(item, out val)) 43 | { 44 | dict[item] = ++val; 45 | } 46 | else 47 | { 48 | dict.Add(item, 1); 49 | } 50 | size++; 51 | } 52 | 53 | public void Clear() 54 | { 55 | dict.Clear(); 56 | size = 0; 57 | } 58 | 59 | public bool Contains(T item) 60 | { 61 | return dict.ContainsKey(item); 62 | } 63 | 64 | public void CopyTo(T[] array, int arrayIndex) 65 | { 66 | if (arrayIndex < 0 || arrayIndex + Count > array.Length) 67 | throw new ArgumentOutOfRangeException(); 68 | 69 | foreach (var p in dict) 70 | for (var j = 0; j < p.Value; j++) 71 | array[arrayIndex++] = p.Key; 72 | } 73 | 74 | public bool Remove(T item) 75 | { 76 | int val; 77 | 78 | if (dict.TryGetValue(item, out val)) 79 | { 80 | size--; 81 | if (val == 1) 82 | dict.Remove(item); 83 | else 84 | { 85 | dict[item] = --val; 86 | } 87 | 88 | return true; 89 | } 90 | return false; 91 | } 92 | 93 | public int Count => size; 94 | public bool IsReadOnly => false; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/IMultiMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace System.Collections.Generic 7 | { 8 | internal interface IMultiMap 9 | { 10 | void Add(TKey key, IEnumerable valueList); 11 | List this[TKey key] { get; set;} 12 | bool Remove(TKey key, TValue value); 13 | void Add(TKey key, TValue value); 14 | bool ContainsKey(TKey key); 15 | 16 | ICollection Keys {get;} 17 | bool Remove(TKey key); 18 | ICollection Values{get;} 19 | 20 | void Add(KeyValuePair item); 21 | void Clear(); 22 | bool Contains(TKey key, TValue item); 23 | void CopyTo(KeyValuePair[] array, int arrayIndex); 24 | int Count {get;} 25 | bool Remove(KeyValuePair item); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/MultiMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Google.Common.Geometry.MultiMap; 7 | 8 | 9 | namespace System.Collections.Generic 10 | { 11 | internal class MultiMap : IMultiMap, IDictionary 12 | { 13 | private Dictionary> _interalStorage = new Dictionary>(); 14 | 15 | public MultiMap(){} 16 | 17 | public MultiMap(IEnumerable> initialData) 18 | { 19 | foreach (var item in initialData) 20 | { 21 | Add(item); 22 | } 23 | } 24 | 25 | public void Add(TKey key, TValue value) 26 | { 27 | if (!_interalStorage.ContainsKey(key)) 28 | { 29 | _interalStorage.Add(key, new List()); 30 | } 31 | _interalStorage[key].Add(value); 32 | } 33 | 34 | public void Add(TKey key, IEnumerable valueList) 35 | { 36 | if (!_interalStorage.ContainsKey(key)) 37 | { 38 | _interalStorage.Add(key, new List()); 39 | } 40 | foreach (TValue value in valueList) 41 | { 42 | _interalStorage[key].Add(value); 43 | } 44 | } 45 | 46 | public bool ContainsKey(TKey key) 47 | { 48 | return _interalStorage.ContainsKey(key); 49 | } 50 | 51 | public ICollection Keys 52 | { 53 | get { return _interalStorage.Keys; } 54 | } 55 | 56 | public bool Remove(TKey key) 57 | { 58 | return _interalStorage.Remove(key); 59 | } 60 | 61 | bool IDictionary.TryGetValue(TKey key, out TValue value) 62 | { 63 | if (!_interalStorage.ContainsKey(key)) 64 | { 65 | value = default(TValue); 66 | return false; 67 | } 68 | value = _interalStorage[key].Last(); 69 | return true; 70 | } 71 | 72 | public ICollection Values 73 | { 74 | get 75 | { 76 | List retVal = new List(); 77 | foreach (var item in _interalStorage) 78 | { 79 | retVal.AddRange(item.Value); 80 | } 81 | return retVal; 82 | } 83 | } 84 | 85 | TValue IDictionary.this[TKey key] 86 | { 87 | get 88 | { 89 | return _interalStorage[key].LastOrDefault(); 90 | } 91 | set 92 | { 93 | Add(key,value); 94 | } 95 | } 96 | 97 | public void Add(KeyValuePair item) 98 | { 99 | if (!_interalStorage.ContainsKey(item.Key)) 100 | { 101 | _interalStorage.Add(item.Key, new List()); 102 | } 103 | _interalStorage[item.Key].Add(item.Value); 104 | } 105 | 106 | public void Clear() 107 | { 108 | _interalStorage.Clear(); 109 | } 110 | 111 | public bool Contains(KeyValuePair item) 112 | { 113 | List valueList; 114 | if (_interalStorage.TryGetValue(item.Key, out valueList)) 115 | return valueList.Contains(item.Value); 116 | return false; 117 | } 118 | 119 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 120 | { 121 | int i = arrayIndex; 122 | foreach (var item in _interalStorage) 123 | { 124 | foreach (TValue value in item.Value) 125 | { 126 | array[i] = new KeyValuePair(item.Key, value); 127 | ++i; 128 | } 129 | } 130 | } 131 | 132 | public int Count 133 | { 134 | get 135 | { 136 | int count = 0; 137 | foreach (var item in _interalStorage) 138 | { 139 | count += item.Value.Count; 140 | } 141 | return count; 142 | } 143 | } 144 | 145 | public bool CountIsAtLeast(int value) 146 | { 147 | int count = 0; 148 | foreach (var item in _interalStorage) 149 | { 150 | count += item.Value.Count; 151 | if (count >= value) 152 | return true; 153 | } 154 | return false; 155 | } 156 | 157 | int ICollection>.Count 158 | { 159 | get { return _interalStorage.Count; } 160 | } 161 | 162 | bool ICollection>.IsReadOnly 163 | { 164 | get { return false; } 165 | } 166 | 167 | public bool Remove(KeyValuePair item) 168 | { 169 | if (!ContainsKey(item.Key)) return false; 170 | 171 | var list = _interalStorage[item.Key]; 172 | var removed = list.Remove(item.Value); 173 | if (list.Count == 0) 174 | _interalStorage.Remove(item.Key); // clear out the dict 175 | 176 | return removed; 177 | } 178 | 179 | public IEnumerator> GetEnumerator() 180 | { 181 | return new MultiMapEnumerator(this); 182 | } 183 | 184 | public IEnumerable> SortedValues 185 | { 186 | get { return new SortedMultiMapEnumerable(this); } 187 | } 188 | 189 | IEnumerator IEnumerable.GetEnumerator() 190 | { 191 | return new MultiMapEnumerator(this); 192 | } 193 | 194 | 195 | public List this[TKey key] 196 | { 197 | get 198 | { 199 | if (!_interalStorage.ContainsKey(key)) 200 | return new List(); 201 | return _interalStorage[key]; 202 | } 203 | set 204 | { 205 | if (!_interalStorage.ContainsKey(key)) 206 | _interalStorage.Add(key, value); 207 | else _interalStorage[key] = value; 208 | } 209 | } 210 | 211 | public bool Remove(TKey key, TValue value) 212 | { 213 | if (!ContainsKey(key)) return false; 214 | return _interalStorage[key].Remove(value); 215 | } 216 | 217 | 218 | 219 | public bool Contains(TKey key, TValue item) 220 | { 221 | if (!_interalStorage.ContainsKey(key)) return false; 222 | return _interalStorage[key].Contains(item); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/MultiMapEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace System.Collections.Generic 7 | { 8 | internal class MultiMapEnumerator : IEnumerator> 9 | { 10 | MultiMap _map; 11 | IEnumerator _keyEnumerator; 12 | IEnumerator _valueEnumerator; 13 | 14 | public MultiMapEnumerator(MultiMap map) 15 | { 16 | this._map = map; 17 | Reset(); 18 | } 19 | 20 | object IEnumerator.Current 21 | { 22 | get 23 | { 24 | return Current; 25 | } 26 | } 27 | 28 | public KeyValuePair Current 29 | { 30 | get 31 | { 32 | return new KeyValuePair(_keyEnumerator.Current, _valueEnumerator.Current); 33 | } 34 | } 35 | 36 | 37 | public void Dispose() 38 | { 39 | _keyEnumerator = null; 40 | _valueEnumerator = null; 41 | _map = null; 42 | } 43 | 44 | 45 | public bool MoveNext() 46 | { 47 | if (!_valueEnumerator.MoveNext()) 48 | { 49 | if (!_keyEnumerator.MoveNext()) 50 | return false; 51 | _valueEnumerator = _map[_keyEnumerator.Current].GetEnumerator(); 52 | _valueEnumerator.MoveNext(); 53 | return true; 54 | } 55 | return true; 56 | } 57 | 58 | public void Reset() 59 | { 60 | _keyEnumerator = _map.Keys.GetEnumerator(); 61 | _valueEnumerator = new List().GetEnumerator(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/PriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry.DataStructures 8 | { 9 | internal class PriorityQueue where T : IComparable 10 | { 11 | private List data; 12 | 13 | public PriorityQueue() 14 | { 15 | this.data = new List(); 16 | } 17 | 18 | public void Enqueue(T item) 19 | { 20 | data.Add(item); 21 | int ci = data.Count - 1; // child index; start at end 22 | while (ci > 0) 23 | { 24 | int pi = (ci - 1) / 2; // parent index 25 | if (data[ci].CompareTo(data[pi]) >= 0) break; // child item is larger than (or equal) parent so we're done 26 | T tmp = data[ci]; data[ci] = data[pi]; data[pi] = tmp; 27 | ci = pi; 28 | } 29 | } 30 | 31 | public T Dequeue() 32 | { 33 | // assumes pq is not empty; up to calling code 34 | int li = data.Count - 1; // last index (before removal) 35 | T frontItem = data[0]; // fetch the front 36 | data[0] = data[li]; 37 | data.RemoveAt(li); 38 | 39 | --li; // last index (after removal) 40 | int pi = 0; // parent index. start at front of pq 41 | while (true) 42 | { 43 | int ci = pi * 2 + 1; // left child index of parent 44 | if (ci > li) break; // no children so done 45 | int rc = ci + 1; // right child 46 | if (rc <= li && data[rc].CompareTo(data[ci]) < 0) // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead 47 | ci = rc; 48 | if (data[pi].CompareTo(data[ci]) <= 0) break; // parent is smaller than (or equal to) smallest child so done 49 | T tmp = data[pi]; data[pi] = data[ci]; data[ci] = tmp; // swap parent and child 50 | pi = ci; 51 | } 52 | return frontItem; 53 | } 54 | 55 | public T Peek() 56 | { 57 | T frontItem = data[0]; 58 | return frontItem; 59 | } 60 | 61 | public int Count 62 | { 63 | get { return data.Count; } 64 | } 65 | 66 | public override string ToString() 67 | { 68 | string s = ""; 69 | for (int i = 0; i < data.Count; ++i) 70 | s += data[i].ToString() + " "; 71 | s += "count = " + data.Count; 72 | return s; 73 | } 74 | 75 | public void Clear() 76 | { 77 | data.Clear(); 78 | } 79 | 80 | public bool IsConsistent() 81 | { 82 | // is the heap property true for all data? 83 | if (data.Count == 0) return true; 84 | int li = data.Count - 1; // last index 85 | for (int pi = 0; pi < data.Count; ++pi) // each parent index 86 | { 87 | int lci = 2 * pi + 1; // left child index 88 | int rci = 2 * pi + 2; // right child index 89 | 90 | if (lci <= li && data[pi].CompareTo(data[lci]) > 0) return false; // if lc exists and it's greater than parent then bad. 91 | if (rci <= li && data[pi].CompareTo(data[rci]) > 0) return false; // check the right child too. 92 | } 93 | return true; // passed all checks 94 | } // IsConsistent 95 | } // PriorityQueue 96 | } 97 | -------------------------------------------------------------------------------- /S2Geometry/DataStructures/SortedMultiMapEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Google.Common.Geometry.MultiMap 9 | { 10 | internal class SortedMultiMapEnumerable : IEnumerable>, IEnumerator> 11 | { 12 | MultiMap _map; 13 | IEnumerator _keyEnumerator; 14 | IEnumerator _valueEnumerator; 15 | 16 | 17 | public SortedMultiMapEnumerable(MultiMap map) 18 | { 19 | this._map = map; 20 | Reset(); 21 | } 22 | 23 | object IEnumerator.Current 24 | { 25 | get 26 | { 27 | return Current; 28 | } 29 | } 30 | 31 | public KeyValuePair Current 32 | { 33 | get 34 | { 35 | return new KeyValuePair(_keyEnumerator.Current, _valueEnumerator.Current); 36 | } 37 | } 38 | 39 | 40 | public void Dispose() 41 | { 42 | _keyEnumerator = null; 43 | _valueEnumerator = null; 44 | _map = null; 45 | } 46 | 47 | 48 | public bool MoveNext() 49 | { 50 | if (!_valueEnumerator.MoveNext()) 51 | { 52 | if (!_keyEnumerator.MoveNext()) 53 | return false; 54 | _valueEnumerator = _map[_keyEnumerator.Current].GetEnumerator(); 55 | _valueEnumerator.MoveNext(); 56 | return true; 57 | } 58 | return true; 59 | } 60 | 61 | public void Reset() 62 | { 63 | _keyEnumerator = _map.Keys.OrderBy(k => k).GetEnumerator(); 64 | _valueEnumerator = new List().GetEnumerator(); 65 | } 66 | 67 | public IEnumerator> GetEnumerator() 68 | { 69 | return new SortedMultiMapEnumerable(_map); 70 | } 71 | 72 | IEnumerator IEnumerable.GetEnumerator() 73 | { 74 | return GetEnumerator(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /S2Geometry/FpUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Google.Common.Geometry 9 | { 10 | // Data from here 11 | //http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/DoubleConsts.java/?v=source 12 | internal static class DoubleConsts 13 | { 14 | /** 15 | * The number of logical bits in the significand of a 16 | * double number, including the implicit bit. 17 | */ 18 | public const int SignificandWidth = 53; 19 | 20 | /** 21 | * Maximum exponent a finite double number may have. 22 | * It is equal to the value returned by 23 | * Math.ilogb(Double.MAX_VALUE). 24 | */ 25 | public const int MaxExponent = 1023; 26 | 27 | /** 28 | * Minimum exponent a normalized double number may 29 | * have. It is equal to the value returned by 30 | * Math.ilogb(Double.MIN_NORMAL). 31 | */ 32 | public const int MinExponent = -1022; 33 | 34 | /** 35 | * Bit mask to isolate the exponent field of a 36 | * double. 37 | */ 38 | public const long ExpBitMask = 0x7FF0000000000000L; 39 | 40 | /** 41 | * Bias used in representing a double exponent. 42 | */ 43 | public const int ExpBias = 1023; 44 | } 45 | 46 | // Data from here http://grepcode.com/file_/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/FpUtils.java/?v=source 47 | internal static class FpUtils 48 | { 49 | private static readonly double twoToTheDoubleScaleUp = PowerOfTwoD(512); 50 | private static readonly double twoToTheDoubleScaleDown = PowerOfTwoD(-512); 51 | 52 | /** 53 | * Returns a floating-point power of two in the normal range. 54 | */ 55 | 56 | private static double PowerOfTwoD(int n) 57 | { 58 | Debug.Assert(n >= DoubleConsts.MinExponent && n <= DoubleConsts.MaxExponent); 59 | return BitConverter.Int64BitsToDouble((((long)n + (long)DoubleConsts.ExpBias) << 60 | (DoubleConsts.SignificandWidth - 1)) 61 | & DoubleConsts.ExpBitMask); 62 | } 63 | 64 | /** 65 | * Return d × 66 | * 2scale_factor rounded as if performed 67 | * by a single correctly rounded floating-point multiply to a 68 | * member of the double value set. See §4.2.3 70 | * of the Java 71 | * Language Specification for a discussion of floating-point 72 | * value sets. If the exponent of the result is between the 73 | * double's minimum exponent and maximum exponent, 74 | * the answer is calculated exactly. If the exponent of the 75 | * result would be larger than doubles's maximum 76 | * exponent, an infinity is returned. Note that if the result is 77 | * subnormal, precision may be lost; that is, when scalb(x, 78 | * n) is subnormal, scalb(scalb(x, n), -n) may 79 | * not equal x. When the result is non-NaN, the result has 80 | * the same sign as d. 81 | * 82 | *

83 | * Special cases: 84 | *

    85 | *
  • If the first argument is NaN, NaN is returned. 86 | *
  • If the first argument is infinite, then an infinity of the 87 | * same sign is returned. 88 | *
  • If the first argument is zero, then a zero of the same 89 | * sign is returned. 90 | *
91 | * 92 | * @param d number to be scaled by a power of two. 93 | * @param scale_factor power of 2 used to scale d 94 | * @return d * 2scale_factor 95 | * @author Joseph D. Darcy 96 | */ 97 | 98 | public static double Scalb(double d, int scaleFactor) 99 | { 100 | /* 101 | * This method does not need to be declared strictfp to 102 | * compute the same correct result on all platforms. When 103 | * scaling up, it does not matter what order the 104 | * multiply-store operations are done; the result will be 105 | * finite or overflow regardless of the operation ordering. 106 | * However, to get the correct result when scaling down, a 107 | * particular ordering must be used. 108 | * 109 | * When scaling down, the multiply-store operations are 110 | * sequenced so that it is not possible for two consecutive 111 | * multiply-stores to return subnormal results. If one 112 | * multiply-store result is subnormal, the next multiply will 113 | * round it away to zero. This is done by first multiplying 114 | * by 2 ^ (scale_factor % n) and then multiplying several 115 | * times by by 2^n as needed where n is the exponent of number 116 | * that is a covenient power of two. In this way, at most one 117 | * real rounding error occurs. If the double value set is 118 | * being used exclusively, the rounding will occur on a 119 | * multiply. If the double-extended-exponent value set is 120 | * being used, the products will (perhaps) be exact but the 121 | * stores to d are guaranteed to round to the double value 122 | * set. 123 | * 124 | * It is _not_ a valid implementation to first multiply d by 125 | * 2^MIN_EXPONENT and then by 2 ^ (scale_factor % 126 | * MIN_EXPONENT) since even in a strictfp program double 127 | * rounding on underflow could occur; e.g. if the scale_factor 128 | * argument was (MIN_EXPONENT - n) and the exponent of d was a 129 | * little less than -(MIN_EXPONENT - n), meaning the final 130 | * result would be subnormal. 131 | * 132 | * Since exact reproducibility of this method can be achieved 133 | * without any undue performance burden, there is no 134 | * compelling reason to allow double rounding on underflow in 135 | * scalb. 136 | */ 137 | 138 | // magnitude of a power of two so large that scaling a finite 139 | // nonzero value by it would be guaranteed to over or 140 | // underflow; due to rounding, scaling down takes takes an 141 | // additional power of two which is reflected here 142 | const int MAX_SCALE = DoubleConsts.MaxExponent + -DoubleConsts.MinExponent + 143 | DoubleConsts.SignificandWidth + 1; 144 | var expAdjust = 0; 145 | var scaleIncrement = 0; 146 | var expDelta = Double.NaN; 147 | 148 | // Make sure scaling factor is in a reasonable range 149 | 150 | if (scaleFactor < 0) 151 | { 152 | scaleFactor = Math.Max(scaleFactor, -MAX_SCALE); 153 | scaleIncrement = -512; 154 | expDelta = twoToTheDoubleScaleDown; 155 | } 156 | else 157 | { 158 | scaleFactor = Math.Min(scaleFactor, MAX_SCALE); 159 | scaleIncrement = 512; 160 | expDelta = twoToTheDoubleScaleUp; 161 | } 162 | 163 | // Calculate (scale_factor % +/-512), 512 = 2^9, using 164 | // technique from "Hacker's Delight" section 10-2. 165 | var u = unchecked ((uint)(scaleFactor >> 9 - 1)); 166 | u = u >> 32 - 9; 167 | var t = unchecked ((int)u); 168 | expAdjust = ((scaleFactor + t) & (512 - 1)) - t; 169 | 170 | d *= PowerOfTwoD(expAdjust); 171 | scaleFactor -= expAdjust; 172 | 173 | while (scaleFactor != 0) 174 | { 175 | d *= expDelta; 176 | scaleFactor -= scaleIncrement; 177 | } 178 | return d; 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /S2Geometry/IS2Region.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | /// 10 | /// An IS2Region represents a two-dimensional region over the unit sphere. It is 11 | /// an abstract interface with various concrete subtypes. 12 | /// The main purpose of this interface is to allow complex regions to be 13 | /// approximated as simpler regions. So rather than having a wide variety of 14 | /// virtual methods that are implemented by all subtypes, the interface is 15 | /// restricted to methods that are useful for computing approximations. 16 | /// 17 | public interface IS2Region 18 | { 19 | /// 20 | /// Return a bounding spherical cap. 21 | /// 22 | /// 23 | S2Cap CapBound { get; } 24 | 25 | 26 | /// 27 | /// Return a bounding latitude-longitude rectangle. 28 | /// 29 | /// 30 | S2LatLngRect RectBound { get; } 31 | 32 | /// 33 | /// If this method returns true, the region completely contains the given cell. 34 | /// Otherwise, either the region does not contain the cell or the containment 35 | /// relationship could not be determined. 36 | /// 37 | /// 38 | /// 39 | bool Contains(S2Cell cell); 40 | 41 | /// 42 | /// If this method returns false, the region does not intersect the given cell. 43 | /// Otherwise, either region intersects the cell, or the intersection 44 | /// relationship could not be determined. 45 | /// 46 | /// 47 | /// 48 | bool MayIntersect(S2Cell cell); 49 | } 50 | } -------------------------------------------------------------------------------- /S2Geometry/NullObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace Google.Common.Geometry 5 | { 6 | internal struct NullObject : IEquatable where T : class 7 | { 8 | private bool isNotNull;// default property initializers are not supported for structs 9 | 10 | private NullObject(T item, bool isNull) : this() 11 | { 12 | this.isNotNull = !isNull; 13 | this.Item = item; 14 | } 15 | 16 | public NullObject(T item) : this(item, item == null) 17 | { 18 | } 19 | 20 | public static NullObject Null() 21 | { 22 | return new NullObject(); 23 | } 24 | 25 | public T Item { get; private set; } 26 | 27 | public bool IsNull() 28 | { 29 | return !this.isNotNull; 30 | } 31 | 32 | public bool IsNotNull() 33 | { 34 | return this.isNotNull; 35 | } 36 | 37 | public static implicit operator T(NullObject nullObject) 38 | { 39 | return nullObject.Item; 40 | } 41 | 42 | public static implicit operator NullObject(T item) 43 | { 44 | return new NullObject(item); 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return (Item != null) ? Item.ToString() : "NULL"; 50 | } 51 | 52 | public override int GetHashCode() 53 | { 54 | unchecked 55 | { 56 | if (this.IsNull()) 57 | return 0; 58 | 59 | var result = Item.GetHashCode(); 60 | 61 | if (result >= 0) 62 | result++; 63 | 64 | return result; 65 | } 66 | } 67 | 68 | public bool Equals(T obj) 69 | { 70 | if (obj == null) 71 | return this.IsNull(); 72 | 73 | if (!(obj is T)) 74 | return false; 75 | 76 | var no = (NullObject)obj; 77 | 78 | if (this.IsNull()) 79 | return no.IsNull(); 80 | 81 | if (no.IsNull()) 82 | return false; 83 | 84 | return this.Item.Equals(no.Item); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /S2Geometry/Preconditions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | internal static class Preconditions 10 | { 11 | public static void CheckArgument(bool expression, string message = null) 12 | { 13 | if (!expression) 14 | throw new ArgumentException(message ?? string.Empty); 15 | } 16 | 17 | public static void CheckState(bool expression, string message = null) 18 | { 19 | if (!expression) 20 | throw new InvalidOperationException(message ?? "bad state"); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /S2Geometry/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("S2Geometry")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("S2Geometry")] 14 | [assembly: AssemblyCopyright("Copyright © Oren Novotny 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.0.0")] 30 | [assembly: AssemblyFileVersion("1.0.0.0")] 31 | [assembly: InternalsVisibleTo("S2Geometry.Tests")] 32 | -------------------------------------------------------------------------------- /S2Geometry/R1Interval.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | /// 10 | /// An R1Interval represents a closed, bounded interval on the real line. It is 11 | /// capable of representing the empty interval (containing no points) and 12 | /// zero-length intervals (containing a single point). 13 | /// 14 | public struct R1Interval : IEquatable 15 | { 16 | /// 17 | /// Returns an empty interval. (Any interval where lo > hi is considered empty.) 18 | /// 19 | public static R1Interval Empty = new R1Interval(1, 0); 20 | 21 | private readonly double _hi; 22 | private readonly double _lo; 23 | 24 | public R1Interval(double lo, double hi) 25 | { 26 | _lo = lo; 27 | _hi = hi; 28 | } 29 | 30 | public double Lo 31 | { 32 | get { return _lo; } 33 | } 34 | 35 | public double Hi 36 | { 37 | get { return _hi; } 38 | } 39 | 40 | public double Center 41 | { 42 | get { return 0.5*(Lo + Hi); } 43 | } 44 | 45 | /** 46 | * Return the length of the interval. The length of an empty interval is 47 | * negative. 48 | */ 49 | 50 | public double Length 51 | { 52 | get { return Hi - Lo; } 53 | } 54 | 55 | public bool IsEmpty 56 | { 57 | get { return Lo > Hi; } 58 | } 59 | 60 | public bool Equals(R1Interval other) 61 | { 62 | return (_hi.Equals(other._hi) && _lo.Equals(other._lo)) || (IsEmpty && other.IsEmpty); 63 | } 64 | 65 | public override bool Equals(object obj) 66 | { 67 | if (ReferenceEquals(null, obj)) return false; 68 | return obj is R1Interval && Equals((R1Interval)obj); 69 | } 70 | 71 | 72 | public override int GetHashCode() 73 | { 74 | unchecked 75 | { 76 | return (_hi.GetHashCode()*397) ^ _lo.GetHashCode(); 77 | } 78 | } 79 | 80 | public static bool operator ==(R1Interval left, R1Interval right) 81 | { 82 | return Equals(left, right); 83 | } 84 | 85 | public static bool operator !=(R1Interval left, R1Interval right) 86 | { 87 | return !Equals(left, right); 88 | } 89 | 90 | 91 | /** 92 | * Convenience method to construct an interval containing a single point. 93 | */ 94 | 95 | public static R1Interval FromPoint(double p) 96 | { 97 | return new R1Interval(p, p); 98 | } 99 | 100 | /** 101 | * Convenience method to construct the minimal interval containing the two 102 | * given points. This is equivalent to starting with an empty interval and 103 | * calling AddPoint() twice, but it is more efficient. 104 | */ 105 | 106 | public static R1Interval FromPointPair(double p1, double p2) 107 | { 108 | if (p1 <= p2) 109 | { 110 | return new R1Interval(p1, p2); 111 | } 112 | else 113 | { 114 | return new R1Interval(p2, p1); 115 | } 116 | } 117 | 118 | /** 119 | * Return true if the interval is empty, i.e. it contains no points. 120 | */ 121 | 122 | /** 123 | * Return the center of the interval. For empty intervals, the result is 124 | * arbitrary. 125 | */ 126 | 127 | public bool Contains(double p) 128 | { 129 | return p >= Lo && p <= Hi; 130 | } 131 | 132 | public bool InteriorContains(double p) 133 | { 134 | return p > Lo && p < Hi; 135 | } 136 | 137 | /** Return true if this interval contains the interval 'y'. */ 138 | 139 | public bool Contains(R1Interval y) 140 | { 141 | if (y.IsEmpty) 142 | { 143 | return true; 144 | } 145 | return y.Lo >= Lo && y.Hi <= Hi; 146 | } 147 | 148 | /** 149 | * Return true if the interior of this interval contains the entire interval 150 | * 'y' (including its boundary). 151 | */ 152 | 153 | public bool InteriorContains(R1Interval y) 154 | { 155 | if (y.IsEmpty) 156 | { 157 | return true; 158 | } 159 | return y.Lo > Lo && y.Hi < Hi; 160 | } 161 | 162 | /** 163 | * Return true if this interval intersects the given interval, i.e. if they 164 | * have any points in common. 165 | */ 166 | 167 | public bool Intersects(R1Interval y) 168 | { 169 | if (Lo <= y.Lo) 170 | { 171 | return y.Lo <= Hi && y.Lo <= y.Hi; 172 | } 173 | else 174 | { 175 | return Lo <= y.Hi && Lo <= Hi; 176 | } 177 | } 178 | 179 | /** 180 | * Return true if the interior of this interval intersects any point of the 181 | * given interval (including its boundary). 182 | */ 183 | 184 | public bool InteriorIntersects(R1Interval y) 185 | { 186 | return y.Lo < Hi && Lo < y.Hi && Lo < Hi && y.Lo <= y.Hi; 187 | } 188 | 189 | /** Expand the interval so that it contains the given point "p". */ 190 | 191 | public R1Interval AddPoint(double p) 192 | { 193 | if (IsEmpty) 194 | { 195 | return FromPoint(p); 196 | } 197 | else if (p < Lo) 198 | { 199 | return new R1Interval(p, Hi); 200 | } 201 | else if (p > Hi) 202 | { 203 | return new R1Interval(Lo, p); 204 | } 205 | else 206 | { 207 | return new R1Interval(Lo, Hi); 208 | } 209 | } 210 | 211 | /** 212 | * Return an interval that contains all points with a distance "radius" of a 213 | * point in this interval. Note that the expansion of an empty interval is 214 | * always empty. 215 | */ 216 | 217 | public R1Interval Expanded(double radius) 218 | { 219 | // assert (radius >= 0); 220 | if (IsEmpty) 221 | { 222 | return this; 223 | } 224 | return new R1Interval(Lo - radius, Hi + radius); 225 | } 226 | 227 | /** 228 | * Return the smallest interval that contains this interval and the given 229 | * interval "y". 230 | */ 231 | 232 | public R1Interval Union(R1Interval y) 233 | { 234 | if (IsEmpty) 235 | { 236 | return y; 237 | } 238 | if (y.IsEmpty) 239 | { 240 | return this; 241 | } 242 | return new R1Interval(Math.Min(Lo, y.Lo), Math.Max(Hi, y.Hi)); 243 | } 244 | 245 | /** 246 | * Return the intersection of this interval with the given interval. Empty 247 | * intervals do not need to be special-cased. 248 | */ 249 | 250 | public R1Interval Intersection(R1Interval y) 251 | { 252 | return new R1Interval(Math.Max(Lo, y.Lo), Math.Min(Hi, y.Hi)); 253 | } 254 | 255 | public bool ApproxEquals(R1Interval y) 256 | { 257 | return ApproxEquals(y, 1e-15); 258 | } 259 | 260 | /** 261 | * Return true if length of the symmetric difference between the two intervals 262 | * is at most the given tolerance. 263 | * 264 | */ 265 | 266 | public bool ApproxEquals(R1Interval y, double maxError) 267 | { 268 | if (IsEmpty) 269 | { 270 | return y.Length <= maxError; 271 | } 272 | if (y.IsEmpty) 273 | { 274 | return Length <= maxError; 275 | } 276 | return Math.Abs(y.Lo - Lo) + Math.Abs(y.Hi - Hi) <= maxError; 277 | } 278 | 279 | public override string ToString() 280 | { 281 | return "[" + Lo + ", " + Hi + "]"; 282 | } 283 | } 284 | } -------------------------------------------------------------------------------- /S2Geometry/R2Vector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | public struct R2Vector : IEquatable 10 | { 11 | private readonly double _x; 12 | private readonly double _y; 13 | 14 | public R2Vector(double x, double y) 15 | { 16 | _x = x; 17 | _y = y; 18 | } 19 | 20 | /// 21 | /// Point as a list of 2; x is index 0, y is index 1 22 | /// 23 | /// 24 | public R2Vector(IList coord) 25 | { 26 | if (coord.Count != 2) 27 | { 28 | throw new ArgumentException("Points must have exactly 2 coordinates", "coord"); 29 | } 30 | _x = coord[0]; 31 | _y = coord[1]; 32 | } 33 | 34 | public double X 35 | { 36 | get { return _x; } 37 | } 38 | 39 | public double Y 40 | { 41 | get { return _y; } 42 | } 43 | 44 | public double this[int index] 45 | { 46 | get 47 | { 48 | if (index > 1) 49 | { 50 | throw new ArgumentOutOfRangeException("index"); 51 | } 52 | return index == 0 ? _x : _y; 53 | } 54 | } 55 | 56 | public double Norm2 57 | { 58 | get { return (_x*_x) + (_y*_y); } 59 | } 60 | 61 | public bool Equals(R2Vector other) 62 | { 63 | return _y.Equals(other._y) && _x.Equals(other._x); 64 | } 65 | 66 | public override bool Equals(object obj) 67 | { 68 | return obj is R2Vector && Equals((R2Vector)obj); 69 | } 70 | 71 | 72 | /** 73 | * Calcualates hashcode based on stored coordinates. Since we want +0.0 and 74 | * -0.0 to be treated the same, we ignore the sign of the coordinates. 75 | */ 76 | 77 | public override int GetHashCode() 78 | { 79 | unchecked 80 | { 81 | return (Math.Abs(_y).GetHashCode()*397) ^ Math.Abs(_x).GetHashCode(); 82 | } 83 | } 84 | 85 | public static bool operator ==(R2Vector left, R2Vector right) 86 | { 87 | return Equals(left, right); 88 | } 89 | 90 | public static bool operator !=(R2Vector left, R2Vector right) 91 | { 92 | return !Equals(left, right); 93 | } 94 | 95 | public static R2Vector operator +(R2Vector p1, R2Vector p2) 96 | { 97 | return new R2Vector(p1._x + p2._x, p1._y + p2._y); 98 | } 99 | 100 | 101 | public static R2Vector operator *(R2Vector p, double m) 102 | { 103 | return new R2Vector(m*p._x, m*p._y); 104 | } 105 | 106 | public static double DotProd(R2Vector p1, R2Vector p2) 107 | { 108 | return (p1._x*p2._x) + (p1._y*p2._y); 109 | } 110 | 111 | public double DotProd(R2Vector that) 112 | { 113 | return DotProd(this, that); 114 | } 115 | 116 | public double CrossProd(R2Vector that) 117 | { 118 | return _x*that._y - _y*that._x; 119 | } 120 | 121 | public static bool operator <(R2Vector x, R2Vector y) 122 | { 123 | if (x._x < y._x) 124 | { 125 | return true; 126 | } 127 | if (y._x < x._x) 128 | { 129 | return false; 130 | } 131 | if (x._y < y._y) 132 | { 133 | return true; 134 | } 135 | return false; 136 | } 137 | 138 | public static bool operator >(R2Vector x, R2Vector y) 139 | { 140 | if (x._x > y._x) 141 | { 142 | return true; 143 | } 144 | if (y._x > x._x) 145 | { 146 | return false; 147 | } 148 | if (x._y > y._y) 149 | { 150 | return true; 151 | } 152 | return false; 153 | } 154 | 155 | public override string ToString() 156 | { 157 | return "(" + _x + ", " + _y + ")"; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /S2Geometry/S1Angle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | public struct S1Angle : IEquatable, IComparable 10 | { 11 | private readonly double _radians; 12 | 13 | private S1Angle(double radians) 14 | { 15 | _radians = radians; 16 | } 17 | 18 | 19 | /// 20 | /// Return the angle between two points, which is also equal to the distance 21 | /// between these points on the unit sphere. The points do not need to be 22 | /// normalized. 23 | /// 24 | /// 25 | /// 26 | public S1Angle(S2Point x, S2Point y) 27 | { 28 | _radians = x.Angle(y); 29 | } 30 | 31 | public double Radians 32 | { 33 | get { return _radians; } 34 | } 35 | 36 | public double Degrees 37 | { 38 | get { return _radians*(180/Math.PI); } 39 | } 40 | 41 | 42 | public int CompareTo(S1Angle other) 43 | { 44 | return _radians < other._radians ? -1 : _radians > other._radians ? 1 : 0; 45 | } 46 | 47 | public bool Equals(S1Angle other) 48 | { 49 | return _radians.Equals(other._radians); 50 | } 51 | 52 | public override bool Equals(object obj) 53 | { 54 | if (ReferenceEquals(null, obj)) return false; 55 | return obj is S1Angle && Equals((S1Angle)obj); 56 | } 57 | 58 | public override int GetHashCode() 59 | { 60 | return _radians.GetHashCode(); 61 | } 62 | 63 | public static bool operator ==(S1Angle left, S1Angle right) 64 | { 65 | return Equals(left, right); 66 | } 67 | 68 | public static bool operator !=(S1Angle left, S1Angle right) 69 | { 70 | return !Equals(left, right); 71 | } 72 | 73 | public long E5() 74 | { 75 | return (long)Math.Round(Degrees*1e5); 76 | } 77 | 78 | public long E6() 79 | { 80 | return (long)Math.Round(Degrees*1e6); 81 | } 82 | 83 | public long E7() 84 | { 85 | return (long)Math.Round(Degrees*1e7); 86 | } 87 | 88 | /** 89 | * The default constructor yields a zero angle. 90 | */ 91 | 92 | public static bool operator <(S1Angle x, S1Angle y) 93 | { 94 | return x.Radians < y.Radians; 95 | } 96 | 97 | public static bool operator >(S1Angle x, S1Angle y) 98 | { 99 | return x.Radians > y.Radians; 100 | } 101 | 102 | public static bool operator <=(S1Angle x, S1Angle y) 103 | { 104 | return x.Radians <= y.Radians; 105 | } 106 | 107 | public static bool operator >=(S1Angle x, S1Angle y) 108 | { 109 | return x.Radians >= y.Radians; 110 | } 111 | 112 | public static S1Angle Max(S1Angle left, S1Angle right) 113 | { 114 | return right > left ? right : left; 115 | } 116 | 117 | public static S1Angle Min(S1Angle left, S1Angle right) 118 | { 119 | return right > left ? left : right; 120 | } 121 | 122 | public static S1Angle FromRadians(double radians) 123 | { 124 | return new S1Angle(radians); 125 | } 126 | 127 | public static S1Angle FromDegrees(double degrees) 128 | { 129 | return new S1Angle(degrees*(Math.PI/180)); 130 | } 131 | 132 | public static S1Angle E5(long e5) 133 | { 134 | return FromDegrees(e5*1e-5); 135 | } 136 | 137 | public static S1Angle E6(long e6) 138 | { 139 | // Multiplying by 1e-6 isn't quite as accurate as dividing by 1e6, 140 | // but it's about 10 times faster and more than accurate enough. 141 | return FromDegrees(e6*1e-6); 142 | } 143 | 144 | public static S1Angle E7(long e7) 145 | { 146 | return FromDegrees(e7*1e-7); 147 | } 148 | 149 | /** 150 | * Writes the angle in degrees with a "d" suffix, e.g. "17.3745d". By default 151 | * 6 digits are printed; this can be changed using setprecision(). Up to 17 152 | * digits are required to distinguish one angle from another. 153 | */ 154 | 155 | public override string ToString() 156 | { 157 | return Degrees + "d"; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /S2Geometry/S2AreaCentroid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | /** 10 | * The area of an interior, i.e. the region on the left side of an odd 11 | * number of loops and optionally a centroid. 12 | * The area is between 0 and 4*Pi. If it has a centroid, it is 13 | * the true centroid of the interiord multiplied by the area of the shape. 14 | * Note that the centroid may not be contained by the shape. 15 | * 16 | * @author dbentley@google.com (Daniel Bentley) 17 | */ 18 | 19 | public struct S2AreaCentroid 20 | { 21 | private readonly double _area; 22 | private readonly S2Point? _centroid; 23 | 24 | public S2AreaCentroid(double area, S2Point? centroid = null) 25 | { 26 | this._area = area; 27 | this._centroid = centroid; 28 | } 29 | 30 | public double Area 31 | { 32 | get { return _area; } 33 | } 34 | 35 | public S2Point? Centroid 36 | { 37 | get { return _centroid; } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /S2Geometry/S2Edge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | /** 10 | * An abstract directed edge from one S2Point to another S2Point. 11 | * 12 | * @author kirilll@google.com (Kirill Levin) 13 | */ 14 | 15 | public struct S2Edge : IEquatable 16 | { 17 | private readonly S2Point _end; 18 | private readonly S2Point _start; 19 | 20 | public S2Edge(S2Point start, S2Point end) 21 | { 22 | _start = start; 23 | _end = end; 24 | } 25 | 26 | public S2Point Start 27 | { 28 | get { return _start; } 29 | } 30 | 31 | public S2Point End 32 | { 33 | get { return _end; } 34 | } 35 | 36 | public bool Equals(S2Edge other) 37 | { 38 | return _end.Equals(other._end) && _start.Equals(other._start); 39 | } 40 | 41 | public override bool Equals(object obj) 42 | { 43 | if (ReferenceEquals(null, obj)) return false; 44 | return obj is S2Edge && Equals((S2Edge)obj); 45 | } 46 | 47 | public override int GetHashCode() 48 | { 49 | unchecked 50 | { 51 | return (_end.GetHashCode()*397) ^ _start.GetHashCode(); 52 | } 53 | } 54 | 55 | 56 | public static bool operator ==(S2Edge left, S2Edge right) 57 | { 58 | return Equals(left, right); 59 | } 60 | 61 | public static bool operator !=(S2Edge left, S2Edge right) 62 | { 63 | return !Equals(left, right); 64 | } 65 | 66 | public override string ToString() 67 | { 68 | return string.Format("Edge: ({0} -> {1})\n or [{2} -> {3}]", 69 | _start.ToDegreesString(), _end.ToDegreesString(), _start, _end); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /S2Geometry/S2Geometry.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 14.0 6 | Debug 7 | AnyCPU 8 | {5E82F402-F876-4A29-B59E-1BA21591A44A} 9 | Library 10 | Properties 11 | Google.Common.Geometry 12 | S2Geometry 13 | v5.0 14 | $(IntermediateOutputPath)AssemblyTFMAttribute.cs 15 | 16 | 17 | 512 18 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | ..\ 20 | true 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | bin\Release\S2Geometry.xml 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 87 | 88 | 89 | 90 | .NETStandard,Version=v1.0 91 | $(RealTargetFrameworkMoniker) 92 | 93 | // <autogenerated /> 94 | using System%3b 95 | using System.Reflection%3b 96 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute("$(RealTargetFrameworkMoniker)", FrameworkDisplayName = "$(RealTargetFrameworkMonikerDisplayName)")] 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /S2Geometry/S2LatLng.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | /** 10 | * This class represents a point on the unit sphere as a pair of 11 | * latitude-longitude coordinates. Like the rest of the "geometry" package, the 12 | * intent is to represent spherical geometry as a mathematical abstraction, so 13 | * functions that are specifically related to the Earth's geometry (e.g. 14 | * easting/northing conversions) should be put elsewhere. 15 | * 16 | */ 17 | 18 | public struct S2LatLng : IEquatable 19 | { 20 | public const double EarthRadiusMeters = 6367000.0; 21 | 22 | /** The center point the lat/lng coordinate system. */ 23 | public static readonly S2LatLng Center = new S2LatLng(0.0, 0.0); 24 | 25 | private readonly double _latRadians; 26 | private readonly double _lngRadians; 27 | 28 | private S2LatLng(double latRadians, double lngRadians) 29 | { 30 | _latRadians = latRadians; 31 | _lngRadians = lngRadians; 32 | } 33 | 34 | /** 35 | * Basic constructor. The latitude and longitude must be within the ranges 36 | * allowed by is_valid() below. 37 | * 38 | * TODO(dbeaumont): Make this a static factory method (fromLatLng() ?). 39 | */ 40 | 41 | public S2LatLng(S1Angle lat, S1Angle lng) : this(lat.Radians, lng.Radians) 42 | { 43 | } 44 | 45 | 46 | /** 47 | * Convert a point (not necessarily normalized) to an S2LatLng. 48 | * 49 | * TODO(dbeaumont): Make this a static factory method (fromPoint() ?). 50 | */ 51 | 52 | public S2LatLng(S2Point p) : this(Math.Atan2(p.Z, Math.Sqrt(p.X*p.X + p.Y*p.Y)), Math.Atan2(p.Y, p.X)) 53 | { 54 | // The latitude and longitude are already normalized. We use atan2 to 55 | // compute the latitude because the input vector is not necessarily unit 56 | // length, and atan2 is much more accurate than asin near the poles. 57 | // Note that atan2(0, 0) is defined to be zero. 58 | } 59 | 60 | internal S1Angle Lat 61 | { 62 | get { return S1Angle.FromRadians(_latRadians); } 63 | } 64 | 65 | /** Returns the latitude of this point as radians. */ 66 | 67 | public double LatRadians 68 | { 69 | get { return _latRadians; } 70 | } 71 | 72 | /** Returns the latitude of this point as degrees. */ 73 | 74 | public double LatDegrees 75 | { 76 | get { return 180.0/Math.PI*_latRadians; } 77 | } 78 | 79 | /** Returns the longitude of this point as a new S1Angle. */ 80 | 81 | internal S1Angle Lng 82 | { 83 | get { return S1Angle.FromRadians(_lngRadians); } 84 | } 85 | 86 | /** Returns the longitude of this point as radians. */ 87 | 88 | public double LngRadians 89 | { 90 | get { return _lngRadians; } 91 | } 92 | 93 | /** Returns the longitude of this point as degrees. */ 94 | 95 | public double LngDegrees 96 | { 97 | get { return 180.0/Math.PI*_lngRadians; } 98 | } 99 | 100 | /** 101 | * Return true if the latitude is between -90 and 90 degrees inclusive and the 102 | * longitude is between -180 and 180 degrees inclusive. 103 | */ 104 | 105 | public bool IsValid 106 | { 107 | get { return Math.Abs(Lat.Radians) <= S2.PiOver2 && Math.Abs(Lng.Radians) <= S2.Pi; } 108 | } 109 | 110 | /** 111 | * Returns a new S2LatLng based on this instance for which {@link #isValid()} 112 | * will be {@code true}. 113 | *
    114 | *
  • Latitude is clipped to the range {@code [-90, 90]} 115 | *
  • Longitude is normalized to be in the range {@code [-180, 180]} 116 | *
117 | *

If the current point is valid then the returned point will have the same 118 | * coordinates. 119 | */ 120 | 121 | public S2LatLng Normalized 122 | { 123 | get 124 | { 125 | // drem(x, 2 * S2.M_PI) reduces its argument to the range 126 | // [-S2.M_PI, S2.M_PI] inclusive, which is what we want here. 127 | return new S2LatLng(Math.Max(-S2.PiOver2, Math.Min(S2.PiOver2, Lat.Radians)), 128 | Math.IEEERemainder(Lng.Radians, 2*S2.Pi)); 129 | } 130 | } 131 | 132 | public bool Equals(S2LatLng other) 133 | { 134 | return _lngRadians.Equals(other._lngRadians) && _latRadians.Equals(other._latRadians); 135 | } 136 | 137 | public override bool Equals(object obj) 138 | { 139 | if (ReferenceEquals(null, obj)) return false; 140 | return obj is S2LatLng && Equals((S2LatLng)obj); 141 | } 142 | 143 | public override int GetHashCode() 144 | { 145 | unchecked 146 | { 147 | return (_lngRadians.GetHashCode()*397) ^ _latRadians.GetHashCode(); 148 | } 149 | } 150 | 151 | public static bool operator ==(S2LatLng left, S2LatLng right) 152 | { 153 | return left.Equals(right); 154 | } 155 | 156 | public static bool operator !=(S2LatLng left, S2LatLng right) 157 | { 158 | return !left.Equals(right); 159 | } 160 | 161 | /** 162 | * Approximate "effective" radius of the Earth in meters. 163 | */ 164 | 165 | public static S2LatLng FromRadians(double latRadians, double lngRadians) 166 | { 167 | return new S2LatLng(latRadians, lngRadians); 168 | } 169 | 170 | public static S2LatLng FromDegrees(double latDegrees, double lngDegrees) 171 | { 172 | return new S2LatLng(S1Angle.FromDegrees(latDegrees), S1Angle.FromDegrees(lngDegrees)); 173 | } 174 | 175 | public static S2LatLng FromE5(long latE5, long lngE5) 176 | { 177 | return new S2LatLng(S1Angle.E5(latE5), S1Angle.E5(lngE5)); 178 | } 179 | 180 | public static S2LatLng FromE6(long latE6, long lngE6) 181 | { 182 | return new S2LatLng(S1Angle.E6(latE6), S1Angle.E6(lngE6)); 183 | } 184 | 185 | public static S2LatLng FromE7(long latE7, long lngE7) 186 | { 187 | return new S2LatLng(S1Angle.E7(latE7), S1Angle.E7(lngE7)); 188 | } 189 | 190 | public static S1Angle Latitude(S2Point p) 191 | { 192 | // We use atan2 rather than asin because the input vector is not necessarily 193 | // unit length, and atan2 is much more accurate than asin near the poles. 194 | return S1Angle.FromRadians( 195 | Math.Atan2(p[2], Math.Sqrt(p[0]*p[0] + p[1]*p[1]))); 196 | } 197 | 198 | public static S1Angle Longitude(S2Point p) 199 | { 200 | // Note that atan2(0, 0) is defined to be zero. 201 | return S1Angle.FromRadians(Math.Atan2(p[1], p[0])); 202 | } 203 | 204 | /** This is internal to avoid ambiguity about which units are expected. */ 205 | 206 | /** Returns the latitude of this point as a new S1Angle. */ 207 | 208 | // Clamps the latitude to the range [-90, 90] degrees, and adds or subtracts 209 | // a multiple of 360 degrees to the longitude if necessary to reduce it to 210 | // the range [-180, 180]. 211 | 212 | /** Convert an S2LatLng to the equivalent unit-length vector (S2Point). */ 213 | 214 | public S2Point ToPoint() 215 | { 216 | var phi = Lat.Radians; 217 | var theta = Lng.Radians; 218 | var cosphi = Math.Cos(phi); 219 | return new S2Point(Math.Cos(theta)*cosphi, Math.Sin(theta)*cosphi, Math.Sin(phi)); 220 | } 221 | 222 | /** 223 | * Return the distance (measured along the surface of the sphere) to the given 224 | * point. 225 | */ 226 | 227 | public S1Angle GetDistance(S2LatLng o) 228 | { 229 | // This implements the Haversine formula, which is numerically stable for 230 | // small distances but only gets about 8 digits of precision for very large 231 | // distances (e.g. antipodal points). Note that 8 digits is still accurate 232 | // to within about 10cm for a sphere the size of the Earth. 233 | // 234 | // This could be fixed with another sin() and cos() below, but at that point 235 | // you might as well just convert both arguments to S2Points and compute the 236 | // distance that way (which gives about 15 digits of accuracy for all 237 | // distances). 238 | 239 | var lat1 = Lat.Radians; 240 | var lat2 = o.Lat.Radians; 241 | var lng1 = Lng.Radians; 242 | var lng2 = o.Lng.Radians; 243 | var dlat = Math.Sin(0.5*(lat2 - lat1)); 244 | var dlng = Math.Sin(0.5*(lng2 - lng1)); 245 | var x = dlat*dlat + dlng*dlng*Math.Cos(lat1)*Math.Cos(lat2); 246 | return S1Angle.FromRadians(2*Math.Atan2(Math.Sqrt(x), Math.Sqrt(Math.Max(0.0, 1.0 - x)))); 247 | // Return the distance (measured along the surface of the sphere) to the 248 | // given S2LatLng. This is mathematically equivalent to: 249 | // 250 | // S1Angle::FromRadians(ToPoint().Angle(o.ToPoint()) 251 | // 252 | // but this implementation is slightly more efficient. 253 | } 254 | 255 | /** 256 | * Returns the surface distance to the given point assuming a constant radius. 257 | */ 258 | 259 | public double GetDistance(S2LatLng o, double radius) 260 | { 261 | // TODO(dbeaumont): Maybe check that radius >= 0 ? 262 | return GetDistance(o).Radians*radius; 263 | } 264 | 265 | /** 266 | * Returns the surface distance to the given point assuming the default Earth 267 | * radius of {@link #EARTH_RADIUS_METERS}. 268 | */ 269 | 270 | public double GetEarthDistance(S2LatLng o) 271 | { 272 | return GetDistance(o, EarthRadiusMeters); 273 | } 274 | 275 | /** 276 | * Adds the given point to this point. 277 | * Note that there is no guarantee that the new point will be valid. 278 | */ 279 | 280 | public static S2LatLng operator +(S2LatLng x, S2LatLng y) 281 | { 282 | return new S2LatLng(x._latRadians + y._latRadians, x._lngRadians + y._lngRadians); 283 | } 284 | 285 | 286 | /** 287 | * Subtracts the given point from this point. 288 | * Note that there is no guarantee that the new point will be valid. 289 | */ 290 | 291 | public static S2LatLng operator -(S2LatLng x, S2LatLng y) 292 | { 293 | return new S2LatLng(x._latRadians - y._latRadians, x._lngRadians - y._lngRadians); 294 | } 295 | 296 | /** 297 | * Scales this point by the given scaling factor. 298 | * Note that there is no guarantee that the new point will be valid. 299 | */ 300 | 301 | public static S2LatLng operator *(S2LatLng x, double m) 302 | { 303 | // TODO(dbeaumont): Maybe check that m >= 0 ? 304 | return new S2LatLng(x._latRadians*m, x._lngRadians*m); 305 | } 306 | 307 | /** 308 | * Returns true if both the latitude and longitude of the given point are 309 | * within {@code maxError} radians of this point. 310 | */ 311 | 312 | public bool ApproxEquals(S2LatLng o, double maxError) 313 | { 314 | return (Math.Abs(_latRadians - o._latRadians) < maxError) 315 | && (Math.Abs(_lngRadians - o._lngRadians) < maxError); 316 | } 317 | 318 | /** 319 | * Returns true if the given point is within {@code 1e-9} radians of this 320 | * point. This corresponds to a distance of less than {@code 1cm} at the 321 | * surface of the Earth. 322 | */ 323 | 324 | public bool ApproxEquals(S2LatLng o) 325 | { 326 | return ApproxEquals(o, 1e-9); 327 | } 328 | 329 | public override String ToString() 330 | { 331 | return "(" + _latRadians + ", " + _lngRadians + ")"; 332 | } 333 | 334 | public String ToStringDegrees() 335 | { 336 | return "(" + LatDegrees + ", " + LngDegrees + ")"; 337 | } 338 | } 339 | } -------------------------------------------------------------------------------- /S2Geometry/S2Point.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Google.Common.Geometry 8 | { 9 | public struct S2Point : IEquatable, IComparable 10 | { 11 | // coordinates of the points 12 | private readonly double _x; 13 | private readonly double _y; 14 | private readonly double _z; 15 | 16 | 17 | public S2Point(double x, double y, double z) 18 | { 19 | _x = x; 20 | _y = y; 21 | _z = z; 22 | } 23 | 24 | public double X 25 | { 26 | get { return _x; } 27 | } 28 | 29 | public double Y 30 | { 31 | get { return _y; } 32 | } 33 | 34 | public double Z 35 | { 36 | get { return _z; } 37 | } 38 | 39 | public double Norm2 40 | { 41 | get { return _x*_x + _y*_y + _z*_z; } 42 | } 43 | 44 | public double Norm 45 | { 46 | get { return Math.Sqrt(Norm2); } 47 | } 48 | 49 | public S2Point Ortho 50 | { 51 | get 52 | { 53 | var k = LargestAbsComponent; 54 | S2Point temp; 55 | if (k == 1) 56 | { 57 | temp = new S2Point(1, 0, 0); 58 | } 59 | else if (k == 2) 60 | { 61 | temp = new S2Point(0, 1, 0); 62 | } 63 | else 64 | { 65 | temp = new S2Point(0, 0, 1); 66 | } 67 | return Normalize(CrossProd(this, temp)); 68 | } 69 | } 70 | 71 | /** Return the index of the largest component fabs */ 72 | 73 | public int LargestAbsComponent 74 | { 75 | get 76 | { 77 | var temp = Fabs(this); 78 | if (temp._x > temp._y) 79 | { 80 | if (temp._x > temp._z) 81 | { 82 | return 0; 83 | } 84 | else 85 | { 86 | return 2; 87 | } 88 | } 89 | else 90 | { 91 | if (temp._y > temp._z) 92 | { 93 | return 1; 94 | } 95 | else 96 | { 97 | return 2; 98 | } 99 | } 100 | } 101 | } 102 | 103 | public double this[int axis] 104 | { 105 | get { return (axis == 0) ? _x : (axis == 1) ? _y : _z; } 106 | } 107 | 108 | public int CompareTo(S2Point other) 109 | { 110 | return this < other ? -1 : (Equals(other) ? 0 : 1); 111 | } 112 | 113 | public bool Equals(S2Point other) 114 | { 115 | return _x.Equals(other._x) && _y.Equals(other._y) && _z.Equals(other._z); 116 | } 117 | 118 | public override bool Equals(object obj) 119 | { 120 | if (obj.GetType() != GetType()) return false; 121 | return Equals((S2Point)obj); 122 | } 123 | 124 | /** 125 | * Calcualates hashcode based on stored coordinates. Since we want +0.0 and 126 | * -0.0 to be treated the same, we ignore the sign of the coordinates. 127 | */ 128 | 129 | public override int GetHashCode() 130 | { 131 | unchecked 132 | { 133 | var hashCode = Math.Abs(_x).GetHashCode(); 134 | hashCode = (hashCode*397) ^ Math.Abs(_y).GetHashCode(); 135 | hashCode = (hashCode*397) ^ Math.Abs(_z).GetHashCode(); 136 | return hashCode; 137 | } 138 | } 139 | 140 | public static bool operator ==(S2Point left, S2Point right) 141 | { 142 | return Equals(left, right); 143 | } 144 | 145 | public static bool operator !=(S2Point left, S2Point right) 146 | { 147 | return !Equals(left, right); 148 | } 149 | 150 | public static S2Point operator -(S2Point p1, S2Point p2) 151 | { 152 | return new S2Point(p1._x - p2._x, p1._y - p2._y, p1._z - p2._z); 153 | } 154 | 155 | public static S2Point operator -(S2Point p) 156 | { 157 | return new S2Point(-p._x, -p._y, -p._z); 158 | } 159 | 160 | public static S2Point CrossProd(S2Point p1, S2Point p2) 161 | { 162 | return new S2Point( 163 | p1._y*p2._z - p1._z*p2._y, p1._z*p2._x - p1._x*p2._z, p1._x*p2._y - p1._y*p2._x); 164 | } 165 | 166 | public static S2Point operator +(S2Point p1, S2Point p2) 167 | { 168 | return new S2Point(p1._x + p2._x, p1._y + p2._y, p1._z + p2._z); 169 | } 170 | 171 | public double DotProd(S2Point that) 172 | { 173 | return _x*that._x + _y*that._y + _z*that._z; 174 | } 175 | 176 | public static S2Point operator *(S2Point p, double m) 177 | { 178 | return new S2Point(m*p._x, m*p._y, m*p._z); 179 | } 180 | 181 | public static S2Point operator /(S2Point p, double m) 182 | { 183 | return new S2Point(p._x/m, p._y/m, p._z/m); 184 | } 185 | 186 | 187 | /** return a vector orthogonal to this one */ 188 | 189 | public static S2Point Fabs(S2Point p) 190 | { 191 | return new S2Point(Math.Abs(p._x), Math.Abs(p._y), Math.Abs(p._z)); 192 | } 193 | 194 | public static S2Point Normalize(S2Point p) 195 | { 196 | var norm = p.Norm; 197 | if (norm != 0) 198 | { 199 | norm = 1.0/norm; 200 | } 201 | return p*norm; 202 | } 203 | 204 | 205 | /** Return the angle between two vectors in radians */ 206 | 207 | public double Angle(S2Point va) 208 | { 209 | return Math.Atan2(CrossProd(this, va).Norm, DotProd(va)); 210 | } 211 | 212 | /** 213 | * Compare two vectors, return true if all their components are within a 214 | * difference of margin. 215 | */ 216 | 217 | public bool ApproxEquals(S2Point that, double margin) 218 | { 219 | return (Math.Abs(_x - that._x) < margin) && (Math.Abs(_y - that._y) < margin) 220 | && (Math.Abs(_z - that._z) < margin); 221 | } 222 | 223 | 224 | public static bool operator <(S2Point x, S2Point y) 225 | { 226 | if (x._x < y._x) 227 | { 228 | return true; 229 | } 230 | if (y._x < x._x) 231 | { 232 | return false; 233 | } 234 | if (x._y < y._y) 235 | { 236 | return true; 237 | } 238 | if (y._y < x._y) 239 | { 240 | return false; 241 | } 242 | if (x._z < y._z) 243 | { 244 | return true; 245 | } 246 | return false; 247 | } 248 | 249 | public static bool operator >(S2Point x, S2Point y) 250 | { 251 | if (x._x > y._x) 252 | { 253 | return true; 254 | } 255 | if (y._x > x._x) 256 | { 257 | return false; 258 | } 259 | if (x._y > y._y) 260 | { 261 | return true; 262 | } 263 | if (y._y > x._y) 264 | { 265 | return false; 266 | } 267 | if (x._z > y._z) 268 | { 269 | return true; 270 | } 271 | return false; 272 | } 273 | 274 | 275 | public override string ToString() 276 | { 277 | return "(" + _x + ", " + _y + ", " + _z + ")"; 278 | } 279 | 280 | public string ToDegreesString() 281 | { 282 | var s2LatLng = new S2LatLng(this); 283 | return "(" + s2LatLng.LatDegrees + ", " 284 | + s2LatLng.LngDegrees + ")"; 285 | } 286 | } 287 | } -------------------------------------------------------------------------------- /S2Geometry/S2Polyline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Google.Common.Geometry 9 | { 10 | /** 11 | * An S2Polyline represents a sequence of zero or more vertices connected by 12 | * straight edges (geodesics). Edges of length 0 and 180 degrees are not 13 | * allowed, i.e. adjacent vertices should not be identical or antipodal. 14 | * 15 | *

Note: Polylines do not have a Contains(S2Point) method, because 16 | * "containment" is not numerically well-defined except at the polyline 17 | * vertices. 18 | * 19 | */ 20 | 21 | public struct S2Polyline : IS2Region, IEquatable 22 | { 23 | private readonly int _numVertices; 24 | private readonly S2Point[] _vertices; 25 | 26 | /** 27 | * Create a polyline that connects the given vertices. Empty polylines are 28 | * allowed. Adjacent vertices should not be identical or antipodal. All 29 | * vertices should be unit length. 30 | */ 31 | 32 | public S2Polyline(IEnumerable vertices) 33 | { 34 | // assert isValid(vertices); 35 | _vertices = vertices.ToArray(); 36 | _numVertices = _vertices.Length; 37 | } 38 | 39 | /** 40 | * Copy constructor. 41 | * 42 | * TODO(dbeaumont): Now that S2Polyline is immutable, remove this. 43 | */ 44 | 45 | public S2Polyline(S2Polyline src) 46 | { 47 | _numVertices = src.NumVertices; 48 | _vertices = (S2Point[])src._vertices.Clone(); 49 | } 50 | 51 | public int NumVertices 52 | { 53 | get { return _numVertices; } 54 | } 55 | 56 | public S1Angle ArcLengthAngle 57 | { 58 | get 59 | { 60 | double lengthSum = 0; 61 | for (var i = 1; i < NumVertices; ++i) 62 | { 63 | lengthSum += Vertex(i - 1).Angle(Vertex(i)); 64 | } 65 | return S1Angle.FromRadians(lengthSum); 66 | } 67 | } 68 | 69 | public bool Equals(S2Polyline other) 70 | { 71 | if (_numVertices != other._numVertices) 72 | { 73 | return false; 74 | } 75 | 76 | for (var i = 0; i < _vertices.Length; i++) 77 | { 78 | if (!_vertices[i].Equals(other._vertices[i])) 79 | { 80 | return false; 81 | } 82 | } 83 | return true; 84 | } 85 | 86 | public S2Cap CapBound 87 | { 88 | get { return RectBound.CapBound; } 89 | } 90 | 91 | 92 | /** Return a bounding latitude-longitude rectangle. */ 93 | 94 | public S2LatLngRect RectBound 95 | { 96 | get 97 | { 98 | var bounder = new RectBounder(); 99 | for (var i = 0; i < NumVertices; ++i) 100 | { 101 | bounder.AddPoint(Vertex(i)); 102 | } 103 | return bounder.Bound; 104 | } 105 | } 106 | 107 | /** 108 | * If this method returns true, the region completely contains the given cell. 109 | * Otherwise, either the region does not contain the cell or the containment 110 | * relationship could not be determined. 111 | */ 112 | 113 | public bool Contains(S2Cell cell) 114 | { 115 | throw new NotSupportedException( 116 | "'containment' is not numerically well-defined " + "except at the polyline vertices"); 117 | } 118 | 119 | /** 120 | * If this method returns false, the region does not intersect the given cell. 121 | * Otherwise, either region intersects the cell, or the intersection 122 | * relationship could not be determined. 123 | */ 124 | 125 | public bool MayIntersect(S2Cell cell) 126 | { 127 | if (NumVertices == 0) 128 | { 129 | return false; 130 | } 131 | 132 | // We only need to check whether the cell contains vertex 0 for correctness, 133 | // but these tests are cheap compared to edge crossings so we might as well 134 | // check all the vertices. 135 | for (var i = 0; i < NumVertices; ++i) 136 | { 137 | if (cell.Contains(Vertex(i))) 138 | { 139 | return true; 140 | } 141 | } 142 | var cellVertices = new S2Point[4]; 143 | for (var i = 0; i < 4; ++i) 144 | { 145 | cellVertices[i] = cell.GetVertex(i); 146 | } 147 | for (var j = 0; j < 4; ++j) 148 | { 149 | var crosser = 150 | new EdgeCrosser(cellVertices[j], cellVertices[(j + 1) & 3], Vertex(0)); 151 | for (var i = 1; i < NumVertices; ++i) 152 | { 153 | if (crosser.RobustCrossing(Vertex(i)) >= 0) 154 | { 155 | // There is a proper crossing, or two vertices were the same. 156 | return true; 157 | } 158 | } 159 | } 160 | return false; 161 | } 162 | 163 | public override bool Equals(object obj) 164 | { 165 | if (ReferenceEquals(null, obj)) return false; 166 | if (obj.GetType() != GetType()) return false; 167 | return Equals((S2Polyline)obj); 168 | } 169 | 170 | public override int GetHashCode() 171 | { 172 | unchecked 173 | { 174 | unchecked 175 | { 176 | var code = (_numVertices*397); 177 | foreach (var v in _vertices) 178 | { 179 | code ^= v.GetHashCode(); 180 | } 181 | 182 | return code; 183 | } 184 | } 185 | } 186 | 187 | public static bool operator ==(S2Polyline left, S2Polyline right) 188 | { 189 | return Equals(left, right); 190 | } 191 | 192 | public static bool operator !=(S2Polyline left, S2Polyline right) 193 | { 194 | return !Equals(left, right); 195 | } 196 | 197 | /** 198 | * Return true if the given vertices form a valid polyline. 199 | */ 200 | 201 | public bool IsValidPolyline(IReadOnlyList vertices) 202 | { 203 | // All vertices must be unit length. 204 | var n = vertices.Count; 205 | for (var i = 0; i < n; ++i) 206 | { 207 | if (!S2.IsUnitLength(vertices[i])) 208 | { 209 | Debug.WriteLine("Vertex " + i + " is not unit length"); 210 | return false; 211 | } 212 | } 213 | 214 | // Adjacent vertices must not be identical or antipodal. 215 | for (var i = 1; i < n; ++i) 216 | { 217 | if (vertices[i - 1].Equals(vertices[i]) 218 | || vertices[i - 1].Equals(-vertices[i])) 219 | { 220 | Debug.WriteLine("Vertices " + (i - 1) + " and " + i + " are identical or antipodal"); 221 | return false; 222 | } 223 | } 224 | 225 | return true; 226 | } 227 | 228 | public S2Point Vertex(int k) 229 | { 230 | // assert (k >= 0 && k < numVertices); 231 | return _vertices[k]; 232 | } 233 | 234 | /** 235 | * Return the angle corresponding to the total arclength of the polyline on a 236 | * unit sphere. 237 | */ 238 | 239 | /** 240 | * Return the point whose distance from vertex 0 along the polyline is the 241 | * given fraction of the polyline's total length. Fractions less than zero or 242 | * greater than one are clamped. The return value is unit length. This cost of 243 | * this function is currently linear in the number of vertices. 244 | */ 245 | 246 | public S2Point Interpolate(double fraction) 247 | { 248 | // We intentionally let the (fraction >= 1) case fall through, since 249 | // we need to handle it in the loop below in any case because of 250 | // possible roundoff errors. 251 | if (fraction <= 0) 252 | { 253 | return Vertex(0); 254 | } 255 | 256 | double lengthSum = 0; 257 | for (var i = 1; i < NumVertices; ++i) 258 | { 259 | lengthSum += Vertex(i - 1).Angle(Vertex(i)); 260 | } 261 | var target = fraction*lengthSum; 262 | for (var i = 1; i < NumVertices; ++i) 263 | { 264 | var length = Vertex(i - 1).Angle(Vertex(i)); 265 | if (target < length) 266 | { 267 | // This code interpolates with respect to arc length rather than 268 | // straight-line distance, and produces a unit-length result. 269 | var f = Math.Sin(target)/Math.Sin(length); 270 | return (Vertex(i - 1)*(Math.Cos(target) - f*Math.Cos(length))) + (Vertex(i)*f); 271 | } 272 | target -= length; 273 | } 274 | return Vertex(NumVertices - 1); 275 | } 276 | 277 | // S2Region interface (see {@code S2Region} for details): 278 | 279 | /** Return a bounding spherical cap. */ 280 | 281 | /** 282 | * Given a point, returns the index of the start point of the (first) edge on 283 | * the polyline that is closest to the given point. The polyline must have at 284 | * least one vertex. Throws IllegalStateException if this is not the case. 285 | */ 286 | 287 | public int GetNearestEdgeIndex(S2Point point) 288 | { 289 | Preconditions.CheckState(NumVertices > 0, "Empty polyline"); 290 | 291 | if (NumVertices == 1) 292 | { 293 | // If there is only one vertex, the "edge" is trivial, and it's the only one 294 | return 0; 295 | } 296 | 297 | // Initial value larger than any possible distance on the unit sphere. 298 | var minDistance = S1Angle.FromRadians(10); 299 | var minIndex = -1; 300 | 301 | // Find the line segment in the polyline that is closest to the point given. 302 | for (var i = 0; i < NumVertices - 1; ++i) 303 | { 304 | var distanceToSegment = S2EdgeUtil.GetDistance(point, Vertex(i), Vertex(i + 1)); 305 | if (distanceToSegment < minDistance) 306 | { 307 | minDistance = distanceToSegment; 308 | minIndex = i; 309 | } 310 | } 311 | return minIndex; 312 | } 313 | 314 | /** 315 | * Given a point p and the index of the start point of an edge of this polyline, 316 | * returns the point on that edge that is closest to p. 317 | */ 318 | 319 | public S2Point ProjectToEdge(S2Point point, int index) 320 | { 321 | Preconditions.CheckState(NumVertices > 0, "Empty polyline"); 322 | Preconditions.CheckState(NumVertices == 1 || index < NumVertices - 1, "Invalid edge index"); 323 | if (NumVertices == 1) 324 | { 325 | // If there is only one vertex, it is always closest to any given point. 326 | return Vertex(0); 327 | } 328 | return S2EdgeUtil.GetClosestPoint(point, Vertex(index), Vertex(index + 1)); 329 | } 330 | } 331 | } -------------------------------------------------------------------------------- /S2Geometry/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "supports": {}, 3 | "dependencies": { 4 | "NETStandard.Library": "1.6.0" 5 | }, 6 | "frameworks": { 7 | "netstandard1.0": {} 8 | } 9 | } -------------------------------------------------------------------------------- /pack.cmd: -------------------------------------------------------------------------------- 1 | nuget.exe pack S2Geometry.nuspec -Symbols --------------------------------------------------------------------------------