1 /* 2 * Copyright 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.geometry; 18 19 import com.google.common.collect.Lists; 20 import com.google.common.collect.Sets; 21 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.logging.Logger; 25 26 /** 27 * Tests for {@link S2EdgeIndex}. 28 * 29 * @author andriy@google.com (Andriy Bihun) ported from util/geometry 30 * @author pilloff@google.com (Mark Pilloff) original author 31 */ 32 public strictfp class S2EdgeIndexTest extends GeometryTestCase { 33 private static final Logger log = Logger.getLogger(S2EdgeIndexTest.class.getCanonicalName()); 34 35 public static class EdgeVectorIndex extends S2EdgeIndex { 36 private List<S2Edge> edges; 37 EdgeVectorIndex(List<S2Edge> edges)38 public EdgeVectorIndex(List<S2Edge> edges) { 39 this.edges = edges; 40 } 41 42 @Override getNumEdges()43 protected int getNumEdges() { 44 return edges.size(); 45 } 46 47 @Override edgeFrom(int index)48 protected S2Point edgeFrom(int index) { 49 return edges.get(index).getStart(); 50 } 51 52 @Override edgeTo(int index)53 protected S2Point edgeTo(int index) { 54 return edges.get(index).getEnd(); 55 } 56 } 57 58 /** 59 * Generates a random edge whose center is in the given cap. 60 */ randomEdgeCrossingCap(double maxLengthMeters, S2Cap cap)61 private S2Edge randomEdgeCrossingCap(double maxLengthMeters, S2Cap cap) { 62 // Pick the edge center at random. 63 S2Point edgeCenter = samplePoint(cap); 64 // Pick two random points in a suitably sized cap about the edge center. 65 S2Cap edgeCap = S2Cap.fromAxisAngle( 66 edgeCenter, S1Angle.radians(maxLengthMeters / S2LatLng.EARTH_RADIUS_METERS / 2)); 67 S2Point p1 = samplePoint(edgeCap); 68 S2Point p2 = samplePoint(edgeCap); 69 return new S2Edge(p1, p2); 70 } 71 72 /* 73 * Generates "numEdges" random edges, of length at most "edgeLengthMetersMax" 74 * and each of whose center is in a randomly located cap with radius 75 * "capSpanMeters", and puts results into "edges". 76 */ generateRandomEarthEdges( double edgeLengthMetersMax, double capSpanMeters, int numEdges, List<S2Edge> edges)77 private void generateRandomEarthEdges( 78 double edgeLengthMetersMax, double capSpanMeters, int numEdges, List<S2Edge> edges) { 79 S2Cap cap = S2Cap.fromAxisAngle( 80 randomPoint(), S1Angle.radians(capSpanMeters / S2LatLng.EARTH_RADIUS_METERS)); 81 for (int i = 0; i < numEdges; ++i) { 82 edges.add(randomEdgeCrossingCap(edgeLengthMetersMax, cap)); 83 } 84 } 85 checkAllCrossings( List<S2Edge> allEdges, int minCrossings, int maxChecksCrossingsRatio)86 private void checkAllCrossings( 87 List<S2Edge> allEdges, int minCrossings, int maxChecksCrossingsRatio) { 88 EdgeVectorIndex index = new EdgeVectorIndex(allEdges); 89 index.computeIndex(); 90 EdgeVectorIndex.DataEdgeIterator it = new EdgeVectorIndex.DataEdgeIterator(index); 91 double totalCrossings = 0; 92 double totalIndexChecks = 0; 93 94 for (int in = 0; in < allEdges.size(); ++in) { 95 S2Edge e = allEdges.get(in); 96 97 HashSet<Integer> candidateSet = Sets.newHashSet(); 98 99 StringBuilder sb = new StringBuilder(); 100 for (it.getCandidates(e.getStart(), e.getEnd()); it.hasNext(); it.next()) { 101 candidateSet.add(it.index()); 102 sb.append(it.index()).append("/"); 103 ++totalIndexChecks; 104 } 105 106 for (int i = 0; i < allEdges.size(); ++i) { 107 int crossing = S2EdgeUtil.robustCrossing( 108 e.getStart(), e.getEnd(), allEdges.get(i).getStart(), allEdges.get(i).getEnd()); 109 if (crossing >= 0) { 110 StringBuilder sbError = new StringBuilder(); 111 sbError 112 .append("\n==CHECK_ERROR===================================\n") 113 .append("CandidateSet: ") 114 .append(sb) 115 .append("\nin=") 116 .append(in) 117 .append(" i=") 118 .append(i) 119 .append(" robustCrossing=") 120 .append(crossing) 121 .append("\nfrom:\n") 122 .append(e) 123 .append("\nto:\n") 124 .append(allEdges.get(i)) 125 .append("\n=================================================="); 126 assertTrue(sbError.toString(), candidateSet.contains(i)); 127 ++totalCrossings; 128 } 129 } 130 } 131 132 log.info( 133 "Pairs/num crossings/check crossing ratio: " 134 + Integer.toString(allEdges.size() * allEdges.size()) + "/" 135 + Double.toString(totalCrossings) + "/" 136 + Double.toString(totalIndexChecks / totalCrossings)); 137 assertTrue(minCrossings <= totalCrossings); 138 assertTrue(totalCrossings * maxChecksCrossingsRatio >= totalIndexChecks); 139 } 140 141 /* 142 * Generates random edges and tests, for each edge, that all those that cross 143 * are candidates. 144 */ tryCrossingsRandomInCap(int numEdges, double edgeLengthMax, double capSpanMeters, int minCrossings, int maxChecksCrossingsRatio)145 private void tryCrossingsRandomInCap(int numEdges, double edgeLengthMax, double capSpanMeters, 146 int minCrossings, int maxChecksCrossingsRatio) { 147 List<S2Edge> allEdges = Lists.newArrayList(); 148 generateRandomEarthEdges(edgeLengthMax, capSpanMeters, numEdges, allEdges); 149 checkAllCrossings(allEdges, minCrossings, maxChecksCrossingsRatio); 150 } 151 testSpecificEdges()152 public void testSpecificEdges() { 153 List<S2Point> ps = Lists.newArrayList(); 154 ps.add(new S2Point(0.8088625416501157, -0.40633615485481134, 0.4250086092929434)); 155 ps.add(new S2Point(0.8088939911085784, -0.40631384442755236, 0.4249700824469155)); 156 ps.add(new S2Point(0.8088088971141814, -0.40642839367135375, 0.425022503835579)); 157 ps.add(new S2Point(0.8088643962606756, -0.406333410696549, 0.4250077032402616)); 158 List<S2Edge> allEdges = Lists.newArrayList(); 159 allEdges.add(new S2Edge(ps.get(0), ps.get(1))); 160 allEdges.add(new S2Edge(ps.get(2), ps.get(3))); 161 checkAllCrossings(allEdges, 0, 16); 162 } 163 testLoopCandidateOfItself()164 public void testLoopCandidateOfItself() { 165 List<S2Point> ps = Lists.newArrayList(); // A diamond loop around 0,180. 166 ps.add(makePoint("0:178")); 167 ps.add(makePoint("-1:180")); 168 ps.add(makePoint("0:-179")); 169 ps.add(makePoint("1:-180")); 170 List<S2Edge> allEdges = Lists.newArrayList(); 171 for (int i = 0; i < 4; ++i) { 172 allEdges.add(new S2Edge(ps.get(i), ps.get((i + 1) % 4))); 173 } 174 checkAllCrossings(allEdges, 0, 16); 175 } 176 testRandomEdgeCrossings()177 public void testRandomEdgeCrossings() { 178 tryCrossingsRandomInCap(2000, 30, 5000, 500, 2); 179 tryCrossingsRandomInCap(1000, 100, 5000, 500, 3); 180 tryCrossingsRandomInCap(1000, 1000, 5000, 1000, 40); 181 tryCrossingsRandomInCap(500, 5000, 5000, 5000, 20); 182 } 183 testRandomEdgeCrossingsSparse()184 public void testRandomEdgeCrossingsSparse() { 185 for (int i = 0; i < 5; ++i) { 186 tryCrossingsRandomInCap(2000, 100, 5000, 500, 8); 187 tryCrossingsRandomInCap(2000, 300, 50000, 1000, 10); 188 } 189 } 190 } 191