# pygplates.PolygonOnSphere

class pygplates.PolygonOnSphere

Represents a polygon on the surface of the unit length sphere. Polygons are equality (`==`, `!=`) comparable (but not hashable - cannot be used as a key in a `dict`). See `PointOnSphere` for an overview of equality in the presence of limited floating-point precision.

A polygon instance is both:

Note

This includes points and segments from the exterior ring followed by all interior rings (if any).

In addition a polygon instance is directly iterable over its points (without having to use `get_points`):

```polygon = pygplates.PolygonOnSphere(points)
for point in polygon:
...
```

…and so the following operations for accessing the points are supported:

Operation

Result

`len(polygon)`

number of vertices in polygon (in exterior ring and interior rings)

`for p in polygon`

iterates over the vertices p of polygon

`p in polygon`

`True` if p is equal to a vertex of polygon

`p not in polygon`

`False` if p is equal to a vertex of polygon

`polygon[i]`

the vertex of polygon at index i

`polygon[i:j]`

slice of polygon from i to j

`polygon[i:j:k]`

slice of polygon from i to j with step k

Note

`if p in polygon` does not test whether a point `p` is inside the the interior area of a polygon - use `is_point_in_polygon()` for that instead.

Since a PolygonOnSphere is immutable it contains no operations or methods that modify its state (such as adding or removing points). This is similar to other immutable types in python such as `str`.
So instead of modifying an existing polygon you will need to create a new `PolygonOnSphere` instance. The following example demonstrates modifying points in the exterior ring:
```# Get a list of exterior ring points from an existing 'polygon'.
exterior_ring = list(polygon.get_exterior_ring_points())

# Modify the exterior ring points list somehow.
exterior_ring[0] = pygplates.PointOnSphere(...)
exterior_ring.append(pygplates.PointOnSphere(...))

# Extract the interior rings (we'll just pass these through unmodified).
interior_rings = [list(polygon.get_interior_ring_points(interior_ring_index))
for interior_ring_index in range(polygon.get_number_of_interior_rings())]

# 'polygon' now references a new PolygonOnSphere instance with a modified exterior ring.
polygon = pygplates.PolygonOnSphere(exterior_ring, interior_rings)
```

The following example demonstrates creating a `PolygonOnSphere` from a `PolylineOnSphere`:

```polygon = pygplates.PolygonOnSphere(polyline)
```

Note

A polygon closes the loop between the last and first points in its exterior ring (created from the polyline) so there’s no need to make the first and last points equal.

Changed in version 0.36: `get_points` and `get_segments()` now include points and segments from interior rings (as do the operations listed in the table above).

__init__(...)

A PolygonOnSphere object can be constructed in more than one way…

__init__(exterior_ring, [interior_rings])

Create a polygon from an exterior ring and optional interior rings, where each ring is a sequence of (x,y,z) or (latitude,longitude) points.

param exterior_ring

Exterior ring sequence of (x,y,z) points, or (latitude,longitude) points (in degrees).

type exterior_ring

Any sequence of `PointOnSphere` or `LatLonPoint` or tuple (float,float,float) or tuple (float,float).

param interior_rings

Optional sequence of interior rings where each ring is a sequence of (x,y,z) points, or (latitude,longitude) points (in degrees).

type interior_rings

Any sequence of rings (where ring is any sequence of `PointOnSphere` or `LatLonPoint` or tuple (float,float,float) or tuple (float,float)), or None.

raises

InvalidLatLonError if any latitude or longitude is invalid

raises

ViolatedUnitVectorInvariantError if any (x,y,z) is not unit magnitude

raises

InvalidPointsForPolygonConstructionError if any ring has less than three points or if any two points (adjacent in a ring) are antipodal to each other (on opposite sides of the globe)

Note

Each ring must contain at least three points in order for the polygon to be valid, otherwise InvalidPointsForPolygonConstructionError will be raised.

During creation, a `GreatCircleArc` is created between each adjacent pair of of points in a ring - see `get_segments()`. The last arc in each ring is created between the last and first points in that ring to close the loop of the ring. For this reason you do not need to ensure that the first and last points in a ring have the same position (although it’s not an error if this is the case because the final arc in the ring will then just have a zero length).

