Repository: opendatacube/datacube-core

Issue:
Query doesn't handle multipolygons correctly
### Expected behaviour
When query with polygons with holes, e.g., `multipolygon`, it's supposed to return the datasets covering the exterior of polygons. 

### Actual behaviour
Gives error `AttributeError: 'MultiPolygon' object has no attribute 'exterior'`


### Steps to reproduce the behaviour

Given a multipolygon
```
test_poly = {'type': 'MultiPolygon',
 'coordinates': [[[(784902.250461787, -3777209.283615751, 0.0),
    (784909.2272874936, -3777219.9128739703, 0.0),
    (784921.8882912621, -3777220.6711605396, 0.0),
    (784934.1526078805, -3777227.778634619, 0.0),
    (784946.2186576352, -3777238.060941644, 0.0),
    (784955.3141866103, -3777244.976476034, 0.0),
    (784964.6023693979, -3777248.7139800135, 0.0),
    (784973.3031510785, -3777261.977839045, 0.0),
    (784985.365512874, -3777272.26031927, 0.0),
    (784994.2989889085, -3777282.3915708438, 0.0),
    (785003.2350752652, -3777292.526152037, 0.0),
    (785009.0079395659, -3777302.4711605944, 0.0),
    (785011.6161862351, -3777312.226466188, 0.0),
    (785020.1871215776, -3777328.7465725783, 0.0),
    (785028.9463838525, -3777342.0827365164, 0.0),
    (785038.0802808888, -3777349.043963233, 0.0),
    (785043.6691648476, -3777362.1844769977, 0.0),
    (785052.4224625938, -3777375.514582131, 0.0),
    (785067.33218031, -3777392.4276976436, 0.0),
    (785076.2800419629, -3777402.577907633, 0.0),
    (785084.8498440683, -3777419.1035048943, 0.0),
    (785100.1583611667, -3777429.6656772047, 0.0),
    (785109.1149003133, -3777439.826050263, 0.0),
    (785118.0616985336, -3777449.974010499, 0.0),
    (785126.8121738955, -3777463.301805863, 0.0),
    (785139.1805850528, -3777470.5291196387, 0.0),
    (785157.3763207421, -3777487.770229241, 0.0),
    (785160.3704429455, -3777491.1670745052, 0.0),
    (785176.2902669497, -3777492.1954940055, 0.0),
    (785188.8408230208, -3777496.2109568343, 0.0),
    (785204.204631973, -3777506.8194623888, 0.0),
    (785213.1998193897, -3777517.0191175565, 0.0),
    (785225.7017325796, -3777520.9748451505, 0.0),
    (785231.4216123484, -3777530.870942889, 0.0),
    (785230.5826019421, -3777543.547882393, 0.0),
    (785236.0903547145, -3777556.6063653976, 0.0),
    (785235.2456293143, -3777569.2692810446, 0.0),
    (785237.5503742732, -3777582.09264767, 0.0),
    (785242.8063186668, -3777598.255784465, 0.0),
    (785241.7838727236, -3777614.1066779774, 0.0),
    (785232.0873100683, -3777616.7242034134, 0.0),
    (785219.0220020488, -3777622.3276026994, 0.0),
    (785206.3634803072, -3777621.5917806607, 0.0),
    (785190.5381733552, -3777620.671820106, 0.0),
    (785181.0289707519, -3777620.1007522866, 0.0),
    (785165.7256626785, -3777609.542464435, 0.0),
    (785153.6009980291, -3777599.186639512, 0.0),
    (785151.0123405419, -3777589.445076907, 0.0),
    (785129.7878501676, -3777572.1835268047, 0.0),
    (785120.6637065224, -3777565.2401126605, 0.0),
    (785111.9268317074, -3777551.929383399, 0.0),
    (785099.5515484214, -3777544.659463262, 0.0),
    (785093.1770704314, -3777544.2151207756, 0.0),
    (785075.2776904926, -3777523.889862528, 0.0),
    (785069.5357740112, -3777513.9863262214, 0.0),
    (785060.4392970912, -3777507.087698994, 0.0),
    (785048.3793915994, -3777496.839451149, 0.0),
    (785039.4667285047, -3777486.7446585577, 0.0),
    (785030.6978427209, -3777473.3733962346, 0.0),
    (785028.4644328132, -3777457.2107329443, 0.0),
    (785016.3525202647, -3777446.865115015, 0.0),
    (785007.0341284424, -3777443.09649566, 0.0),
    (784991.1900282837, -3777442.134980807, 0.0),
    (784982.2507872283, -3777431.9840799067, 0.0),
    (784966.7873855606, -3777424.6419333685, 0.0),
    (784954.6884223856, -3777414.314513769, 0.0),
    (784942.206784308, -3777410.3594470695, 0.0),
    (784930.109509021, -3777400.033161737, 0.0),
    (784914.2687674463, -3777399.0715618357, 0.0),
    (784905.3417970464, -3777388.9411944672, 0.0),
    (784899.7773264498, -3777375.825404769, 0.0),
    (784890.853680972, -3777365.7072412986, 0.0),
    (784891.0461590216, -3777362.522414243, 0.0),
    (784879.5329753272, -3777342.662028957, 0.0),
    (784867.4421403483, -3777332.3554985654, 0.0),
    (784855.5436584204, -3777318.8640786614, 0.0),
    (784849.7871836275, -3777308.933943307, 0.0),
    (784841.0551910475, -3777295.6116751786, 0.0),
    (784832.517391678, -3777279.102381682, 0.0),
    (784819.8494207561, -3777278.3087981585, 0.0),
    (784810.9312710986, -3777268.159730455, 0.0),
    (784802.2082313187, -3777254.843997447, 0.0),
    (784793.2921356373, -3777244.7209444344, 0.0),
    (784787.7322836779, -3777231.624889696, 0.0),
    (784800.5858193599, -3777229.229118986, 0.0),
    (784812.6693547778, -3777239.5496714115, 0.0),
    (784824.9483194873, -3777246.694099767, 0.0),
    (784830.5090232156, -3777259.800573282, 0.0),
    (784839.6243488826, -3777266.7571226824, 0.0),
    (784851.9096666649, -3777273.912946552, 0.0),
    (784861.0250522383, -3777280.8472062983, 0.0),
    (784869.9473088644, -3777290.9643023387, 0.0),
    (784881.6509527154, -3777307.6372375414, 0.0),
    (784890.5730508678, -3777317.7539598253, 0.0),
    (784899.6870935895, -3777324.6868402753, 0.0),
    (784905.4427703507, -3777334.6150581427, 0.0),
    (784908.0323312692, -3777344.3551585097, 0.0),
    (784923.1008819826, -3777358.0509902667, 0.0),
    (784926.2686959393, -3777358.2433209904, 0.0),
    (784948.0583599582, -3777365.958236469, 0.0),
    (784963.5139733218, -3777373.289007997, 0.0),
    (784972.8260346763, -3777377.050619755, 0.0),
    (784982.138297379, -3777380.8124295715, 0.0),
    (784997.595377598, -3777388.1440374292, 0.0),
    (785006.7173801847, -3777395.091499783, 0.0),
    (785018.4360499606, -3777411.788685687, 0.0),
    (785030.3468209505, -3777425.3019188456, 0.0),
    (785035.9356408343, -3777438.455178054, 0.0),
    (785041.5301188342, -3777451.6189393625, 0.0),
    (785053.6601672843, -3777461.9903015625, 0.0),
    (785060.1941062212, -3777459.194198046, 0.0),
    (785061.5032605566, -3777436.8475265224, 0.0),
    (785055.7233381905, -3777426.8838146515, 0.0),
    (785046.7772634104, -3777416.7313035894, 0.0),
    (785038.403959658, -3777397.0235252604, 0.0),
    (785026.4878567308, -3777383.5043472443, 0.0),
    (785020.3428311758, -3777379.9348469283, 0.0),
    (785005.2673462778, -3777366.234329175, 0.0),
    (784996.7176210657, -3777349.733225817, 0.0),
    (784984.6087002121, -3777339.3922431227, 0.0),
    (784972.6902329214, -3777325.864272522, 0.0),
    (784966.9199296795, -3777315.913250271, 0.0),
    (784954.8193663284, -3777305.5804066528, 0.0),
    (784955.5754908957, -3777292.8385140523, 0.0),
    (784950.018743284, -3777279.74235495, 0.0),
    (784950.7889806144, -3777267.0244360343, 0.0),
    (784938.5315102302, -3777259.9254441205, 0.0),
    (784929.4328446202, -3777253.0114167687, 0.0),
    (784917.7518362403, -3777236.3745729886, 0.0),
    (784912.0043085627, -3777226.460067125, 0.0),
    (784903.283802934, -3777213.1748731732, 0.0),
    (784902.250461787, -3777209.283615751, 0.0)]],
  [[(784900.7005266733, -3777203.4470181447, 0.0),
    (784902.250461787, -3777209.283615751, 0.0),
    (784900.5062423237, -3777206.626281347, 0.0),
    (784900.7005266733, -3777203.4470181447, 0.0)]]]}
```
then do
```
from datacube.utils.geometry import CRS, Geometry
from datacube import Datacube

dc = Datacube()

query_poly = Geometry(test_poly, crs=CRS('EPSG:3577'))
query = {'time': ('2020-01-01', '2020-12-31'), 'geopolygon':query_poly}
dc.find_datasets(product="ga_ls8c_ard_3", **query, measurements=['blue'])
```
return error
```
/env/lib/python3.6/site-packages/datacube/utils/geometry/_base.py in lonlat_bounds(geom, mode, resolution)
   1391 
   1392     if geom.type in ('Polygon', 'MultiPolygon'):
-> 1393         geom = geom.exterior
   1394 
   1395     if resolution is not None and math.isfinite(resolution):

/env/lib/python3.6/site-packages/datacube/utils/geometry/_base.py in exterior(self)
    537     @property
    538     def exterior(self) -> 'Geometry':
--> 539         return Geometry(self.geom.exterior, self.crs)
    540 
    541     @property

AttributeError: 'MultiPolygon' object has no attribute 'exterior'
```

