diff --git a/ckanext/spatial/lib/__init__.py b/ckanext/spatial/lib/__init__.py index fd0cd41..2e84198 100644 --- a/ckanext/spatial/lib/__init__.py +++ b/ckanext/spatial/lib/__init__.py @@ -105,3 +105,23 @@ def fit_bbox(bbox_dict): "miny": _adjust_latitude(bbox_dict["miny"]), "maxy": _adjust_latitude(bbox_dict["maxy"]), } + + +def fit_linear_ring(lr): + + bbox = { + "minx": lr[0][0], + "maxx": lr[2][0], + "miny": lr[1][1], + "maxy": lr[0][1], + } + + bbox = fit_bbox(bbox) + + return [ + (bbox["minx"], bbox["maxy"]), + (bbox["minx"], bbox["miny"]), + (bbox["maxx"], bbox["miny"]), + (bbox["maxx"], bbox["maxy"]), + (bbox["minx"], bbox["maxy"]), + ] diff --git a/ckanext/spatial/plugin/__init__.py b/ckanext/spatial/plugin/__init__.py index 72fdf2c..62fa377 100644 --- a/ckanext/spatial/plugin/__init__.py +++ b/ckanext/spatial/plugin/__init__.py @@ -13,7 +13,7 @@ from ckan import plugins as p from ckan.lib.search import SearchError from ckan.lib.helpers import json -from ckanext.spatial.lib import normalize_bbox, fit_bbox +from ckanext.spatial.lib import normalize_bbox, fit_bbox, fit_linear_ring if tk.check_ckan_version(min_version="2.9.0"): from ckanext.spatial.plugin.flask_plugin import ( @@ -36,7 +36,6 @@ ALLOWED_SEARCH_BACKENDS = [ ] - class SpatialMetadata(p.SingletonPlugin): p.implements(p.IPackageController, inherit=True) @@ -251,8 +250,12 @@ class SpatialQuery(SpatialQueryMixin, p.SingletonPlugin): # Check if coordinates are defined counter-clockwise, # otherwise we'll get wrong results from Solr lr = shapely.geometry.polygon.LinearRing(geometry['coordinates'][0]) - lr_coords = list(lr.coords) if lr.is_ccw else reversed(list(lr.coords)) - polygon = shapely.geometry.polygon.Polygon(lr_coords) + lr_coords = ( + list(lr.coords) if lr.is_ccw + else reversed(list(lr.coords)) + ) + polygon = shapely.geometry.polygon.Polygon( + fit_linear_ring(lr_coords)) wkt = polygon.wkt if not wkt: diff --git a/ckanext/spatial/tests/test_spatial_search.py b/ckanext/spatial/tests/test_spatial_search.py index ce3f672..a446fa9 100644 --- a/ckanext/spatial/tests/test_spatial_search.py +++ b/ckanext/spatial/tests/test_spatial_search.py @@ -107,6 +107,29 @@ class TestBBoxSearch(SpatialTestBase): assert result["count"] == 1 assert result["results"][0]["id"] == dataset["id"] + def test_spatial_polygon_split_across_antimeridian_outside_bbox(self): + """ + This test passes because as the geometry passes the antemeridian, the + extent generated to be index is (-180, miny, 180, maxy). Sites needing to + deal with this scenario should use the `solr-spatial-field` backend. + See ``TestSpatialFieldSearch.test_spatial_polygon_split_across_antimeridian_outside_bbox`` + """ + dataset = factories.Dataset( + extras=[ + { + "key": "spatial", + "value": self.read_file("data/chukot.geojson") + } + ] + ) + + result = helpers.call_action( + "package_search", extras={"ext_bbox": "0,61,15,64"} + ) + + assert result["count"] == 1 + assert result["results"][0]["id"] == dataset["id"] + def test_spatial_query_nz(self): dataset = factories.Dataset(extras=[{"key": "spatial", "value": extents["nz"]}]) @@ -442,3 +465,35 @@ class TestSpatialFieldSearch(SpatialTestBase): assert result["count"] == 1 assert result["results"][0]["id"] == dataset["id"] + def test_spatial_polygon_split_across_antimeridian(self): + dataset = factories.Dataset( + extras=[ + { + "key": "spatial", + "value": self.read_file("data/chukot.geojson") + } + ] + ) + + result = helpers.call_action( + "package_search", extras={"ext_bbox": "175,61,179,64"} + ) + + assert result["count"] == 1 + assert result["results"][0]["id"] == dataset["id"] + + def test_spatial_polygon_split_across_antimeridian_outside_bbox(self): + factories.Dataset( + extras=[ + { + "key": "spatial", + "value": self.read_file("data/chukot.geojson") + } + ] + ) + + result = helpers.call_action( + "package_search", extras={"ext_bbox": "0,61,15,64"} + ) + + assert result["count"] == 0