It is not an error for adjacent points in a ring to be coincident. In this case each `GreatCircleArc` between two such adjacent points will have zero length (`GreatCircleArc.is_zero_length()` will return `True`) and will have no rotation axis (`GreatCircleArc.get_rotation_axis()` will raise an error).

The following example shows a few different ways to create a `polygon`:

```# Polygon with only an exterior ring.
points = []
points.append(pygplates.PointOnSphere(...))
points.append(pygplates.PointOnSphere(...))
points.append(pygplates.PointOnSphere(...))
polygon = pygplates.PolygonOnSphere(points)

# Polygon with only an exterior ring.
points = []
points.append((lat1,lon1))
points.append((lat2,lon2))
points.append((lat3,lon3))
polygon = pygplates.PolygonOnSphere(points)

# Polygon with only an exterior ring.
points = []
points.append([x1,y1,z1])
points.append([x2,y2,z2])
points.append([x3,y3,z3])
polygon = pygplates.PolygonOnSphere(points)

# Polygon with an exterior ring and interior rings.
exterior_ring = []
exterior_ring.append(pygplates.PointOnSphere(...))
...
interior_rings = []
first_interior_ring = []
first_interior_ring.append(pygplates.PointOnSphere(...))
...
interior_rings.append(first_interior_ring)
second_interior_ring = []
second_interior_ring.append(pygplates.PointOnSphere(...))
...
interior_rings.append(second_interior_ring)
polygon = pygplates.PolygonOnSphere(exterior_ring, interior_rings)
```

If you have latitude/longitude values but they are not a sequence of tuples or if the latitude/longitude order is swapped then the following examples demonstrate how you could restructure them:

```# Flat lat/lon array.
points = numpy.array([lat1, lon1, lat2, lon2, lat3, lon3])
polygon = pygplates.PolygonOnSphere(zip(points[::2],points[1::2]))

# Flat lon/lat list (ie, different latitude/longitude order).
points = [lon1, lat1, lon2, lat2, lon3, lat3]
polygon = pygplates.PolygonOnSphere(zip(points[1::2],points[::2]))

# Separate lat/lon arrays.
lats = numpy.array([lat1, lat2, lat3])
lons = numpy.array([lon1, lon2, lon3])
polygon = pygplates.PolygonOnSphere(zip(lats,lons))

# Lon/lat list of tuples (ie, different latitude/longitude order).
points = [(lon1, lat1), (lon2, lat2), (lon3, lat3)]
polygon = pygplates.PolygonOnSphere([(lat,lon) for lon, lat in points])
```

Changed in version 0.36: Can now optionally specify interior rings (in addition to the exterior ring).

__init__(geometry, [allow_one_or_two_points=True])

Create a polygon from a `GeometryOnSphere`.

param geometry

The point, multi-point, polyline or polygon geometry to convert from.

type geometry

`GeometryOnSphere`

param allow_one_or_two_points

Whether geometry is allowed to be a `PointOnSphere` or a `MultiPointOnSphere` containing only one or two points - if allowed then one of those points is duplicated since a PolygonOnSphere requires at least three points - default is `True`.

type allow_one_or_two_points

bool

raises

InvalidPointsForPolygonConstructionError if geometry is a `PointOnSphere`, or a `MultiPointOnSphere` with one or two points (and allow_one_or_two_points is `False`), or if any two consecutive points in a `MultiPointOnSphere` are antipodal to each other (on opposite sides of the globe)

If allow_one_or_two_points is `True` then geometry can be `PointOnSphere`, `MultiPointOnSphere`, `PolylineOnSphere` or `PolygonOnSphere`. However if allow_one_or_two_points is `False` then geometry must be a `PolygonOnSphere`, or a `MultiPointOnSphere` or `PolylineOnSphere` containing at least three points to avoid raising InvalidPointsForPolygonConstructionError.

During creation, a `GreatCircleArc` is created between each adjacent pair of geometry points - see `get_segments()`.

It is not an error for adjacent points in a geometry sequence to be coincident. In this case each `GreatCircleArc` between two such adjacent points will have zero length (`GreatCircleArc.is_zero_length()` will return `True`) and will have no rotation axis (`GreatCircleArc.get_rotation_axis()` will raise an error). However if two such adjacent points are antipodal (on opposite sides of the globe) then InvalidPointsForPolygonConstructionError will be raised

