"""
Spatial extents classes for map layer and space time datasets
Usage:
.. code-block:: python
>>> import grass.temporal as tgis
>>> tgis.init()
>>> extent = tgis.RasterSpatialExtent(
... ident="raster@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent = tgis.Raster3DSpatialExtent(
... ident="raster3d@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent = tgis.VectorSpatialExtent(
... ident="vector@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent = tgis.STRDSSpatialExtent(
... ident="strds@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent = tgis.STR3DSSpatialExtent(
... ident="str3ds@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent = tgis.STVDSSpatialExtent(
... ident="stvds@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
(C) 2012-2013 by the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.
:authors: Soeren Gebbert
"""
from typing import Literal
from .base import SQLDatabaseInterface
[docs]class SpatialExtent(SQLDatabaseInterface):
"""This is the spatial extent base class for all maps and space time datasets
This class implements a three dimensional axis aligned bounding box
and functions to compute topological relationships
Usage:
.. code-block:: python
>>> init()
>>> extent = SpatialExtent(
... table="raster_spatial_extent",
... ident="soil@PERMANENT",
... north=90,
... south=90,
... east=180,
... west=180,
... top=100,
... bottom=-20,
... )
>>> extent.id
'soil@PERMANENT'
>>> extent.north
90.0
>>> extent.south
90.0
>>> extent.east
180.0
>>> extent.west
180.0
>>> extent.top
100.0
>>> extent.bottom
-20.0
>>> extent.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 90.0
| South:...................... 90.0
| East:.. .................... 180.0
| West:....................... 180.0
| Top:........................ 100.0
| Bottom:..................... -20.0
>>> extent.print_shell_info()
north=90.0
south=90.0
east=180.0
west=180.0
top=100.0
bottom=-20.0
"""
def __init__(
self,
table=None,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
proj="XY",
) -> None:
SQLDatabaseInterface.__init__(self, table, ident)
self.set_id(ident)
self.set_spatial_extent_from_values(north, south, east, west, top, bottom)
self.set_projection(proj)
[docs] def overlapping_2d(self, extent) -> bool:
"""Return True if this (A) and the provided spatial extent (B) overlaps
in two dimensional space.
Code is lend from wind_overlap.c in lib/gis
Overlapping includes the spatial relations:
- contain
- in
- cover
- covered
- equivalent
.. code-block:: python
>>> A = SpatialExtent(north=80, south=20, east=60, west=10)
>>> B = SpatialExtent(north=80, south=20, east=60, west=10)
>>> A.overlapping_2d(B)
True
:param extent: The spatial extent to check overlapping with
:return: True or False
"""
if self.get_projection() != extent.get_projection():
self.msgr.error(
_(
"Projections are different. Unable to compute "
"overlapping_2d for spatial extents"
)
)
return False
N = extent.get_north()
S = extent.get_south()
E = extent.get_east()
W = extent.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while self.get_west() > E:
E += 360.0
W += 360.0
while self.get_east() < W:
E -= 360.0
W -= 360.0
return not (
self.get_north() <= S
or self.get_south() >= N
or self.get_east() <= W
or self.get_west() >= E
)
[docs] def overlapping(self, extent) -> bool:
"""Return True if this (A) and the provided spatial
extent (B) overlaps in three dimensional space.
Overlapping includes the spatial relations:
- contain
- in
- cover
- covered
- equivalent
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.overlapping(B)
True
:param extent: The spatial extent to check overlapping with
:return: True or False
"""
if not self.overlapping_2d(extent):
return False
T = extent.get_top()
B = extent.get_bottom()
return not (self.get_top() <= B or self.get_bottom() >= T)
[docs] def intersect_2d(self, extent):
"""Return the two dimensional intersection as spatial_extent
object or None in case no intersection was found.
:param extent: The spatial extent to intersect with
:return: The intersection spatial extent
"""
if not self.overlapping_2d(extent):
return None
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
# Compute the extent
nN = N
nS = S
nE = E
nW = W
if eW > W:
nW = eW
if eE < E:
nE = eE
if eN < N:
nN = eN
if eS > S:
nS = eS
return SpatialExtent(
north=nN,
south=nS,
east=nE,
west=nW,
top=0,
bottom=0,
proj=self.get_projection(),
)
[docs] def intersect(self, extent):
"""Return the three dimensional intersection as spatial_extent
object or None in case no intersection was found.
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> C = A.intersect(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=10, bottom=-50, top=50
... )
>>> C = A.intersect(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 40.0
| South:...................... 30.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-50, top=50
... )
>>> C = A.intersect(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 40.0
| South:...................... 30.0
| East:.. .................... 60.0
| West:....................... 30.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-30, top=50
... )
>>> C = A.intersect(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 40.0
| South:...................... 30.0
| East:.. .................... 60.0
| West:....................... 30.0
| Top:........................ 50.0
| Bottom:..................... -30.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-30, top=30
... )
>>> C = A.intersect(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 40.0
| South:...................... 30.0
| East:.. .................... 60.0
| West:....................... 30.0
| Top:........................ 30.0
| Bottom:..................... -30.0
:param extent: The spatial extent to intersect with
:return: The intersection spatial extent
""" # noqa: E501
if not self.overlapping(extent):
return None
new = self.intersect_2d(extent)
eT = extent.get_top()
eB = extent.get_bottom()
T = self.get_top()
B = self.get_bottom()
nT = T
nB = B
if eB > B:
nB = eB
if eT < T:
nT = eT
new.set_top(nT)
new.set_bottom(nB)
return new
[docs] def union_2d(self, extent):
"""Return the two dimensional union as spatial_extent
object or None in case the extents does not overlap or meet.
:param extent: The spatial extent to create a union with
:return: The union spatial extent
"""
if not self.overlapping_2d(extent) and not self.meet_2d(extent):
return None
return self.disjoint_union_2d(extent)
[docs] def disjoint_union_2d(self, extent):
"""Return the two dimensional union as spatial_extent.
:param extent: The spatial extent to create a union with
:return: The union spatial extent
"""
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
# Compute the extent
nN = N
nS = S
nE = E
nW = W
if eW < W:
nW = eW
if eE > E:
nE = eE
if eN > N:
nN = eN
if eS < S:
nS = eS
return SpatialExtent(
north=nN,
south=nS,
east=nE,
west=nW,
top=0,
bottom=0,
proj=self.get_projection(),
)
[docs] def union(self, extent):
"""Return the three dimensional union as spatial_extent
object or None in case the extents does not overlap or meet.
:param extent: The spatial extent to create a union with
:return: The union spatial extent
"""
if not self.overlapping(extent) and not self.meet(extent):
return None
return self.disjoint_union(extent)
[docs] def disjoint_union(self, extent):
"""Return the three dimensional union as spatial_extent .
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=10, bottom=-50, top=50
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-50, top=50
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-30, top=50
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> B = SpatialExtent(
... north=40, south=30, east=60, west=30, bottom=-30, top=30
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 80.0
| South:...................... 20.0
| East:.. .................... 60.0
| West:....................... 10.0
| Top:........................ 50.0
| Bottom:..................... -50.0
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=90, south=80, east=70, west=20, bottom=-30, top=60
... )
>>> C = A.disjoint_union(B)
>>> C.print_info()
+-------------------- Spatial extent ----------------------------------------+
| North:...................... 90.0
| South:...................... 20.0
| East:.. .................... 70.0
| West:....................... 10.0
| Top:........................ 60.0
| Bottom:..................... -50.0
:param extent: The spatial extent to create a disjoint union with
:return: The union spatial extent
""" # noqa: E501
new = self.disjoint_union_2d(extent)
eT = extent.get_top()
eB = extent.get_bottom()
T = self.get_top()
B = self.get_bottom()
nT = T
nB = B
if eB < B:
nB = eB
if eT > T:
nT = eT
new.set_top(nT)
new.set_bottom(nB)
return new
[docs] def is_in_2d(self, extent) -> bool:
"""Return True if this extent (A) is located in the provided spatial
extent (B) in two dimensions.
::
_____
|A _ |
| |_| |
|_____|B
:param extent: The spatial extent
:return: True or False
"""
if self.get_projection() != extent.get_projection():
self.msgr.error(
_(
"Projections are different. Unable to compute "
"is_in_2d for spatial extents"
)
)
return False
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
return not (eW >= W or eE <= E or eN <= N or eS >= S)
[docs] def is_in(self, extent) -> bool:
"""Return True if this extent (A) is located in the provided spatial
extent (B) in three dimensions.
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=79, south=21, east=59, west=11, bottom=-49, top=49
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.is_in(B)
True
>>> B.is_in(A)
False
:param extent: The spatial extent
:return: True or False
"""
if not self.is_in_2d(extent):
return False
eT = extent.get_top()
eB = extent.get_bottom()
T = self.get_top()
B = self.get_bottom()
return not (eB >= B or eT <= T)
[docs] def contain_2d(self, extent):
"""Return True if this extent (A) contains the provided spatial
extent (B) in two dimensions.
Usage:
.. code-block:: python
>>> A = SpatialExtent(north=80, south=20, east=60, west=10)
>>> B = SpatialExtent(north=79, south=21, east=59, west=11)
>>> A.contain_2d(B)
True
>>> B.contain_2d(A)
False
:param extent: The spatial extent
:return: True or False
"""
return extent.is_in_2d(self)
[docs] def contain(self, extent):
"""Return True if this extent (A) contains the provided spatial
extent (B) in three dimensions.
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=79, south=21, east=59, west=11, bottom=-49, top=49
... )
>>> A.contain(B)
True
>>> B.contain(A)
False
:param extent: The spatial extent
:return: True or False
"""
return extent.is_in(self)
[docs] def equivalent_2d(self, extent) -> bool:
"""Return True if this extent (A) is equal to the provided spatial
extent (B) in two dimensions.
Usage:
.. code-block:: python
>>> A = SpatialExtent(north=80, south=20, east=60, west=10)
>>> B = SpatialExtent(north=80, south=20, east=60, west=10)
>>> A.equivalent_2d(B)
True
>>> B.equivalent_2d(A)
True
:param extent: The spatial extent
:return: True or False
"""
if self.get_projection() != extent.get_projection():
self.msgr.error(
_(
"Projections are different. Unable to compute "
"equivalent_2d for spatial extents"
)
)
return False
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
return not (eW != W or eE != E or eN != N or eS != S)
[docs] def equivalent(self, extent) -> bool:
"""Return True if this extent (A) is equal to the provided spatial
extent (B) in three dimensions.
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.equivalent(B)
True
>>> B.equivalent(A)
True
:param extent: The spatial extent
:return: True or False
"""
if not self.equivalent_2d(extent):
return False
eT = extent.get_top()
eB = extent.get_bottom()
T = self.get_top()
B = self.get_bottom()
return not (eB != B or eT != T)
[docs] def cover_2d(self, extent) -> bool:
"""Return True if this extent (A) covers the provided spatial
extent (B) in two dimensions.
::
_____ _____ _____ _____
|A __| |__ A| |A | B| |B | A|
| |B | | B| | | |__| |__| |
|__|__| |__|__| |_____| |_____|
_____ _____ _____ _____
|A|B| | |A __| |A _ | |__ A|
| |_| | | |__|B | |B| | B|__| |
|_____| |_____| |_|_|_| |_____|
_____ _____ _____ _____
|A|B | |_____|A |A|B|A| |_____|A
| | | |B | | | | | |_____|B
|_|___| |_____| |_|_|_| |_____|A
The following cases are excluded:
- contain
- in
- equivalent
:param extent: The spatial extent
:return: True or False
"""
if self.get_projection() != extent.get_projection():
self.msgr.error(
_(
"Projections are different. Unable to compute"
" cover_2d for spatial extents"
)
)
return False
# Exclude equivalent_2d
if self.equivalent_2d(extent):
return False
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
# Edges of extent located outside of self are not allowed
if eW >= E:
return False
if eE <= W:
return False
if eS >= N:
return False
if eN <= S:
return False
# First we check that at least one edge of extent meets an edge of self
if eW != W and eE != E and eN != N and eS != S:
return False
# We check that at least one edge of extent is located in self
edge_count = 0
if W < eW < E:
edge_count += 1
if W < eE < E:
edge_count += 1
if S < eN < N:
edge_count += 1
if S < eS < N:
edge_count += 1
return edge_count != 0
[docs] def cover(self, extent) -> bool:
"""Return True if this extent covers the provided spatial
extent in three dimensions.
The following cases are excluded:
- contain
- in
- equivalent
:param extent: The spatial extent
:return: True or False
"""
if self.get_projection() != extent.get_projection():
self.msgr.error(
_(
"Projections are different. Unable to compute "
"cover for spatial extents"
)
)
return False
# Exclude equivalent_2d
if self.equivalent_2d(extent):
return False
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
eT = extent.get_top()
eB = extent.get_bottom()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
T = self.get_top()
B = self.get_bottom()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
# Edges of extent located outside of self are not allowed
if (eW >= E) or (eE <= W) or (eS >= N) or (eN <= S) or (eB >= T) or (eT <= B):
return False
# First we check that at least one edge of extent meets an edge of self
if eW != W and eE != E and eN != N and eS != S and eB != B and eT != T:
return False
# We check that at least one edge of extent is located in self
edge_count = 0
if W < eW < E:
edge_count += 1
if W < eE < E:
edge_count += 1
if S < eN < N:
edge_count += 1
if S < eS < N:
edge_count += 1
if S < eN < N:
edge_count += 1
if S < eS < N:
edge_count += 1
if B < eT < T:
edge_count += 1
if B < eB < T:
edge_count += 1
return edge_count != 0
[docs] def covered_2d(self, extent):
"""Return True if this extent is covered by the provided spatial
extent in two dimensions.
The following cases are excluded:
- contain
- in
- equivalent
:param extent: The spatial extent
:return: True or False
"""
return extent.cover_2d(self)
[docs] def covered(self, extent):
"""Return True if this extent is covered by the provided spatial
extent in three dimensions.
The following cases are excluded:
- contain
- in
- equivalent
:param extent: The spatial extent
:return: True or False
"""
return extent.cover(self)
[docs] def overlap_2d(self, extent) -> bool:
"""Return True if this extent (A) overlaps with the provided spatial
extent (B) in two dimensions.
Code is lend from wind_overlap.c in lib/gis
::
_____
|A __|__
| | | B|
|__|__| |
|_____|
The following cases are excluded:
- contain
- in
- cover
- covered
- equivalent
:param extent: The spatial extent
:return: True or False
"""
if self.contain_2d(extent):
return False
if self.is_in_2d(extent):
return False
if self.cover_2d(extent):
return False
if self.covered_2d(extent):
return False
if self.equivalent_2d(extent):
return False
N = extent.get_north()
S = extent.get_south()
E = extent.get_east()
W = extent.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while self.get_west() > E:
E += 360.0
W += 360.0
while self.get_east() < W:
E -= 360.0
W -= 360.0
return not (
self.get_north() <= S
or self.get_south() >= N
or self.get_east() <= W
or self.get_west() >= E
)
[docs] def overlap(self, extent) -> bool:
"""Return True if this extent overlaps with the provided spatial
extent in three dimensions.
The following cases are excluded:
- contain
- in
- cover
- covered
- equivalent
:param extent: The spatial extent
:return: True or False
"""
if self.is_in(extent):
return False
if self.contain(extent):
return False
if self.cover(extent):
return False
if self.covered(extent):
return False
if self.equivalent(extent):
return False
N = extent.get_north()
S = extent.get_south()
E = extent.get_east()
W = extent.get_west()
T = extent.get_top()
B = extent.get_bottom()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while self.get_west() > E:
E += 360.0
W += 360.0
while self.get_east() < W:
E -= 360.0
W -= 360.0
return not (
self.get_north() <= S
or self.get_south() >= N
or self.get_east() <= W
or self.get_west() >= E
or self.get_top() <= B
or self.get_bottom() >= T
)
[docs] def meet_2d(self, extent) -> bool:
"""Return True if this extent (A) meets with the provided spatial
extent (B) in two dimensions.
::
_____ _____
| A | B |
|_____| |
|_____|
_____ _____
| B | A |
| | |
|_____|_____|
___
| A |
| |
|___|
| B |
| |
|_____|
_____
| B |
| |
|_____|_
| A |
| |
|_____|
:param extent: The spatial extent
:return: True or False
"""
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
edge = None
edge_count = 0
if eW == E:
edge = "E"
edge_count += 1
if eE == W:
edge = "W"
edge_count += 1
if eS == N:
edge = "N"
edge_count += 1
if eN == S:
edge = "S"
edge_count += 1
# Meet a a single edge only
if edge_count != 1:
return False
# Check boundaries of the faces
if edge in {"E", "W"}:
if eS > N or eN < S:
return False
if edge in {"N", "S"}:
if eW > E or eE < W:
return False
return True
[docs] def meet(self, extent) -> bool:
"""Return True if this extent meets with the provided spatial
extent in three dimensions.
:param extent: The spatial extent
:return: True or False
"""
eN = extent.get_north()
eS = extent.get_south()
eE = extent.get_east()
eW = extent.get_west()
eT = extent.get_top()
eB = extent.get_bottom()
N = self.get_north()
S = self.get_south()
E = self.get_east()
W = self.get_west()
T = self.get_top()
B = self.get_bottom()
# Adjust the east and west in case of LL projection
if self.get_projection() == "LL":
while eE < W:
eE += 360.0
eW += 360.0
while eW > E:
eE -= 360.0
eW -= 360.0
edge = None
edge_count = 0
if eW == E:
edge = "E"
edge_count += 1
if eE == W:
edge = "W"
edge_count += 1
if eS == N:
edge = "N"
edge_count += 1
if eN == S:
edge = "S"
edge_count += 1
if eB == T:
edge = "T"
edge_count += 1
if eT == B:
edge = "B"
edge_count += 1
# Meet a single edge only
if edge_count != 1:
return False
# Check boundaries of the faces
if edge in {"E", "W"}:
if eS > N or eN < S:
return False
if eB > T or eT < B:
return False
if edge in {"N", "S"}:
if eW > E or eE < W:
return False
if eB > T or eT < B:
return False
if edge in {"T", "B"}:
if eW > E or eE < W:
return False
if eS > N or eN < S:
return False
return True
[docs] def disjoint_2d(self, extent) -> bool:
"""Return True if this extent (A) is disjoint with the provided spatial
extent (B) in three dimensions.
::
_____
| A |
|_____|
_______
| B |
|_______|
:param extent: The spatial extent
:return: True or False
"""
return not (
self.is_in_2d(extent)
or self.contain_2d(extent)
or self.cover_2d(extent)
or self.covered_2d(extent)
or self.equivalent_2d(extent)
or self.overlapping_2d(extent)
or self.meet_2d(extent)
)
[docs] def disjoint(self, extent) -> bool:
"""Return True if this extent is disjoint with the provided spatial
extent in three dimensions.
:param extent: The spatial extent
:return: True or False
"""
return not (
self.is_in(extent)
or self.contain(extent)
or self.cover(extent)
or self.covered(extent)
or self.equivalent(extent)
or self.overlapping(extent)
or self.meet(extent)
)
[docs] def spatial_relation_2d(self, extent) -> Literal[
"equivalent",
"contain",
"in",
"cover",
"covered",
"overlap",
"meet",
"disjoint",
"unknown",
]:
"""Returns the two dimensional spatial relation between this
extent and the provided spatial extent in two dimensions.
Spatial relations are:
- disjoint
- meet
- overlap
- cover
- covered
- in
- contain
- equivalent
Usage: see self.spatial_relation()
"""
if self.equivalent_2d(extent):
return "equivalent"
if self.contain_2d(extent):
return "contain"
if self.is_in_2d(extent):
return "in"
if self.cover_2d(extent):
return "cover"
if self.covered_2d(extent):
return "covered"
if self.overlap_2d(extent):
return "overlap"
if self.meet_2d(extent):
return "meet"
if self.disjoint_2d(extent):
return "disjoint"
return "unknown"
[docs] def spatial_relation(self, extent) -> Literal[
"equivalent",
"contain",
"in",
"cover",
"covered",
"overlap",
"meet",
"disjoint",
"unknown",
]:
"""Returns the two dimensional spatial relation between this
extent and the provided spatial extent in three dimensions.
Spatial relations are:
- disjoint
- meet
- overlap
- cover
- covered
- in
- contain
- equivalent
Usage:
.. code-block:: python
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation(B)
'equivalent'
>>> B.spatial_relation(A)
'equivalent'
>>> B = SpatialExtent(
... north=70, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'cover'
>>> A.spatial_relation(B)
'cover'
>>> B = SpatialExtent(
... north=70, south=30, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'cover'
>>> A.spatial_relation(B)
'cover'
>>> B.spatial_relation_2d(A)
'covered'
>>> B.spatial_relation(A)
'covered'
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'cover'
>>> B.spatial_relation_2d(A)
'covered'
>>> A.spatial_relation(B)
'cover'
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=20, bottom=-50, top=50
... )
>>> B.spatial_relation(A)
'covered'
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=20, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'contain'
>>> A.spatial_relation(B)
'cover'
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=20, bottom=-40, top=50
... )
>>> A.spatial_relation(B)
'cover'
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=20, bottom=-40, top=40
... )
>>> A.spatial_relation(B)
'contain'
>>> B.spatial_relation(A)
'in'
>>> B = SpatialExtent(
... north=90, south=30, east=50, west=20, bottom=-40, top=40
... )
>>> A.spatial_relation_2d(B)
'overlap'
>>> A.spatial_relation(B)
'overlap'
>>> B = SpatialExtent(
... north=90, south=5, east=70, west=5, bottom=-40, top=40
... )
>>> A.spatial_relation_2d(B)
'in'
>>> A.spatial_relation(B)
'overlap'
>>> B = SpatialExtent(
... north=90, south=5, east=70, west=5, bottom=-40, top=60
... )
>>> A.spatial_relation(B)
'overlap'
>>> B = SpatialExtent(
... north=90, south=5, east=70, west=5, bottom=-60, top=60
... )
>>> A.spatial_relation(B)
'in'
>>> A = SpatialExtent(
... north=80, south=60, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=60, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=60, south=40, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=60, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=40, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=90, south=30, east=60, west=40, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=70, south=50, east=60, west=40, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=60, south=20, east=60, west=40, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'meet'
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=40, south=20, east=60, west=40, bottom=-50, top=50
... )
>>> A.spatial_relation_2d(B)
'disjoint'
>>> A.spatial_relation(B)
'disjoint'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=60, south=20, east=60, west=40, bottom=-60, top=60
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=40, west=20, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=90, south=30, east=60, west=40, bottom=-40, top=40
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> B = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> B = SpatialExtent(
... north=80, south=50, east=60, west=30, bottom=-50, top=0
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> B = SpatialExtent(
... north=70, south=50, east=50, west=30, bottom=-50, top=0
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> B = SpatialExtent(
... north=90, south=30, east=70, west=10, bottom=-50, top=0
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=10, bottom=-50, top=0
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> B = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=0, top=50
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> B = SpatialExtent(
... north=80, south=50, east=60, west=30, bottom=0, top=50
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> B = SpatialExtent(
... north=70, south=50, east=50, west=30, bottom=0, top=50
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> B = SpatialExtent(
... north=90, south=30, east=70, west=10, bottom=0, top=50
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=40, east=60, west=20, bottom=-50, top=0
... )
>>> B = SpatialExtent(
... north=70, south=30, east=50, west=10, bottom=0, top=50
... )
>>> A.spatial_relation(B)
'meet'
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=90, south=81, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation(B)
'disjoint'
>>> A = SpatialExtent(
... north=80, south=20, east=60, west=10, bottom=-50, top=50
... )
>>> B = SpatialExtent(
... north=90, south=80, east=60, west=10, bottom=-50, top=50
... )
>>> A.spatial_relation(B)
'meet'
"""
if self.equivalent(extent):
return "equivalent"
if self.contain(extent):
return "contain"
if self.is_in(extent):
return "in"
if self.cover(extent):
return "cover"
if self.covered(extent):
return "covered"
if self.overlap(extent):
return "overlap"
if self.meet(extent):
return "meet"
if self.disjoint(extent):
return "disjoint"
return "unknown"
[docs] def set_spatial_extent_from_values(
self, north, south, east, west, top, bottom
) -> None:
"""Set the three dimensional spatial extent
:param north: The northern edge
:param south: The southern edge
:param east: The eastern edge
:param west: The western edge
:param top: The top edge
:param bottom: The bottom edge
"""
self.set_north(north)
self.set_south(south)
self.set_east(east)
self.set_west(west)
self.set_top(top)
self.set_bottom(bottom)
[docs] def set_spatial_extent(self, spatial_extent) -> None:
"""Set the three dimensional spatial extent
:param spatial_extent: An object of type SpatialExtent or its
subclasses
"""
self.set_north(spatial_extent.get_north())
self.set_south(spatial_extent.get_south())
self.set_east(spatial_extent.get_east())
self.set_west(spatial_extent.get_west())
self.set_top(spatial_extent.get_top())
self.set_bottom(spatial_extent.get_bottom())
[docs] def set_projection(self, proj) -> None:
"""Set the projection of the spatial extent it should be XY or LL.
As default the projection is XY
"""
if proj is None or (proj not in {"XY", "LL"}):
self.D["proj"] = "XY"
else:
self.D["proj"] = proj
[docs] def set_spatial_extent_from_values_2d(self, north, south, east, west) -> None:
"""Set the two dimensional spatial extent from values
:param north: The northern edge
:param south: The southern edge
:param east: The eastern edge
:param west: The western edge
"""
self.set_north(north)
self.set_south(south)
self.set_east(east)
self.set_west(west)
[docs] def set_spatial_extent_2d(self, spatial_extent) -> None:
"""Set the three dimensional spatial extent
:param spatial_extent: An object of type SpatialExtent or its
subclasses
"""
self.set_north(spatial_extent.north)
self.set_south(spatial_extent.south)
self.set_east(spatial_extent.east)
self.set_west(spatial_extent.west)
[docs] def set_id(self, ident) -> None:
"""Convenient method to set the unique identifier (primary key)"""
self.ident = ident
self.D["id"] = ident
[docs] def set_north(self, north) -> None:
"""Set the northern edge of the map"""
if north is not None:
self.D["north"] = float(north)
else:
self.D["north"] = None
[docs] def set_south(self, south) -> None:
"""Set the southern edge of the map"""
if south is not None:
self.D["south"] = float(south)
else:
self.D["south"] = None
[docs] def set_west(self, west) -> None:
"""Set the western edge of the map"""
if west is not None:
self.D["west"] = float(west)
else:
self.D["west"] = None
[docs] def set_east(self, east) -> None:
"""Set the eastern edge of the map"""
if east is not None:
self.D["east"] = float(east)
else:
self.D["east"] = None
[docs] def set_top(self, top) -> None:
"""Set the top edge of the map"""
if top is not None:
self.D["top"] = float(top)
else:
self.D["top"] = None
[docs] def set_bottom(self, bottom) -> None:
"""Set the bottom edge of the map"""
if bottom is not None:
self.D["bottom"] = float(bottom)
else:
self.D["bottom"] = None
[docs] def get_id(self):
"""Convenient method to get the unique identifier (primary key)
:return: None if not found
"""
if "id" in self.D:
return self.D["id"]
return None
[docs] def get_projection(self):
"""Get the projection of the spatial extent"""
return self.D["proj"]
[docs] def get_volume(self):
"""Compute the volume of the extent, in case z is zero
(top == bottom or top - bottom = 1) the area is returned"""
if self.get_projection() == "LL":
self.msgr.error(_("Volume computation is not supported for LL projections"))
area = self.get_area()
bbox = self.get_spatial_extent_as_tuple()
z = abs(bbox[4] - bbox[5])
if z == 0:
z = 1.0
return area * z
[docs] def get_area(self):
"""Compute the area of the extent, extent in z direction is ignored"""
if self.get_projection() == "LL":
self.msgr.error(_("Area computation is not supported for LL projections"))
bbox = self.get_spatial_extent_as_tuple()
y = abs(bbox[0] - bbox[1])
x = abs(bbox[2] - bbox[3])
return x * y
[docs] def get_spatial_extent_as_tuple(self):
"""Return a tuple (north, south, east, west, top, bottom)
of the spatial extent"""
return (self.north, self.south, self.east, self.west, self.top, self.bottom)
[docs] def get_spatial_extent_as_tuple_2d(self):
"""Return a tuple (north, south, east, west,) of the 2d spatial extent"""
return (self.north, self.south, self.east, self.west)
[docs] def get_north(self):
"""Get the northern edge of the map
:return: None if not found"""
if "north" in self.D:
return self.D["north"]
return None
[docs] def get_south(self):
"""Get the southern edge of the map
:return: None if not found"""
if "south" in self.D:
return self.D["south"]
return None
[docs] def get_east(self):
"""Get the eastern edge of the map
:return: None if not found"""
if "east" in self.D:
return self.D["east"]
return None
[docs] def get_west(self):
"""Get the western edge of the map
:return: None if not found"""
if "west" in self.D:
return self.D["west"]
return None
[docs] def get_top(self):
"""Get the top edge of the map
:return: None if not found"""
if "top" in self.D:
return self.D["top"]
return None
[docs] def get_bottom(self):
"""Get the bottom edge of the map
:return: None if not found"""
if "bottom" in self.D:
return self.D["bottom"]
return None
id = property(fget=get_id, fset=set_id)
north = property(fget=get_north, fset=set_north)
south = property(fget=get_south, fset=set_south)
east = property(fget=get_east, fset=set_east)
west = property(fget=get_west, fset=set_west)
top = property(fget=get_top, fset=set_top)
bottom = property(fget=get_bottom, fset=set_bottom)
[docs] def print_info(self) -> None:
"""Print information about this class in human readable style"""
# 0123456789012345678901234567890
print(
" +-------------------- Spatial extent ----------------------------------------+" # noqa: E501
)
print(" | North:...................... " + str(self.get_north()))
print(" | South:...................... " + str(self.get_south()))
print(" | East:.. .................... " + str(self.get_east()))
print(" | West:....................... " + str(self.get_west()))
print(" | Top:........................ " + str(self.get_top()))
print(" | Bottom:..................... " + str(self.get_bottom()))
[docs] def print_shell_info(self) -> None:
"""Print information about this class in shell style"""
print("north=" + str(self.get_north()))
print("south=" + str(self.get_south()))
print("east=" + str(self.get_east()))
print("west=" + str(self.get_west()))
print("top=" + str(self.get_top()))
print("bottom=" + str(self.get_bottom()))
###############################################################################
[docs]class RasterSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self, "raster_spatial_extent", ident, north, south, east, west, top, bottom
)
[docs]class Raster3DSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self,
"raster3d_spatial_extent",
ident,
north,
south,
east,
west,
top,
bottom,
)
[docs]class VectorSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self, "vector_spatial_extent", ident, north, south, east, west, top, bottom
)
[docs]class STRDSSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self, "strds_spatial_extent", ident, north, south, east, west, top, bottom
)
[docs]class STR3DSSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self, "str3ds_spatial_extent", ident, north, south, east, west, top, bottom
)
[docs]class STVDSSpatialExtent(SpatialExtent):
def __init__(
self,
ident=None,
north=None,
south=None,
east=None,
west=None,
top=None,
bottom=None,
) -> None:
SpatialExtent.__init__(
self, "stvds_spatial_extent", ident, north, south, east, west, top, bottom
)
###############################################################################
if __name__ == "__main__":
import doctest
doctest.testmod()