/** * Initialize this polygon to the intersection, union, or difference (A - B) * of the given two polygons. The "vertexMergeRadius" determines how close two * vertices must be to be merged together and how close a vertex must be to an * edge in order to be spliced into it (see S2PolygonBuilder for details). By * default, the merge radius is just large enough to compensate for errors * that occur when computing intersection points between edges * (S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE). * * If you are going to convert the resulting polygon to a lower-precision * format, it is necessary to increase the merge radius in order to get a * valid result after rounding (i.e. no duplicate vertices, etc). For example, * if you are going to convert them to geostore.PolygonProto format, then * S1Angle.e7(1) is a good value for "vertex_merge_radius". *#/ * public void initToIntersection(final S2Polygon a, final S2Polygon b) { * initToIntersectionSloppy(a, b, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE); * } * * public void initToIntersectionSloppy( * final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius) { * Preconditions.checkState(numLoops() == 0); * if (!a.bound.intersects(b.bound)) { * return; * } * * // We want the boundary of A clipped to the interior of B, * // plus the boundary of B clipped to the interior of A, * // plus one copy of any directed edges that are in both boundaries. * * S2PolygonBuilder.Options options = S2PolygonBuilder.Options.DIRECTED_XOR; * options.setMergeDistance(vertexMergeRadius); * S2PolygonBuilder builder = new S2PolygonBuilder(options); * clipBoundary(a, false, b, false, false, true, builder); * clipBoundary(b, false, a, false, false, false, builder); * if (!builder.assemblePolygon(this, null)) { * // TODO (andriy): do something more meaningful here. * log.severe("Bad directed edges"); * } * } * * public void initToUnion(final S2Polygon a, final S2Polygon b) { * initToUnionSloppy(a, b, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE); * } * * public void initToUnionSloppy(final S2Polygon a, final S2Polygon b, S1Angle vertexMergeRadius) { * Preconditions.checkState(numLoops() == 0); * * // We want the boundary of A clipped to the exterior of B, * // plus the boundary of B clipped to the exterior of A, * // plus one copy of any directed edges that are in both boundaries. * * S2PolygonBuilder.Options options = S2PolygonBuilder.Options.DIRECTED_XOR; * options.setMergeDistance(vertexMergeRadius); * S2PolygonBuilder builder = new S2PolygonBuilder(options); * clipBoundary(a, false, b, false, true, true, builder); * clipBoundary(b, false, a, false, true, false, builder); * if (!builder.assemblePolygon(this, null)) { * // TODO(andriy): do something more meaningful here. * log.severe("Bad directed edges"); * } * } * * /** * Return a polygon which is the union of the given polygons. Note: clears the * List! *#/ * public static S2Polygon destructiveUnion(List * <S2Polygon> polygons) { * return destructiveUnionSloppy(polygons, S2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE); * } * * /** * Return a polygon which is the union of the given polygons; combines * vertices that form edges that are almost identical, as defined by * vertexMergeRadius. Note: clears the List! *#/ * public static S2Polygon destructiveUnionSloppy( * List * <S2Polygon> polygons, S1Angle vertexMergeRadius) { * // Effectively create a priority queue of polygons in order of number of * // vertices. Repeatedly union the two smallest polygons and add the result * // to the queue until we have a single polygon to return. * * // map: # of vertices -> polygon * TreeMultimap * <Integer * , S2Polygon> queue = TreeMultimap.create(); * * for (S2Polygon polygon : polygons) { * queue.put(polygon.getNumVertices(), polygon); * } * polygons.clear(); * * Set * <Map.Entry * <Integer * , S2Polygon>> queueSet = queue.entries(); * while (queueSet.size() > 1) { * // Pop two simplest polygons from queue. * queueSet = queue.entries(); * Iterator * <Map.Entry * <Integer * , S2Polygon>> smallestIter = queueSet.iterator(); * * Map.Entry * <Integer * , S2Polygon> smallest = smallestIter.next(); * int aSize = smallest.getKey().intValue(); * S2Polygon aPolygon = smallest.getValue(); * smallestIter.remove(); * * smallest = smallestIter.next(); * int bSize = smallest.getKey().intValue(); * S2Polygon bPolygon = smallest.getValue(); * smallestIter.remove(); * * // Union and add result back to queue. * S2Polygon unionPolygon = new S2Polygon(); * unionPolygon.initToUnionSloppy(aPolygon, bPolygon, vertexMergeRadius); * int unionSize = aSize + bSize; * queue.put(unionSize, unionPolygon); * // We assume that the number of vertices in the union polygon is the * // sum of the number of vertices in the original polygons, which is not * // always true, but will almost always be a decent approximation, and * // faster than recomputing. * } * * if (queue.isEmpty()) { * return new S2Polygon(); * } else { * return queue.get(queue.asMap().firstKey()).first(); * } * } * * public boolean isNormalized() { * Multiset * <S2Point> vertices = HashMultiset. * <S2Point>create(); * S2Loop lastParent = null; * for (int i = 0; i < numLoops(); ++i) { * S2Loop child = loop(i); * if (child.depth() == 0) { * continue; * } * S2Loop parent = loop(getParent(i)); * if (parent != lastParent) { * vertices.clear(); * for (int j = 0; j < parent.numVertices(); ++j) { * vertices.add(parent.vertex(j)); * } * lastParent = parent; * } * int count = 0; * for (int j = 0; j < child.numVertices(); ++j) { * if (vertices.count(child.vertex(j)) > 0) { * ++count; * } * } * if (count > 1) { * return false; * } * } * return true; * } * * /** * Return true if two polygons have the same boundary except for vertex * perturbations. Both polygons must have loops with the same cyclic vertex * order and the same nesting hierarchy, but the vertex locations are allowed * to differ by up to "max_error". Note: This method mostly useful only for * testing purposes. *#/ * boolean boundaryApproxEquals(S2Polygon b, double maxError) { * if (numLoops() != b.numLoops()) { * log.severe( * "!= loops: " + Integer.toString(numLoops()) + " vs. " + Integer.toString(b.numLoops())); * return false; * } * * // For now, we assume that there is at most one candidate match for each * // loop. (So far this method is just used for testing.) * for (int i = 0; i < numLoops(); ++i) { * S2Loop aLoop = loop(i); * boolean success = false; * for (int j = 0; j < numLoops(); ++j) { * S2Loop bLoop = b.loop(j); * if (bLoop.depth() == aLoop.depth() && bLoop.boundaryApproxEquals(aLoop, maxError)) { * success = true; * break; * } * } * if (!success) { * return false; * } * } * return true; * } * * // S2Region interface (see S2Region.java for details): * * /** Return a bounding spherical cap. */ public function getCapBound() { return $this->bound->getCapBound(); }