To create a PolygonOnSphere from any geometry type:

```polygon = pygplates.PolygonOnSphere(geometry)
```

To create a PolygonOnSphere from any geometry containing at least three points:

```try:
polygon = pygplates.PolygonOnSphere(geometry, allow_one_or_two_points=False)
except pygplates.InvalidPointsForPolygonConstructionError:
... # Handle failure to convert 'geometry' to a PolygonOnSphere.
```

Note

The created polygon will have no interior rings unless geometry is also a `PolygonOnSphere` and has interior rings.

Methods

 `__init__`(...) A PolygonOnSphere object can be constructed in more than one way... `clone`() Create a duplicate of this geometry (derived) instance. `distance`(geometry1, geometry2, ...) [staticmethod] Returns the (minimum) distance between two geometries (in radians). Returns the total arc length of this polygon (in radians) including the exterior ring and all interiors rings (if any). Returns the area of this polygon (on a sphere of unit radius). Returns the boundary centroid of this polygon. Returns the centroid of this polygon (equivalent to calling `get_interior_centroid()`). Returns a read-only sequence of `points` in the exterior ring. Returns a read-only sequence of `segments` in the exterior ring. Returns the interior centroid of this polygon. `get_interior_ring_points`(interior_ring_index) Returns a read-only sequence of `points` in the interior ring at the specified interior ring index. `get_interior_ring_segments`(interior_ring_index) Returns a read-only sequence of `segments` in the interior ring at the specified interior ring index. Returns the number of interior rings. Returns whether this polygon is clockwise or counter-clockwise. `get_points`() Returns a read-only sequence of `points` in this geometry. Returns a read-only sequence of all `segments` in this polygon (exterior ring followed by interior rings if any). Returns the signed area of this polygon (on a sphere of unit radius). Determines whether the specified point lies within the interior of this polygon. `partition`(geometry, ...) Partition a geometry into optional inside/outside lists of partitioned geometry pieces. `to_lat_lon_array`() Returns the sequence of points, in this geometry, as a numpy array of (latitude,longitude) pairs (in degrees). `to_lat_lon_list`() Returns the sequence of points, in this geometry, as (latitude,longitude) tuples (in degrees). `to_lat_lon_point_list`() Returns the sequence of points, in this geometry, as `lat lon points`. `to_tessellated`(tessellate_radians) Returns a new polygon that is tessellated version of this polygon. `to_xyz_array`() Returns the sequence of points, in this geometry, as a numpy array of (x,y,z) triplets. `to_xyz_list`() Returns the sequence of points, in this geometry, as (x,y,z) cartesian coordinate tuples.
class Orientation

Bases: `Boost.Python.enum`

class PartitionResult

Bases: `Boost.Python.enum`

get_arc_length()

Returns the total arc length of this polygon (in radians) including the exterior ring and all interiors rings (if any).

Return type

float

This is the sum of the arc lengths of the exterior ring and all interior rings (if any).
To convert to distance, multiply the result by the Earth radius (see `Earth`).
get_area()

Returns the area of this polygon (on a sphere of unit radius).

Return type

float

The area is essentially the absolute value of the `signed area`.

To convert to area on the Earth’s surface, multiply the result by the Earth radius squared (see `Earth`).

Note

The interior rings reduce the absolute area of the exterior ring (regardless of their orientation) because they are holes in the polygon.

get_boundary_centroid()

Returns the boundary centroid of this polygon.

Return type

`PointOnSphere`

The boundary centroid is calculated as a weighted average of the mid-points of the `great circle arcs` of the exterior ring of this polygon with weighting proportional to the individual arc lengths. The interior rings are ignored.

Note that if you want a centroid closer to the centre-of-mass of the polygon interior then use `get_interior_centroid()` instead.

get_centroid()

Returns the centroid of this polygon (equivalent to calling `get_interior_centroid()`).

Return type

`PointOnSphere`

New in version 0.36.

get_exterior_ring_points()

Returns a read-only sequence of `points` in the exterior ring.

Return type

a read-only sequence of `PointOnSphere`

The following operations for accessing the points in the returned read-only sequence are supported:

Operation

Result

`len(seq)`

number of points in the exterior ring

