Class S2Polygon
- java.lang.Object
-
- com.google.common.geometry.S2Polygon
-
- All Implemented Interfaces:
S2Region,Serializable,Comparable<S2Polygon>
@GwtCompatible(serializable=true) public final class S2Polygon extends Object implements S2Region, Comparable<S2Polygon>, Serializable
An S2Polygon is an S2Region object that represents a polygon. A polygon is defined by zero or more loops; recall that the interior of a loop is defined to be its left-hand side (seeS2Loop.)There are two different conventions for creating an S2Polygon:
First,
initNested(List)expects the input loops to be nested hierarchically. The polygon interior then consists of the set of points contained by an odd number of loops. So for example, a circular region with a hole in it would be defined as two CCW loops, with one loop containing the other. The loops can be provided in any order.When the orientation of the input loops is unknown, the nesting requirement is typically met by calling
S2Loop.normalize()on each loop (which inverts the loop if necessary so that it encloses at most half the sphere). But in fact any set of loops can be used as long as (1) there is no pair of loops that cross, and (2) there is no pair of loops whose union is the entire sphere.Second,
initOriented(List)expects the input loops to be oriented such that the polygon interior is on the left-hand side of every loop. So for example, a circular region with a hole in it would be defined using a CCW outer loop and a CW inner loop. The loop orientations must all be consistent; for example, it is not valid to have one CCW loop nested inside another CCW loop, because the region between the two loops is on the left-hand side of one loop and the right-hand side of the other.Most clients will not call these methods directly; instead they should use
S2PolygonBuilder, which has better support for dealing with imperfect data.When the polygon is initialized, the given loops are automatically converted into a canonical form consisting of "shells" and "holes". Shells and holes are both oriented CCW, and are nested hierarchically. The loops are reordered to correspond to a preorder traversal of the nesting hierarchy; initOriented may also invert some loops.
Polygons may represent any region of the sphere with a polygonal boundary, including the entire sphere (known as the "full" polygon). The full polygon consists of a single full loop (see
S2Loop.full()), whereas the empty polygon has no loops at all.Polygons have the following restrictions:
- Loops may not cross, i.e. the boundary of a loop may not intersect both the interior and exterior of any other loop.
- Loops may not share edges, i.e. if a loop contains an edge AB, then no other loop may contain AB or BA.
- Loops may share vertices, however no vertex may appear twice in a single loop (see S2Loop).
- No loop may be empty. The full loop may appear only in the full polygon.
- See Also:
- Serialized Form
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static classS2Polygon.S2PolygonIndexIndexing structure for anS2Polygon.classS2Polygon.ShapeWrapper class for indexing a polygon viaS2ShapeIndex.
-
Constructor Summary
Constructors Constructor Description S2Polygon()Creates an empty polygon.S2Polygon(S2Cell cell)Creates an S2Polygon for a given cell.S2Polygon(S2Loop loop)Copy constructor.S2Polygon(S2Polygon src)Copy constructor.S2Polygon(List<S2Loop> loops)Creates an empty polygon and then callsinitNested(List)with the given loops.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description booleanapproxContains(S2Polygon b, S1Angle vertexMergeRadius)Returns true if this polygon (A) approximately contains the given other polygon (B).static voidbreakEdgesAndAddToBuilder(S2ShapeIndex index, S2PolygonBuilder builder)Takes a set of possibly intersecting edges, stored in the S2ShapeIndex, and breaks the edges into small pieces so that there is no intersection anymore, and adds all these edges to the builder.intcompareTo(S2Polygon other)Comparator (needed by Comparable interface).booleancontains(S2Cell cell)If this method returns true, the region completely contains the given cell.booleancontains(S2Point p)The pointpdoes not need to be normalized.booleancontains(S2Polygon b)Returns true if this polygon contains the given other polygon, i.e., if polygon A contains all points contained by polygon B.static S2Polygondecode(InputStream input)Decodes a polygon that was encoded usingencode(java.io.OutputStream).voidencode(OutputStream output)Encodes the polygon into an efficient, lossless binary representation, which can be decoded by callingdecode(java.io.InputStream).booleanequals(Object o)booleanfindValidationError(S2Error error)Returns true if this is *not* a valid polygon and setserrorappropriately.doublegetArea()Returns the area of the polygon interior, i.e.S2AreaCentroidgetAreaAndCentroid()Returns the area of the polygon interior, i.e.S2CapgetCapBound()Returns a spherical cap that bounds this loop.S2PointgetCentroid()Returns the true centroid of the polygon, weighted by the area of the polygon (see s2.h for details on centroids).S1AnglegetDistance(S2Point p)Returns the shortest distance from a point P to this polygon, given as the angle formed between P, the origin, and the nearest point on the polygon to P.intgetLastDescendant(int k)Returns the index of the last loop that is contained within loopk.List<S2Loop>getLoops()Returns a view of the list ofS2Loops that make up this S2Polygon.intgetNumVertices()Returns the total number of vertices in all loops.static doublegetOverlapFraction(S2Polygon a, S2Polygon b)Returns the overlap fraction of polygon b on polygon a, i.e.intgetParent(int k)Returns the index of the parent of loopk, or -1 if it has no parent.S2LatLngRectgetRectBound()Returns a fairly tight bounding latitude-longitude rectangle.intgetSnapLevel()If all of the polygon's vertices happen to be the centers of S2Cells at some level, then returns that level, otherwise returns -1.inthashCode()S2ShapeIndexindex()Returns the index of this polygon.voidinit(List<S2Loop> loops)Initializes a polygon by callinginitNested(List).voidinitNested(List<S2Loop> loops)Initializes this polygon from a set of hierarchically nested loops.voidinitOriented(List<S2Loop> loops)LikeinitNested(List), but expects loops to be oriented such that the polygon interior is on the left-hand side of all loops.voidinitToComplement(S2Polygon a)Initializes this polygon to the complement of the given polygon.voidinitToDifference(S2Polygon a, S2Polygon b)voidinitToDifferenceSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)voidinitToIntersection(S2Polygon a, S2Polygon b)Initializes this polygon to the intersection, union, or difference (A - B) of the given two polygons.voidinitToIntersectionSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)voidinitToSimplified(S2Polygon a, S1Angle tolerance, boolean snapToCellCenters)Initializes this polygon to a polygon that contains fewer vertices and is within tolerance of the polygon a, with some caveats.voidinitToSimplifiedInCell(S2Polygon a, S2Cell cell, S1Angle tolerance)Initializes this polygon to a polygon that contains fewer vertices and is within tolerance of the polygon a, while ensuring that the vertices on the cell boundary are preserved.voidinitToSnapped(S2Polygon a, int snapLevel)Use S2PolygonBuilder to build this polygon by assembling the edges of a given polygon after snapping its vertices to the center of leaf cells.voidinitToUnion(S2Polygon a, S2Polygon b)voidinitToUnionSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)voidinitWithNestedLoops(Map<S2Loop,List<S2Loop>> nestedLoops)Initializes a polygon from a set ofS2Loops.booleanintersects(S2Polygon b)Returns true if this polygon intersects the given other polygon, i.e., if there is a point that is contained by both polygons.List<S2Polyline>intersectWithPolyline(S2Polyline in)Intersects this polygon with theS2Polylineinand returns the resulting zero or more polylines.List<S2Polyline>intersectWithPolylineSloppy(S2Polyline in, S1Angle vertexMergeRadius)Similar tointersectWithPolyline(com.google.common.geometry.S2Polyline), except that vertices will be dropped as necessary to ensure that all adjacent vertices in the sequence obtained by concatenating the output polylines will be farther thanvertexMergeRadiusapart.booleanisEmpty()booleanisFull()booleanisNormalized()Return true if every loop of this polygon shares at most one vertex with its parent loop.booleanisValid()Returns true if each loop on this polygon is valid, and if the relationships between all loops are valid.static booleanisValid(List<S2Loop> loops)Returns true if the given loops form a valid polygon, including checking whether the loops themselves are valid.S2Looploop(int k)Returns the loop at the given index.booleanmayIntersect(S2Cell target)If this method returns false, the region does not intersect the given cell.intnumLoops()S2Pointproject(S2Point p)Returns a point on the polygon that is closest to point P.voidrelease(List<S2Loop> loops)Appends the loops of this polygon to the given list and resets this polygon to be empty.S2Polygon.Shapeshape()Returns a shape wrapping this polygon.List<S2Polyline>subtractFromPolyline(S2Polyline in)Same asintersectWithPolyline(com.google.common.geometry.S2Polyline), but subtracts this polygon from the given polyline.List<S2Polyline>subtractFromPolylineSloppy(S2Polyline in, S1Angle vertexMergeRadius)Same asintersectWithPolylineSloppy(com.google.common.geometry.S2Polyline, com.google.common.geometry.S1Angle), but subtracts this polygon from the given polyline.StringtoString()Returns a human readable representation of the polygon.static S2Polygonunion(Iterable<S2Polygon> polygons)Returns a polygon that is the union of the given polygons.static S2PolygonunionSloppy(Iterable<S2Polygon> polygons, S1Angle vertexMergeRadius)Returns a polygon that is the union of the given polygons; combines vertices that form edges that are almost identical, as defined byvertexMergeRadius.
-
-
-
Constructor Detail
-
S2Polygon
public S2Polygon()
Creates an empty polygon. It can be made non-empty by callinginit(List).
-
S2Polygon
public S2Polygon(S2Cell cell)
Creates an S2Polygon for a given cell.
-
S2Polygon
public S2Polygon(List<S2Loop> loops)
Creates an empty polygon and then callsinitNested(List)with the given loops. Clears the given list.
-
S2Polygon
public S2Polygon(S2Loop loop)
Copy constructor.
-
S2Polygon
public S2Polygon(S2Polygon src)
Copy constructor.
-
-
Method Detail
-
compareTo
public int compareTo(S2Polygon other)
Comparator (needed by Comparable interface). For two polygons to be compared as equal:- They must have the same number of loops
- The loops must be ordered in the same way (this is guaranteed by the total ordering
imposed by
sortValueLoops(java.util.Map<com.google.common.geometry.S2Loop, java.util.List<com.google.common.geometry.S2Loop>>)) - Loops must be logically equivalent (even if ordered with a different starting point, e.g. ABCD and BCDA).
- Specified by:
compareToin interfaceComparable<S2Polygon>
-
init
public void init(List<S2Loop> loops)
Initializes a polygon by callinginitNested(List).
-
initNested
public void initNested(List<S2Loop> loops)
Initializes this polygon from a set of hierarchically nested loops. The polygon interior consists of the points contained by an odd number of loops. (Recall that a loop contains the set of points on its left-hand side.)This method takes ownership of the given loops and clears the given list. It then figures out the loop nesting hierarchy and assigns every loop a depth. Shells have even depths, and holes have odd depths. Note that the loops are reordered so the hierarchy can be traversed more easily (see
getParent(int),getLastDescendant(int), andS2Loop.depth()).This method may be called more than once, in which case any existing loops are deleted before being replaced by the input loops.
-
initOriented
public void initOriented(List<S2Loop> loops)
LikeinitNested(List), but expects loops to be oriented such that the polygon interior is on the left-hand side of all loops. This implies that shells and holes should have opposite orientations in the input to this method. (During initialization, loops representing holes will automatically be inverted.)
-
initWithNestedLoops
public void initWithNestedLoops(Map<S2Loop,List<S2Loop>> nestedLoops)
Initializes a polygon from a set ofS2Loops.Unlike
init(java.util.List<com.google.common.geometry.S2Loop>)this method assumes the caller already knows the nesting of loops within other loops. The passed-in map maps from parents to their immediate child loops, withnullmapping to the list of top-most shell loops. Immediate child loops must be completely spatially contained within their parent loop, but not contained in any other loop, except for ancestors of the parent. This method avoids the cost of determining nesting internally, but if the passed in nesting is wrong, future operations on the S2Polygon may be arbitrarily incorrect.Note that unlike
init(java.util.List<com.google.common.geometry.S2Loop>), the passed-in container of loops is not cleared; however, the passed-in loops become owned by the S2Polygon and should not be modified by the caller after calling this method.- Parameters:
nestedLoops- loops with nesting.
-
release
public void release(List<S2Loop> loops)
Appends the loops of this polygon to the given list and resets this polygon to be empty.
-
isValid
public static boolean isValid(List<S2Loop> loops)
Returns true if the given loops form a valid polygon, including checking whether the loops themselves are valid.
-
isValid
public boolean isValid()
Returns true if each loop on this polygon is valid, and if the relationships between all loops are valid.Specifically, this verifies that
S2Loop.isValid()is true for eachS2Loop, and thatisValid(List)is true for the whole list of loops.
-
findValidationError
public boolean findValidationError(S2Error error)
Returns true if this is *not* a valid polygon and setserrorappropriately. Otherwise, returns false and leaveserrorunchanged.
-
isEmpty
public boolean isEmpty()
-
isFull
public boolean isFull()
-
numLoops
public int numLoops()
-
loop
public S2Loop loop(int k)
Returns the loop at the given index. Note that during initialization, the given loops are reordered according to a preorder traversal of the loop nesting hierarchy. This implies that every loop is immediately followed by its descendants. This hierarchy can be traversed using the methodsgetParent(int),getLastDescendant(int), andS2Loop.depth().
-
getLoops
public List<S2Loop> getLoops()
Returns a view of the list ofS2Loops that make up this S2Polygon.
-
index
public S2ShapeIndex index()
Returns the index of this polygon.
-
getParent
public int getParent(int k)
Returns the index of the parent of loopk, or -1 if it has no parent.
-
getLastDescendant
public int getLastDescendant(int k)
Returns the index of the last loop that is contained within loopk. ReturnsnumLoops() - 1ifk < 0. Note that loops are indexed according to a preorder traversal of the nesting hierarchy, so the immediate children of loopkcan be found by iterating over loops(k+1)..getLastDescendant(k)and selecting those whose depth is equal to(loop(k).depth() + 1).
-
getAreaAndCentroid
public S2AreaCentroid getAreaAndCentroid()
Returns the area of the polygon interior, i.e. the region on the left side of an odd number of loops (the area is between 0 and 4*Pi) and the true centroid of the polygon, weighted by the area of the polygon (see s2.h for details on centroids). Note that the centroid might not be contained by the polygon.
-
getArea
public double getArea()
Returns the area of the polygon interior, i.e. the region on the left side of an odd number of loops. The return value is between 0 and 4*Pi.
-
getCentroid
public S2Point getCentroid()
Returns the true centroid of the polygon, weighted by the area of the polygon (see s2.h for details on centroids). Note that the centroid might not be contained by the polygon.
-
getSnapLevel
public int getSnapLevel()
If all of the polygon's vertices happen to be the centers of S2Cells at some level, then returns that level, otherwise returns -1. See alsoinitToSnapped(S2Polygon, int)andS2PolygonBuilder.Options.Builder.setSnapToCellCenters(boolean). Returns -1 if the polygon has no vertices.
-
getDistance
public S1Angle getDistance(S2Point p)
Returns the shortest distance from a point P to this polygon, given as the angle formed between P, the origin, and the nearest point on the polygon to P. This angle in radians is equivalent to the arclength along the unit sphere.If the point is contained inside the polygon, the distance returned is 0.
-
getOverlapFraction
public static double getOverlapFraction(S2Polygon a, S2Polygon b)
Returns the overlap fraction of polygon b on polygon a, i.e. the ratio of area of intersection to the area of polygon a.
-
project
public S2Point project(S2Point p)
Returns a point on the polygon that is closest to point P. The distance between these two points should be the result ofgetDistance(S2Point).If point P is contained within the loop, it is returned.
The polygon must not be empty.
-
contains
public boolean contains(S2Polygon b)
Returns true if this polygon contains the given other polygon, i.e., if polygon A contains all points contained by polygon B.
-
approxContains
public boolean approxContains(S2Polygon b, S1Angle vertexMergeRadius)
Returns true if this polygon (A) approximately contains the given other polygon (B). This is true if it is possible to move the vertices of B no further than "vertexMergeRadius" such that A contains the modified B.For example, the empty polygon will contain any polygon whose maximum width is no more than vertexMergeRadius.
-
intersects
public boolean intersects(S2Polygon b)
Returns true if this polygon intersects the given other polygon, i.e., if there is a point that is contained by both polygons.
-
getNumVertices
public int getNumVertices()
Returns the total number of vertices in all loops.
-
initToComplement
public void initToComplement(S2Polygon a)
Initializes this polygon to the complement of the given polygon.
-
initToSnapped
public void initToSnapped(S2Polygon a, int snapLevel)
Use S2PolygonBuilder to build this polygon by assembling the edges of a given polygon after snapping its vertices to the center of leaf cells. This will simplify the polygon with a tolerance ofS2Projections.maxDiag.getValue(S2CellId.MAX_LEVEL), or approximately 0.13 microdegrees, or 1.5cm on the surface of the Earth. Such a polygon can be efficiently compressed when serialized. The snap level can be changed to a non-leaf level if needed.
-
initToIntersection
public void initToIntersection(S2Polygon a, S2Polygon b)
Initializes this polygon to the intersection, union, or difference (A - B) of the given two polygons. ThevertexMergeRadiusdetermines 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 (seeS2PolygonBuilderfor 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.PolygonProtoformat, thenS1Angle.e7(1)is a good value forvertexMergeRadius.
-
initToIntersectionSloppy
public void initToIntersectionSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)
-
initToUnionSloppy
public void initToUnionSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)
-
initToDifferenceSloppy
public void initToDifferenceSloppy(S2Polygon a, S2Polygon b, S1Angle vertexMergeRadius)
-
initToSimplified
public void initToSimplified(S2Polygon a, S1Angle tolerance, boolean snapToCellCenters)
Initializes this polygon to a polygon that contains fewer vertices and is within tolerance of the polygon a, with some caveats.If
snapToCellCentersis true, the vertices of this polygon will be snapped to the centers of cells at the smallest level that is guaranteed to result in a valid polygon given the specified tolerance.- If there is a very small island in the original polygon, it may disappear completely. Thus some parts of the original polygon may not be close to the simplified polygon. Those parts are small, though, and arguably don't need to be kept.
- However, if there are dense islands, they may all disappear, instead of replacing them by a big simplified island.
- For small tolerances (compared to the polygon size), it may happen that the simplified polygon has more vertices than the original, if the first step of the simplification creates too many self-intersections. One can construct unrealistic cases where that happens to an extreme degree.
-
initToSimplifiedInCell
public void initToSimplifiedInCell(S2Polygon a, S2Cell cell, S1Angle tolerance)
Initializes this polygon to a polygon that contains fewer vertices and is within tolerance of the polygon a, while ensuring that the vertices on the cell boundary are preserved.Precondition: Polygon a is contained in the cell.
-
breakEdgesAndAddToBuilder
public static void breakEdgesAndAddToBuilder(S2ShapeIndex index, S2PolygonBuilder builder)
Takes a set of possibly intersecting edges, stored in the S2ShapeIndex, and breaks the edges into small pieces so that there is no intersection anymore, and adds all these edges to the builder.
-
union
public static S2Polygon union(Iterable<S2Polygon> polygons)
Returns a polygon that is the union of the given polygons.
-
unionSloppy
public static S2Polygon unionSloppy(Iterable<S2Polygon> polygons, S1Angle vertexMergeRadius)
Returns a polygon that is the union of the given polygons; combines vertices that form edges that are almost identical, as defined byvertexMergeRadius.
-
intersectWithPolyline
public List<S2Polyline> intersectWithPolyline(S2Polyline in)
Intersects this polygon with theS2Polylineinand returns the resulting zero or more polylines. The polylines are ordered in the order they would be encountered by traversinginfrom beginning to end. Note that the output may include polylines with only one vertex, but there will not be any zero-vertex polylines.This is equivalent to calling
intersectWithPolylineSloppy(com.google.common.geometry.S2Polyline, com.google.common.geometry.S1Angle)with thevertexMergeRadiusset toS2EdgeUtil.DEFAULT_INTERSECTION_TOLERANCE.
-
intersectWithPolylineSloppy
public List<S2Polyline> intersectWithPolylineSloppy(S2Polyline in, S1Angle vertexMergeRadius)
Similar tointersectWithPolyline(com.google.common.geometry.S2Polyline), except that vertices will be dropped as necessary to ensure that all adjacent vertices in the sequence obtained by concatenating the output polylines will be farther thanvertexMergeRadiusapart. Note that this can change the number of output polylines and/or yield single-vertex polylines.
-
subtractFromPolyline
public List<S2Polyline> subtractFromPolyline(S2Polyline in)
Same asintersectWithPolyline(com.google.common.geometry.S2Polyline), but subtracts this polygon from the given polyline.
-
subtractFromPolylineSloppy
public List<S2Polyline> subtractFromPolylineSloppy(S2Polyline in, S1Angle vertexMergeRadius)
Same asintersectWithPolylineSloppy(com.google.common.geometry.S2Polyline, com.google.common.geometry.S1Angle), but subtracts this polygon from the given polyline.
-
isNormalized
public boolean isNormalized()
Return true if every loop of this polygon shares at most one vertex with its parent loop. Every polygon has a unique normalized form. A polygon can be normalized by passing it through S2Builder (with no snapping) in order to reconstruct the polygon from its edges.Generally there is no reason to convert polygons to normalized form. It is mainly useful for testing in order to compare whether two polygons have exactly the same interior, even when they have a different loop structure. For example, a diamond nested within a square (touching at four points) could be represented as a square with a diamond-shaped hole, or as four triangles. Methods such as
boundaryApproxEquals(S2Polygon, double)will report these polygons as being different (because they have different boundaries) even though they contain the same points. However if they are both converted to normalized form (the "four triangles" version) then they can be compared more easily.
-
getCapBound
public S2Cap getCapBound()
Returns a spherical cap that bounds this loop. It may be expanded slightly such that if the loop contains a point P, then the bound contains P also.- Specified by:
getCapBoundin interfaceS2Region
-
getRectBound
public S2LatLngRect getRectBound()
Returns a fairly tight bounding latitude-longitude rectangle. It is not guaranteed to be as tight as possible, to ensure that if the loop contains a point P, then the bound contains P also.- Specified by:
getRectBoundin interfaceS2Region
-
contains
public boolean contains(S2Cell cell)
If this method returns true, the region completely contains the given cell. Otherwise, either the region does not contain the cell or the containment relationship could not be determined.
-
mayIntersect
public boolean mayIntersect(S2Cell target)
If this method returns false, the region does not intersect the given cell. Otherwise, either region intersects the cell, or the intersection relationship could not be determined.- Specified by:
mayIntersectin interfaceS2Region
-
contains
public boolean contains(S2Point p)
The pointpdoes not need to be normalized.
-
toString
public String toString()
Returns a human readable representation of the polygon.
-
encode
public void encode(OutputStream output) throws IOException
Encodes the polygon into an efficient, lossless binary representation, which can be decoded by callingdecode(java.io.InputStream). The encoding is byte-compatible with the C++ version of the S2 library.- Parameters:
output- The output stream into which the encoding should be written.- Throws:
IOException- if there was a problem writing into the output stream.
-
decode
public static S2Polygon decode(InputStream input) throws IOException
Decodes a polygon that was encoded usingencode(java.io.OutputStream).This method will never return null. It will either throw an exception or return a valid
S2Polygon.- Parameters:
input- The input stream containing the encoded polygon data.- Returns:
- the decoded
S2Polygon. - Throws:
IOException- if there was a problem reading from the input stream.
-
shape
public S2Polygon.Shape shape()
Returns a shape wrapping this polygon.
-
-