### Environment information

* Which ``datacube --version`` are you using?
 Open Data Cube core, version 1.8.4.dev193+g3997b90f
* What datacube deployment/enviornment are you running against?
  sandbox dev unstable



Discussion:

Resolving patch:
diff --git a/datacube/utils/geometry/_base.py b/datacube/utils/geometry/_base.py
--- a/datacube/utils/geometry/_base.py
+++ b/datacube/utils/geometry/_base.py
@@ -1389,16 +1389,12 @@ def lonlat_bounds(geom: Geometry,
     if geom.crs.geographic:
         return geom.boundingbox
 
-    if geom.type in ('Polygon', 'MultiPolygon'):
-        geom = geom.exterior
-
     if resolution is not None and math.isfinite(resolution):
         geom = geom.segmented(resolution)
 
-    xx, yy = geom.to_crs('EPSG:4326', resolution=math.inf).xy
-    xx_range = min(xx), max(xx)
-    yy_range = min(yy), max(yy)
+    bbox = geom.to_crs('EPSG:4326', resolution=math.inf).boundingbox
 
+    xx_range = bbox.range_x
     if mode == "safe":
         # If range in Longitude is more than 180 then it's probably wrapped
         # around 180 (X-360 for X > 180), so we add back 360 but only for X<0
@@ -1406,18 +1402,17 @@ def lonlat_bounds(geom: Geometry,
         # a globe, so we need to check for that too, but this is not yet
         # implemented...
 
-        span_x = xx_range[1] - xx_range[0]
-        if span_x > 180:
+        if bbox.span_x > 180:
             # TODO: check the case when input geometry spans >180 region.
             #       For now we assume "smaller" geometries not too close
             #       to poles.
-            xx_ = [x + 360 if x < 0 else x for x in xx]
+            xx_ = [x + 360 if x < 0 else x for x in bbox.range_x]
             xx_range_ = min(xx_), max(xx_)
             span_x_ = xx_range_[1] - xx_range_[0]
-            if span_x_ < span_x:
+            if span_x_ < bbox.span_x:
                 xx_range = xx_range_
 
-    return BoundingBox.from_xy(xx_range, yy_range)
+    return BoundingBox.from_xy(xx_range, bbox.range_y)
 
 
 def assign_crs(xx: Union[xr.DataArray, xr.Dataset],


Test patch:
diff --git a/tests/test_geometry.py b/tests/test_geometry.py
--- a/tests/test_geometry.py
+++ b/tests/test_geometry.py
@@ -1541,6 +1541,22 @@ def test_lonlat_bounds():
     with pytest.raises(ValueError):
         geometry.lonlat_bounds(geometry.box(0, 0, 1, 1, None))
 
+    multi = {
+        "type": "MultiPolygon",
+        "coordinates": [
+            [[[174, 52], [174, 53], [175, 53], [174, 52]]],
+            [[[168, 54], [167, 55], [167, 54], [168, 54]]]
+        ]
+    }
+
+    multi_geom = geometry.Geometry(multi, "epsg:4326")
+    multi_geom_projected = multi_geom.to_crs('epsg:32659', math.inf)
+
+    ll_bounds = geometry.lonlat_bounds(multi_geom)
+    ll_bounds_projected = geometry.lonlat_bounds(multi_geom_projected)
+
+    assert ll_bounds == approx(ll_bounds_projected)
+
 
 @pytest.mark.xfail(True, reason="Bounds computation for large geometries in safe mode is broken")
 def test_lonalt_bounds_more_than_180():