`for p in seq`

iterates over the points p in the exterior ring

`p in seq`

`True` if p is an exterior ring point

`p not in seq`

`False` if p is an exterior ring point

`seq[i]`

the exterior ring point at index i

`seq[i:j]`

slice of exterior ring points from i to j

`seq[i:j:k]`

slice of exterior ring points from i to j with step k

The following example demonstrates some uses of the above operations:

```polygon = pygplates.PolygonOnSphere(exterior_ring)
...
exterior_ring_points = polygon.get_exterior_ring_points()
for point in exterior_ring_points:
print(point)
```

Note

The returned sequence is read-only and cannot be modified.

New in version 0.36.

get_exterior_ring_segments()

Returns a read-only sequence of `segments` in the exterior ring.

Return type

a read-only sequence of `GreatCircleArc`

The following operations for accessing the great circle arcs in the returned read-only sequence are supported:

Operation

Result

`len(seq)`

number of segments in the exterior ring

`for s in seq`

iterates over the segments s in the exterior ring

`s in seq`

`True` if s is an exterior ring segment

`s not in seq`

`False` if s is an exterior ring segment

`seq[i]`

the exterior ring segment at index i

`seq[i:j]`

slice of exterior ring segments from i to j

`seq[i:j:k]`

slice of exterior ring segments from i to j with step k

Note

Between each adjacent pair of `points` in the exterior ring there is a `segment` such that the number of exterior ring points equals the number of exterior ring segments.

The following example demonstrates some uses of the above operations:

```polygon = pygplates.PolygonOnSphere(exterior_ring)
...
exterior_ring_segments = polygon.get_exterior_ring_segments()
for segment in exterior_ring_segments:
if not segment.is_zero_length():
segment_midpoint_direction = segment.get_arc_direction(0.5)
```

Note

The `end point` of the last segment in the exterior ring is equal to the `start point` of the first segment in the exterior ring.

Note

The returned sequence is read-only and cannot be modified.

New in version 0.36.

get_interior_centroid()

Returns the interior centroid of this polygon.

Return type

`PointOnSphere`

The interior centroid is calculated as a weighted average of the centroids of spherical triangles formed by all `great circle arcs` of this polygon with weighting proportional to the signed area of each individual spherical triangle. The interior rings change the spherical area weighting because they are holes in the polygon and essentially cut out the internal area of the exterior ring.

This centroid is useful when the centre-of-mass of the polygon interior is desired. For example, the interior centroid of a bottom-heavy, pear-shaped polygon will be closer to the bottom of the polygon. This centroid is not exactly at the centre-of-mass, but it will be a lot closer to the real centre-of-mass than `get_boundary_centroid()`.

get_interior_ring_points(interior_ring_index)

Returns a read-only sequence of `points` in the interior ring at the specified interior ring index.

Parameters

interior_ring_index (int) – Index of interior ring (must be less than `get_number_of_interior_rings()`).

Return type

a read-only sequence of `PointOnSphere`

The following operations for accessing the points in the returned read-only sequence are supported:

Operation

Result

`len(seq)`

number of points in the interior ring

`for p in seq`

iterates over the points p in the interior ring

`p in seq`

`True` if p is a point in the interior ring

`p not in seq`

`False` if p is a point in the interior ring

`seq[i]`

the interior ring’s point at index i

`seq[i:j]`

slice of interior ring’s points from i to j

`seq[i:j:k]`

slice of interior ring’s points from i to j with step k

The following example demonstrates some uses of the above operations:

```polygon = pygplates.PolygonOnSphere(exterior_ring, [interior_ring_0, interior_ring_1])
...
for interior_ring_index in range(polygon.get_number_of_interior_rings()):
interior_ring_points = polygon.get_interior_ring_points(interior_ring_index)
for point in interior_ring_points:
print(point)
```

Note

The returned sequence is read-only and cannot be modified.

New in version 0.36.

get_interior_ring_segments(interior_ring_index)

Returns a read-only sequence of `segments` in the interior ring at the specified interior ring index.

Parameters

interior_ring_index (int) – Index of interior ring (must be less than `get_number_of_interior_rings()`).

Return type

a read-only sequence of `GreatCircleArc`

The following operations for accessing the great circle arcs in the returned read-only sequence are supported:

Operation

Result

`len(seq)`

number of segments in the interior ring

`for s in seq`

iterates over the segments s in the interior ring

`s in seq`

`True` if s is a segment in the interior ring

`s not in seq`

`False` if s is a segment in the interior ring

`seq[i]`

the interior ring’s segment at index i

`seq[i:j]`

slice of the interior ring’s segments from i to j

`seq[i:j:k]`

slice of the interior ring’s segments from i to j with step k

Note

Between each adjacent pair of `points` in the interior ring there is a `segment` such that the number of points in the interior ring equals its number of segments.

The following example demonstrates some uses of the above operations:

```polygon = pygplates.PolygonOnSphere(exterior_ring, [interior_ring_0, interior_ring_1])
...
for interior_ring_index in range(polygon.get_number_of_interior_rings()):
interior_ring_segments = polygon.get_interior_ring_segments(interior_ring_index)
for segment in interior_ring_segments:
if not segment.is_zero_length():
segment_midpoint_direction = segment.get_arc_direction(0.5)
```

Note

The `end point` of the last segment in an interior ring is equal to the `start point` of the first segment in that interior ring.

Note

The returned sequence is read-only and cannot be modified.

New in version 0.36.

get_number_of_interior_rings()

Returns the number of interior rings.

Return type

int

If there are no interior rings then `0` is returned.

New in version 0.36.

get_orientation()

Returns whether this polygon is clockwise or counter-clockwise.

Return type

PolygonOnSphere.Orientation

If this polygon is clockwise (when viewed from above the surface of the sphere) then PolygonOnSphere.Orientation.clockwise is returned, otherwise PolygonOnSphere.Orientation.counter_clockwise is returned.

```if polygon.get_orientation() == pygplates.PolygonOnSphere.Orientation.clockwise:
print 'Orientation is clockwise'
else:
print 'Orientation is counter-clockwise'
```

Note

The orientation is determined by the sign of the `signed area` (with an optimization to make it more efficient in most cases).

get_segments()

Returns a read-only sequence of all `segments` in this polygon (exterior ring followed by interior rings if any).

Return type

a read-only sequence of `GreatCircleArc`

The following operations for accessing the great circle arcs in the returned read-only sequence are supported:

Operation

Result

`len(seq)`

number of segments of the polygon (in exterior ring and interior rings)

`for s in seq`

iterates over the segments s of the polygon

`s in seq`

`True` if s is an segment of the polygon

`s not in seq`

`False` if s is an segment of the polygon

`seq[i]`

the segment of the polygon at index i

`seq[i:j]`

slice of segments of the polygon from i to j

`seq[i:j:k]`

slice of segments of the polygon from i to j with step k

Note

Between each adjacent pair of `points` in each ring there is a `segment` such that the total number of points equals the total number of segments.

The following example demonstrates some uses of the above operations:

```polygon = pygplates.PolygonOnSphere(exterior_ring, interior_rings)
...
segments = polygon.get_segments()
for segment in segments:
if not segment.is_zero_length():
segment_midpoint_direction = segment.get_arc_direction(0.5)
```

Note

The `end point` of the last segment in a ring is equal to the `start point` of the first segment in that ring.

Note

The returned sequence is read-only and cannot be modified.

Changed in version 0.36: The returned segments now include interior rings (if any).

get_signed_area()

Returns the signed area of this polygon (on a sphere of unit radius).

Return type

float

If this polygon is clockwise (when viewed from above the surface of the sphere) then the returned area will be negative, otherwise it will be positive. However if you only want to determine the orientation of this polygon then `get_orientation()` is more efficient than comparing the sign of the area.

To convert to signed area on the Earth’s surface, multiply the result by the Earth radius squared (see `Earth`).

Note

The interior rings reduce the absolute area of the exterior ring (regardless of their orientation) because they are holes in the polygon. So if the signed area of exterior ring is positive then any interior rings will reduce that, and if it’s negative then any interior rings will make it less negative.

is_point_in_polygon(point)

Determines whether the specified point lies within the interior of this polygon.

Parameters

point (`PointOnSphere` or `LatLonPoint` or (latitude,longitude), in degrees, or (x,y,z)) – the point to be tested

Return type

bool

Test if a (latitude, longitude) point is inside a polygon:

```if polygon.is_point_in_polygon((latitude, longitude)):
...
```

Note

If a polygon has one or more non-intersecting interior rings contained fully within its exterior ring and the test point is inside any interior ring then the test point is considered to be outside the polygon. This is because the interior rings subtract area from the exterior ring.

partition(geometry[, partitioned_geometries_inside][, partitioned_geometries_outside])

Partition a geometry into optional inside/outside lists of partitioned geometry pieces.

Parameters
• geometry (`GeometryOnSphere`) – the geometry to be partitioned

• partitioned_geometries_inside (`list` of `GeometryOnSphere`, or None) – optional list of geometries partitioned inside this polygon (note that the list is not cleared first)

• partitioned_geometries_outside (`list` of `GeometryOnSphere`, or None) – optional list of geometries partitioned outside this polygon (note that the list is not cleared first)

Return type

PolygonOnSphere.PartitionResult

The returned result is:

• PolygonOnSphere.PartitionResult.inside: if geometry is entirely inside this polygon, or

• PolygonOnSphere.PartitionResult.outside: if geometry is entirely outside this polygon, or

• PolygonOnSphere.PartitionResult.intersecting: if geometry intersects this polygon.

If partitioned_geometries_inside is specified then it must be a `list` and any part of geometry inside this polygon is added to it. So if PolygonOnSphere.PartitionResult.inside is returned this means geometry is added and if PolygonOnSphere.PartitionResult.intersecting is returned this means the partitioned parts of geometry inside this polygon are added.

If partitioned_geometries_outside is specified then if must be a `list` and any part of geometry outside this polygon is added to it. So if PolygonOnSphere.PartitionResult.outside is returned this means geometry is added and if PolygonOnSphere.PartitionResult.intersecting is returned this means the partitioned parts of geometry outside this polygon are added.

Note

Partitioning `point` geometries returns only PolygonOnSphere.PartitionResult.inside or PolygonOnSphere.PartitionResult.outside.

If a partitioned `multi-point` contains points both inside and outside this polygon then PolygonOnSphere.PartitionResult.intersecting is returned. In this case the points inside are added as a single `MultiPointOnSphere` to partitioned_geometries_inside (if specified) and the points outside are added as a single `MultiPointOnSphere` to partitioned_geometries_outside (if specified).

Warning

Support for partitioning a `polygon` geometry is partial.
If a polygon geometry is entirely inside or entirely outside this polygon then it will get added as a polygon as expected (to partitioned_geometries_inside or partitioned_geometries_outside respectively if specified).
But if a polygon geometry intersects this polygon, then partitioned polylines (not polygons) are added (to the optional inside/outside lists).
This is also how it is in the Assign Plate IDs dialog in GPlates.
In a future release this will be fixed to always return polygons.

Test if a polyline is entirely inside a polygon:

```if polygon.partition(polyline) == pygplates.PolygonOnSphere.PartitionResult.inside:
...
```

Find the bits of a polyline that are outside a group of continental polygons:

```# Start with the original polyline to partition.
oceanic_polylines = [polyline]

for continental_polygon in continental_polygons:
# Iterate over the polylines that are outside the continental polygons processed so far.
current_oceanic_polylines = oceanic_polylines
# The new list of polylines will also be outside the current continental polygon.
oceanic_polylines = []
for current_oceanic_polyline in current_oceanic_polylines:
continental_polygon.partition(current_oceanic_polyline, partitioned_geometries_outside=oceanic_polylines)

# The final result is in 'oceanic_polylines'.
```

Returns a new polygon that is tessellated version of this polygon.

Parameters

Return type

`PolygonOnSphere`

Adjacent points (in the returned tessellated polygon) are separated by no more than tessellate_radians on the globe.

Create a polygon tessellated to 2 degrees:

```tessellated_polygon = polygon.to_tessellated(math.radians(2))
```

Note

Since a PolygonOnSphere is immutable it cannot be modified. Which is why a new (tessellated) PolygonOnSphere is returned.

Note

The distance between adjacent points (in each tessellated ring) will not be exactly uniform. This is because each `segment` in the original polygon is tessellated to the nearest integer number of points (that keeps that segment under the threshold) and hence each original segment will have a slightly different tessellation angle.