From fe62d47e59a43eafdb07f174c61c9166b519294b Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 10 Oct 2024 23:50:06 +0200 Subject: [PATCH 01/19] refactor geographic crs attribute --- CHANGES.md | 5 +++++ morecantile/models.py | 17 ++++------------- tests/test_models.py | 21 +++++++-------------- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b2c8882..5d9c790 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,9 @@ +## 6.0.0 (TBD) + +* remove `_geographic_crs` private attribute in `TileMatrixSet` model **breaking change** +* use `crs.geodetic_crs` property as `geographic_crs` **breaking change** + ## 5.4.2 (2024-08-29) * better handle anti-meridian crossing bbox in `tms.tiles()` (author @ljstrnadiii, https://github.com/developmentseed/morecantile/pull/154) diff --git a/morecantile/models.py b/morecantile/models.py index 863a5b6..c43349c 100644 --- a/morecantile/models.py +++ b/morecantile/models.py @@ -486,7 +486,6 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True): ] # Private attributes - _geographic_crs: pyproj.CRS = PrivateAttr(default=WGS84_CRS) _to_geographic: pyproj.Transformer = PrivateAttr() _from_geographic: pyproj.Transformer = PrivateAttr() @@ -494,16 +493,12 @@ def __init__(self, **data): """Set private attributes.""" super().__init__(**data) - self._geographic_crs = pyproj.CRS.from_user_input( - data.get("_geographic_crs", WGS84_CRS) - ) - try: self._to_geographic = pyproj.Transformer.from_crs( - self.crs._pyproj_crs, self._geographic_crs, always_xy=True + self.crs._pyproj_crs, self.crs._pyproj_crs.geodetic_crs, always_xy=True ) self._from_geographic = pyproj.Transformer.from_crs( - self._geographic_crs, self.crs._pyproj_crs, always_xy=True + self.crs._pyproj_crs.geodetic_crs, self.crs._pyproj_crs, always_xy=True ) except ProjError: warnings.warn( @@ -555,7 +550,7 @@ def __repr__(self): @cached_property def geographic_crs(self) -> pyproj.CRS: """Return the TMS's geographic CRS.""" - return self._geographic_crs + return self.crs._pyproj_crs.geodetic_crs @cached_property def rasterio_crs(self): @@ -565,7 +560,7 @@ def rasterio_crs(self): @cached_property def rasterio_geographic_crs(self): """Return the geographic CRS as a rasterio CRS.""" - return to_rasterio_crs(self._geographic_crs) + return to_rasterio_crs(self.crs._pyproj_crs.geodetic_crs) @property def minzoom(self) -> int: @@ -656,7 +651,6 @@ def custom( title: Optional[str] = None, id: Optional[str] = None, ordered_axes: Optional[List[str]] = None, - geographic_crs: pyproj.CRS = WGS84_CRS, screen_pixel_size: float = 0.28e-3, decimation_base: int = 2, **kwargs: Any, @@ -689,8 +683,6 @@ def custom( Tile Matrix Set title id: str, optional Tile Matrix Set identifier - geographic_crs: pyproj.CRS - Geographic (lat,lon) coordinate reference system (default is EPSG:4326) ordered_axes: list of str, optional Override Axis order (e.g `["N", "S"]`) else default to CRS's metadata screen_pixel_size: float, optional @@ -769,7 +761,6 @@ def custom( tileMatrices=tile_matrices, id=id, title=title, - _geographic_crs=geographic_crs, **kwargs, ) diff --git a/tests/test_models.py b/tests/test_models.py index 95e4c33..a21997a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -30,6 +30,7 @@ def test_tile_matrix_set(tileset): ts = TileMatrixSet.model_validate_json(f.read()) # This would fail if `crs` isn't supported by PROJ assert isinstance(ts.crs._pyproj_crs, pyproj.CRS) + assert isinstance(ts.geographic_crs, pyproj.CRS) assert repr(ts) @@ -163,17 +164,13 @@ def test_Custom(): assert round(wmMat.pointOfOrigin[0], 6) == round(cusMat.pointOfOrigin[0], 6) extent = (-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892) - custom_tms = TileMatrixSet.custom( - extent, pyproj.CRS.from_epsg(3857), geographic_crs="epsg:4326" - ) - assert isinstance(custom_tms._geographic_crs, pyproj.CRS) - assert custom_tms._geographic_crs == pyproj.CRS.from_epsg(4326) + custom_tms = TileMatrixSet.custom(extent, pyproj.CRS.from_epsg(3857)) + assert isinstance(custom_tms.geographic_crs, pyproj.CRS) + assert custom_tms.geographic_crs == pyproj.CRS.from_epsg(4326) extent = (-20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892) - custom_tms = TileMatrixSet.custom( - extent, pyproj.CRS.from_epsg(3857), geographic_crs=pyproj.CRS.from_epsg(4326) - ) - assert isinstance(custom_tms._geographic_crs, pyproj.CRS) + custom_tms = TileMatrixSet.custom(extent, pyproj.CRS.from_epsg(3857)) + assert isinstance(custom_tms.geographic_crs, pyproj.CRS) def test_custom_tms_bounds_epsg4326(): @@ -300,8 +297,7 @@ def test_schema(): "+proj=stere +lat_0=90 +lon_0=0 +k=2 +x_0=0 +y_0=0 +R=3396190 +units=m +no_defs" ) extent = [-13584760.000, -13585240.000, 13585240.000, 13584760.000] - with pytest.warns(UserWarning): - tms = morecantile.TileMatrixSet.custom(extent, crs, id="MarsNPolek2MOLA5k") + tms = morecantile.TileMatrixSet.custom(extent, crs, id="MarsNPolek2MOLA5k") assert tms.model_json_schema() assert tms.model_dump(exclude_none=True) json_doc = json.loads(tms.model_dump_json(exclude_none=True)) @@ -338,7 +334,6 @@ def test_mars_tms(): MARS_MERCATOR, extent_crs=MARS2000_SPHERE, title="Web Mercator Mars", - geographic_crs=MARS2000_SPHERE, ) pos = (35, 40, 3) @@ -362,7 +357,6 @@ def test_mars_local_tms(): [-5e5, -5e5, 5e5, 5e5], SYRTIS_TM, title="Web Mercator Mars", - geographic_crs=MARS2000_SPHERE, ) assert SYRTIS_TM == syrtis_tms.crs._pyproj_crs assert syrtis_tms.model_dump(mode="json") @@ -558,7 +552,6 @@ def test_boundingbox(): def test_private_attr(): """Check private attr.""" tms = morecantile.tms.get("WebMercatorQuad") - assert "_geographic_crs" in tms.__private_attributes__ assert "_to_geographic" in tms.__private_attributes__ assert "_from_geographic" in tms.__private_attributes__ From 81538fb50afd0f302e8552f6fc1b0b4030918d66 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Fri, 11 Oct 2024 10:40:34 +0200 Subject: [PATCH 02/19] add more tests --- tests/test_models.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index a21997a..90f15ec 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -34,6 +34,20 @@ def test_tile_matrix_set(tileset): assert repr(ts) +@pytest.mark.parametrize("tileset", tilesets) +def test_geographic_crs_bbox(tileset): + """check that geographic bounds are correct.""" + with open(tileset, "r") as f: + ts = TileMatrixSet.model_validate_json(f.read()) + + if not pyproj.CRS.from_epsg(4326) == ts.geographic_crs: + _to_geographic = pyproj.Transformer.from_crs( + ts.crs._pyproj_crs, pyproj.CRS.from_epsg(4326), always_xy=True + ) + bbox = _to_geographic.transform_bounds(*ts.xy_bbox, densify_pts=21) + assert bbox == ts.bbox + + def test_tile_matrix_iter(): """Test iterator""" tms = morecantile.tms.get("WebMercatorQuad") @@ -312,13 +326,12 @@ def test_schema(): assert json_doc["crs"] == "http://www.opengis.net/def/crs/EPSG/0/3031" -MARS2000_SPHERE = pyproj.CRS.from_proj4("+proj=longlat +R=3396190 +no_defs") - - def test_mars_tms(): """The Mars global mercator scheme should broadly align with the Earth Web Mercator CRS, despite the different planetary radius and scale. """ + MARS2000_SPHERE = pyproj.CRS.from_proj4("+proj=longlat +R=3396190 +no_defs") + MARS_MERCATOR = pyproj.CRS.from_proj4( "+proj=merc +R=3396190 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +no_defs" ) @@ -335,6 +348,7 @@ def test_mars_tms(): extent_crs=MARS2000_SPHERE, title="Web Mercator Mars", ) + assert mars_tms.geographic_crs pos = (35, 40, 3) mars_tile = mars_tms.tile(*pos) @@ -345,9 +359,17 @@ def test_mars_tms(): assert mars_tile.y == earth_tile.y assert mars_tile.z == earth_tile.z == 3 + _to_geographic = pyproj.Transformer.from_crs( + mars_tms.crs._pyproj_crs, MARS2000_SPHERE, always_xy=True + ) + bbox = _to_geographic.transform_bounds(*mars_tms.xy_bbox, densify_pts=21) + assert bbox == mars_tms.bbox + def test_mars_local_tms(): """Local TMS using Mars CRS""" + MARS2000_SPHERE = pyproj.CRS.from_proj4("+proj=longlat +R=3396190 +no_defs") + # A transverse mercator projection for the landing site of the Perseverance rover. SYRTIS_TM = pyproj.CRS.from_proj4( "+proj=tmerc +lat_0=17 +lon_0=76.5 +k=0.9996 +x_0=0 +y_0=0 +a=3396190 +b=3376200 +units=m +no_defs" @@ -359,12 +381,19 @@ def test_mars_local_tms(): title="Web Mercator Mars", ) assert SYRTIS_TM == syrtis_tms.crs._pyproj_crs + assert syrtis_tms.geographic_crs assert syrtis_tms.model_dump(mode="json") center = syrtis_tms.ul(1, 1, 1) assert round(center.x, 6) == 76.5 assert round(center.y, 6) == 17 + _to_geographic = pyproj.Transformer.from_crs( + syrtis_tms.crs._pyproj_crs, MARS2000_SPHERE, always_xy=True + ) + bbox = _to_geographic.transform_bounds(*syrtis_tms.xy_bbox, densify_pts=21) + assert bbox == syrtis_tms.bbox + @pytest.mark.parametrize( "identifier, file, crs", From 4aa83ec152fe5a563a2f2aab3be8ab1b69010aba Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Tue, 15 Oct 2024 18:43:07 +0200 Subject: [PATCH 03/19] check crs equality --- tests/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_models.py b/tests/test_models.py index 90f15ec..a62078b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -348,7 +348,7 @@ def test_mars_tms(): extent_crs=MARS2000_SPHERE, title="Web Mercator Mars", ) - assert mars_tms.geographic_crs + assert mars_tms.geographic_crs == MARS2000_SPHERE pos = (35, 40, 3) mars_tile = mars_tms.tile(*pos) From bd73194d824a6cc80344f48d58daba203eed422a Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 13:20:38 -0700 Subject: [PATCH 04/19] added two failing tests for mars crss --- .gitignore | 4 ++++ morecantile/models.py | 1 - tests/test_models.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 23077b4..788139f 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,7 @@ dmypy.json # PyCharm: .idea + +# VSCode +.vscode +.vscode/ diff --git a/morecantile/models.py b/morecantile/models.py index c43349c..e5052b0 100644 --- a/morecantile/models.py +++ b/morecantile/models.py @@ -40,7 +40,6 @@ NumType = Union[float, int] BoundsType = Tuple[NumType, NumType] LL_EPSILON = 1e-11 -WGS84_CRS = pyproj.CRS.from_epsg(4326) axesInfo = Annotated[List[str], Field(min_length=2, max_length=2)] diff --git a/tests/test_models.py b/tests/test_models.py index a62078b..d2afb31 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -395,6 +395,35 @@ def test_mars_local_tms(): assert bbox == syrtis_tms.bbox +def test_mars_tms_construction(): + mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") + extent = [-90, -180, 90, 180] + mars_tms = morecantile.TileMatrixSet.custom( + extent, mars_sphere_crs, id="MarsGeographicCRS" + ) + assert "4326" not in mars_tms.geographic_crs.to_wkt() + assert "4326" not in mars_tms.rasterio_geographic_crs.to_wkt() + assert mars_tms.xy_bbox.left == pytest.approx(-90.0) + assert mars_tms.xy_bbox.right == pytest.approx(180.0) + assert mars_tms.xy_bbox.bottom == pytest.approx(180.0) + assert mars_tms.xy_bbox.top == pytest.approx(90.0) + + +def test_mars_web_mercator_long_lat(): + wkt_mars_web_mercator = 'PROJCRS["Mars (2015) - Sphere XY / Pseudo-Mercator",BASEGEOGCRS["Mars (2015) - Sphere",DATUM["Mars (2015) - Sphere",ELLIPSOID["Mars (2015) - Sphere",3396190,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]],ANCHOR["Viking 1 lander : 47.95137 W"]],PRIMEM["Reference Meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06 S and 85.06 N."],BBOX[-85.850511287,-180,85.0511287,180]],REMARK["Use semi-major radius as sphere radius for interoperability. Source of IAU Coordinate systems: doi:10.1007/s10569-017-9805-5"]]' + crs_mars_web_mercator = pyproj.CRS.from_wkt(wkt_mars_web_mercator) + extent_wm = [-85.850511287, -180, 85.0511287, 180] + mars_tms_wm = morecantile.TileMatrixSet.custom( + extent_wm, crs_mars_web_mercator, id="MarsWebMercator" + ) + assert "4326" not in mars_tms_wm.geographic_crs.to_wkt() + assert "4326" not in mars_tms_wm.rasterio_geographic_crs.to_wkt() + assert mars_tms_wm.bbox.left == pytest.approx(-180.0) + assert mars_tms_wm.bbox.right == pytest.approx(180.0) + assert mars_tms_wm.bbox.bottom == pytest.approx(-85.850511287) + assert mars_tms_wm.bbox.top == pytest.approx(85.850511287) + + @pytest.mark.parametrize( "identifier, file, crs", [ From 5bf1e3b31ab55a2bfb0ce19de5ae15d313bad5f1 Mon Sep 17 00:00:00 2001 From: "Dr. Andrew Annex" Date: Tue, 15 Oct 2024 15:13:35 -0700 Subject: [PATCH 05/19] Update tests/test_models.py Co-authored-by: Vincent Sarago --- tests/test_models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_models.py b/tests/test_models.py index d2afb31..a2e70d1 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -399,7 +399,10 @@ def test_mars_tms_construction(): mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") extent = [-90, -180, 90, 180] mars_tms = morecantile.TileMatrixSet.custom( - extent, mars_sphere_crs, id="MarsGeographicCRS" + extent, + mars_sphere_crs, + id="MarsGeographicCRS", + matrix_scale=[1, 2], ) assert "4326" not in mars_tms.geographic_crs.to_wkt() assert "4326" not in mars_tms.rasterio_geographic_crs.to_wkt() From e178037adeb04cd4aa5f0c04bc8dfdec6f365adb Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 15:32:51 -0700 Subject: [PATCH 06/19] being more specific with the extent_crs's --- tests/test_models.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index a2e70d1..5ff8578 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -399,9 +399,10 @@ def test_mars_tms_construction(): mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") extent = [-90, -180, 90, 180] mars_tms = morecantile.TileMatrixSet.custom( - extent, - mars_sphere_crs, - id="MarsGeographicCRS", + extent, + extent_crs=mars_sphere_crs, + crs=mars_sphere_crs, + id="MarsGeographicCRS", matrix_scale=[1, 2], ) assert "4326" not in mars_tms.geographic_crs.to_wkt() @@ -413,11 +414,15 @@ def test_mars_tms_construction(): def test_mars_web_mercator_long_lat(): + mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") wkt_mars_web_mercator = 'PROJCRS["Mars (2015) - Sphere XY / Pseudo-Mercator",BASEGEOGCRS["Mars (2015) - Sphere",DATUM["Mars (2015) - Sphere",ELLIPSOID["Mars (2015) - Sphere",3396190,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]],ANCHOR["Viking 1 lander : 47.95137 W"]],PRIMEM["Reference Meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06 S and 85.06 N."],BBOX[-85.850511287,-180,85.0511287,180]],REMARK["Use semi-major radius as sphere radius for interoperability. Source of IAU Coordinate systems: doi:10.1007/s10569-017-9805-5"]]' crs_mars_web_mercator = pyproj.CRS.from_wkt(wkt_mars_web_mercator) - extent_wm = [-85.850511287, -180, 85.0511287, 180] + extent_wm = [-85.850511287, -180, 85.850511287, 180] mars_tms_wm = morecantile.TileMatrixSet.custom( - extent_wm, crs_mars_web_mercator, id="MarsWebMercator" + extent_wm, + extent_crs=mars_sphere_crs, + crs=crs_mars_web_mercator, + id="MarsWebMercator", ) assert "4326" not in mars_tms_wm.geographic_crs.to_wkt() assert "4326" not in mars_tms_wm.rasterio_geographic_crs.to_wkt() From 56ad6fcd98778fdb5a04766475668455926a3b59 Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 15:40:19 -0700 Subject: [PATCH 07/19] fix for bone-headed issues with basic geographic mars test --- tests/test_models.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index 5ff8578..be41dc1 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -397,19 +397,18 @@ def test_mars_local_tms(): def test_mars_tms_construction(): mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") - extent = [-90, -180, 90, 180] + extent = [-180.0, -90.0, 180.0, 90.0] mars_tms = morecantile.TileMatrixSet.custom( extent, - extent_crs=mars_sphere_crs, crs=mars_sphere_crs, id="MarsGeographicCRS", - matrix_scale=[1, 2], + matrix_scale=[2, 1], ) assert "4326" not in mars_tms.geographic_crs.to_wkt() assert "4326" not in mars_tms.rasterio_geographic_crs.to_wkt() - assert mars_tms.xy_bbox.left == pytest.approx(-90.0) + assert mars_tms.xy_bbox.left == pytest.approx(-180.0) + assert mars_tms.xy_bbox.bottom == pytest.approx(-90.0) assert mars_tms.xy_bbox.right == pytest.approx(180.0) - assert mars_tms.xy_bbox.bottom == pytest.approx(180.0) assert mars_tms.xy_bbox.top == pytest.approx(90.0) @@ -417,7 +416,7 @@ def test_mars_web_mercator_long_lat(): mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") wkt_mars_web_mercator = 'PROJCRS["Mars (2015) - Sphere XY / Pseudo-Mercator",BASEGEOGCRS["Mars (2015) - Sphere",DATUM["Mars (2015) - Sphere",ELLIPSOID["Mars (2015) - Sphere",3396190,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]],ANCHOR["Viking 1 lander : 47.95137 W"]],PRIMEM["Reference Meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06 S and 85.06 N."],BBOX[-85.850511287,-180,85.0511287,180]],REMARK["Use semi-major radius as sphere radius for interoperability. Source of IAU Coordinate systems: doi:10.1007/s10569-017-9805-5"]]' crs_mars_web_mercator = pyproj.CRS.from_wkt(wkt_mars_web_mercator) - extent_wm = [-85.850511287, -180, 85.850511287, 180] + extent_wm = [-180.0, -85.850511287, 180.0, 85.850511287] mars_tms_wm = morecantile.TileMatrixSet.custom( extent_wm, extent_crs=mars_sphere_crs, @@ -427,8 +426,8 @@ def test_mars_web_mercator_long_lat(): assert "4326" not in mars_tms_wm.geographic_crs.to_wkt() assert "4326" not in mars_tms_wm.rasterio_geographic_crs.to_wkt() assert mars_tms_wm.bbox.left == pytest.approx(-180.0) - assert mars_tms_wm.bbox.right == pytest.approx(180.0) assert mars_tms_wm.bbox.bottom == pytest.approx(-85.850511287) + assert mars_tms_wm.bbox.right == pytest.approx(180.0) assert mars_tms_wm.bbox.top == pytest.approx(85.850511287) From 44b914e90725498740283ef3e3af658d50500432 Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 15:54:55 -0700 Subject: [PATCH 08/19] fix for typo in the wkt for mars web mercator --- tests/test_models.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_models.py b/tests/test_models.py index be41dc1..a3fc05d 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -413,22 +413,26 @@ def test_mars_tms_construction(): def test_mars_web_mercator_long_lat(): - mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") - wkt_mars_web_mercator = 'PROJCRS["Mars (2015) - Sphere XY / Pseudo-Mercator",BASEGEOGCRS["Mars (2015) - Sphere",DATUM["Mars (2015) - Sphere",ELLIPSOID["Mars (2015) - Sphere",3396190,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]],ANCHOR["Viking 1 lander : 47.95137 W"]],PRIMEM["Reference Meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06 S and 85.06 N."],BBOX[-85.850511287,-180,85.0511287,180]],REMARK["Use semi-major radius as sphere radius for interoperability. Source of IAU Coordinate systems: doi:10.1007/s10569-017-9805-5"]]' + wkt_mars_web_mercator = 'PROJCRS["Mars (2015) - Sphere XY / Pseudo-Mercator",BASEGEOGCRS["Mars (2015) - Sphere",DATUM["Mars (2015) - Sphere",ELLIPSOID["Mars (2015) - Sphere",3396190,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]],ANCHOR["Viking 1 lander : 47.95137 W"]],PRIMEM["Reference Meridian",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9122]]]],CONVERSION["Popular Visualisation Pseudo-Mercator",METHOD["Popular Visualisation Pseudo Mercator",ID["EPSG",1024]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]],USAGE[SCOPE["Web mapping and visualisation."],AREA["World between 85.06 S and 85.06 N."],BBOX[-85.050511287,-180,85.050511287,180]],REMARK["Use semi-major radius as sphere radius for interoperability. Source of IAU Coordinate systems: doi:10.1007/s10569-017-9805-5"]]' crs_mars_web_mercator = pyproj.CRS.from_wkt(wkt_mars_web_mercator) - extent_wm = [-180.0, -85.850511287, 180.0, 85.850511287] + extent_wm = [ + -10669445.554195119, + -10669445.554195119, + 10669445.554195119, + 10669445.554195119, + ] mars_tms_wm = morecantile.TileMatrixSet.custom( extent_wm, - extent_crs=mars_sphere_crs, + # extent_crs=mars_sphere_crs, crs=crs_mars_web_mercator, id="MarsWebMercator", ) assert "4326" not in mars_tms_wm.geographic_crs.to_wkt() assert "4326" not in mars_tms_wm.rasterio_geographic_crs.to_wkt() assert mars_tms_wm.bbox.left == pytest.approx(-180.0) - assert mars_tms_wm.bbox.bottom == pytest.approx(-85.850511287) + assert mars_tms_wm.bbox.bottom == pytest.approx(-85.0511287) assert mars_tms_wm.bbox.right == pytest.approx(180.0) - assert mars_tms_wm.bbox.top == pytest.approx(85.850511287) + assert mars_tms_wm.bbox.top == pytest.approx(85.0511287) @pytest.mark.parametrize( From d5d2aa0aaefe788cd7c2204d850283a75c7b4a27 Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 15:56:18 -0700 Subject: [PATCH 09/19] removed commented out line --- tests/test_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_models.py b/tests/test_models.py index a3fc05d..bd53e4f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -423,7 +423,6 @@ def test_mars_web_mercator_long_lat(): ] mars_tms_wm = morecantile.TileMatrixSet.custom( extent_wm, - # extent_crs=mars_sphere_crs, crs=crs_mars_web_mercator, id="MarsWebMercator", ) From 003ec17fffe758d98b4ee45539928ed478ca561d Mon Sep 17 00:00:00 2001 From: Andrew Annex Date: Tue, 15 Oct 2024 16:01:04 -0700 Subject: [PATCH 10/19] another sanity check --- tests/test_models.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index bd53e4f..c1a84ca 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -432,6 +432,23 @@ def test_mars_web_mercator_long_lat(): assert mars_tms_wm.bbox.bottom == pytest.approx(-85.0511287) assert mars_tms_wm.bbox.right == pytest.approx(180.0) assert mars_tms_wm.bbox.top == pytest.approx(85.0511287) + extent_wm_geog = [ + -179.9999999999996, + -85.05112877980656, + 179.9999999999996, + 85.05112877980656, + ] + mars_sphere_crs = pyproj.CRS.from_user_input("IAU_2015:49900") + mars_tms_wm_geog_ext = morecantile.TileMatrixSet.custom( + extent_wm_geog, + extent_crs=mars_sphere_crs, + crs=crs_mars_web_mercator, + id="MarsWebMercator", + ) + assert mars_tms_wm_geog_ext.bbox.left == pytest.approx(-180.0) + assert mars_tms_wm_geog_ext.bbox.bottom == pytest.approx(-85.0511287) + assert mars_tms_wm_geog_ext.bbox.right == pytest.approx(180.0) + assert mars_tms_wm_geog_ext.bbox.top == pytest.approx(85.0511287) @pytest.mark.parametrize( From 3874856f522fd66a98c296b6bfabf93490c64ed7 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 16 Oct 2024 10:11:13 +0200 Subject: [PATCH 11/19] update changelog and docs --- CHANGES.md | 2 +- docs/src/usage.md | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5d9c790..fcb0d60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,5 @@ -## 6.0.0 (TBD) +## 6.0.0 (2024-10-16) * remove `_geographic_crs` private attribute in `TileMatrixSet` model **breaking change** * use `crs.geodetic_crs` property as `geographic_crs` **breaking change** diff --git a/docs/src/usage.md b/docs/src/usage.md index d43f6c5..496aea5 100644 --- a/docs/src/usage.md +++ b/docs/src/usage.md @@ -103,8 +103,6 @@ Here are the available options: - **id** (*str, defaults to `Custom`*): Tile Matrix Set identifier -- **geographic_crs** (*pyproj.CRS, defaults to `EPSG:4326`*): Geographic (lat,lon) coordinate reference system - - **ordered_axes** (*list of str, Optional*): Override Axis order (e.g `["N", "S"]`) else default to CRS's metadata - **screen_pixel_size** (*float, optional*): Rendering pixel size. `0.28` mm was the actual pixel size of a common display from 2005 and considered as standard by OGC. From 891cf234090aab4986ebd543852a4493e8517dce Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 16 Oct 2024 10:44:27 +0200 Subject: [PATCH 12/19] =?UTF-8?q?Bump=20version:=205.4.2=20=E2=86=92=206.0?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- morecantile/__init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/morecantile/__init__.py b/morecantile/__init__.py index 3c96b86..b08f7a0 100644 --- a/morecantile/__init__.py +++ b/morecantile/__init__.py @@ -8,7 +8,7 @@ """ -__version__ = "5.4.2" +__version__ = "6.0.0" from .commons import BoundingBox, Coords, Tile # noqa from .defaults import TileMatrixSets, tms # noqa diff --git a/pyproject.toml b/pyproject.toml index 91cdb20..d004ae9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ filterwarnings = [ ] [tool.bumpversion] -current_version = "5.4.2" +current_version = "6.0.0" search = "{current_version}" replace = "{new_version}" From c98b77df806a9183e2306dd6a6506a462d65ce85 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 16 Oct 2024 17:47:01 +0200 Subject: [PATCH 13/19] add matrix lookup dict and remove duplicated call to parse_tile_arg --- CHANGES.md | 5 ++++ morecantile/models.py | 55 ++++++++++++++++++--------------------- morecantile/utils.py | 1 + tests/test_models.py | 2 +- tests/test_morecantile.py | 15 +++++------ 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fcb0d60..69a84af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,9 @@ +## 7.0.0 (TBD) + +* add `_tile_matrices_idx: Dict[str, int]` private attribute to improve `matrices` lookup +* change `_ul`, `ul`, `_lr` and `lr` method to only accept `morecantile.Tile` object **breaking change** + ## 6.0.0 (2024-10-16) * remove `_geographic_crs` private attribute in `TileMatrixSet` model **breaking change** diff --git a/morecantile/models.py b/morecantile/models.py index e5052b0..d9cd26f 100644 --- a/morecantile/models.py +++ b/morecantile/models.py @@ -487,11 +487,16 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True): # Private attributes _to_geographic: pyproj.Transformer = PrivateAttr() _from_geographic: pyproj.Transformer = PrivateAttr() + _tile_matrices_idx: Dict[str, int] = PrivateAttr() def __init__(self, **data): """Set private attributes.""" super().__init__(**data) + self._tile_matrices_idx = { + mat.id: idx for idx, mat in enumerate(self.tileMatrices) + } + try: self._to_geographic = pyproj.Transformer.from_crs( self.crs._pyproj_crs, self.crs._pyproj_crs.geodetic_crs, always_xy=True @@ -765,9 +770,8 @@ def custom( def matrix(self, zoom: int) -> TileMatrix: """Return the TileMatrix for a specific zoom.""" - for m in self.tileMatrices: - if m.id == str(zoom): - return m + if (idx := self._tile_matrices_idx.get(str(zoom), None)) is not None: + return self.tileMatrices[idx] ####################################################################### # If user wants a deeper matrix we calculate it @@ -1021,61 +1025,58 @@ def tile( x, y = self.xy(lng, lat, truncate=truncate) return self._tile(x, y, zoom, ignore_coalescence=ignore_coalescence) - def _ul(self, *tile: Tile) -> Coords: + def _ul(self, tile: Tile) -> Coords: """ Return the upper left coordinate of the tile in TMS coordinate reference system. Attributes ---------- - tile: (x, y, z) tile coordinates or a Tile object we want the upper left coordinates of. + tile: Tile object we want the upper left coordinates of. Returns ------- Coords: The upper left coordinates of the input tile. """ - t = _parse_tile_arg(*tile) - - matrix = self.matrix(t.z) + matrix = self.matrix(tile.z) origin_x, origin_y = self._matrix_origin(matrix) cf = ( - matrix.get_coalesce_factor(t.y) + matrix.get_coalesce_factor(tile.y) if matrix.variableMatrixWidths is not None else 1 ) return Coords( - origin_x + math.floor(t.x / cf) * matrix.cellSize * cf * matrix.tileWidth, - origin_y - t.y * matrix.cellSize * matrix.tileHeight, + origin_x + + math.floor(tile.x / cf) * matrix.cellSize * cf * matrix.tileWidth, + origin_y - tile.y * matrix.cellSize * matrix.tileHeight, ) - def _lr(self, *tile: Tile) -> Coords: + def _lr(self, tile: Tile) -> Coords: """ Return the lower right coordinate of the tile in TMS coordinate reference system. Attributes ---------- - tile: (x, y, z) tile coordinates or a Tile object we want the lower right coordinates of. + tile: Tile object we want the lower right coordinates of. Returns ------- Coords: The lower right coordinates of the input tile. """ - t = _parse_tile_arg(*tile) - - matrix = self.matrix(t.z) + matrix = self.matrix(tile.z) origin_x, origin_y = self._matrix_origin(matrix) cf = ( - matrix.get_coalesce_factor(t.y) + matrix.get_coalesce_factor(tile.y) if matrix.variableMatrixWidths is not None else 1 ) return Coords( origin_x - + (math.floor(t.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth, - origin_y - (t.y + 1) * matrix.cellSize * matrix.tileHeight, + + (math.floor(tile.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth, + origin_y - (tile.y + 1) * matrix.cellSize * matrix.tileHeight, ) def xy_bounds(self, *tile: Tile) -> BoundingBox: @@ -1097,40 +1098,36 @@ def xy_bounds(self, *tile: Tile) -> BoundingBox: right, bottom = self._lr(t) return BoundingBox(left, bottom, right, top) - def ul(self, *tile: Tile) -> Coords: + def ul(self, tile: Tile) -> Coords: """ Return the upper left coordinates of the tile in geographic coordinate reference system. Attributes ---------- - tile (tuple or Tile): (x, y, z) tile coordinates or a Tile object we want the upper left geographic coordinates of. + tile (Tile): Tile object we want the upper left geographic coordinates of. Returns ------- Coords: The upper left geographic coordinates of the input tile. """ - t = _parse_tile_arg(*tile) - - x, y = self._ul(t) + x, y = self._ul(tile) return Coords(*self.lnglat(x, y)) - def lr(self, *tile: Tile) -> Coords: + def lr(self, tile: Tile) -> Coords: """ Return the lower right coordinates of the tile in geographic coordinate reference system. Attributes ---------- - tile (tuple or Tile): (x, y, z) tile coordinates or a Tile object we want the lower right geographic coordinates of. + tile (Tile): Tile object we want the lower right geographic coordinates of. Returns ------- Coords: The lower right geographic coordinates of the input tile. """ - t = _parse_tile_arg(*tile) - - x, y = self._lr(t) + x, y = self._lr(tile) return Coords(*self.lnglat(x, y)) def bounds(self, *tile: Tile) -> BoundingBox: diff --git a/morecantile/utils.py b/morecantile/utils.py index 5da80cb..da98fe3 100644 --- a/morecantile/utils.py +++ b/morecantile/utils.py @@ -32,6 +32,7 @@ def _parse_tile_arg(*args) -> Tile: """ if len(args) == 1: args = args[0] + if len(args) == 3: return Tile(*args) else: diff --git a/tests/test_models.py b/tests/test_models.py index c1a84ca..9e51da9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -384,7 +384,7 @@ def test_mars_local_tms(): assert syrtis_tms.geographic_crs assert syrtis_tms.model_dump(mode="json") - center = syrtis_tms.ul(1, 1, 1) + center = syrtis_tms.ul(Tile(1, 1, 1)) assert round(center.x, 6) == 76.5 assert round(center.y, 6) == 17 diff --git a/tests/test_morecantile.py b/tests/test_morecantile.py index c49f8cc..d42f0e5 100644 --- a/tests/test_morecantile.py +++ b/tests/test_morecantile.py @@ -133,7 +133,7 @@ def test_ul_tile(): test form https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py """ tms = morecantile.tms.get("WebMercatorQuad") - xy = tms.ul(486, 332, 10) + xy = tms.ul(morecantile.Tile(486, 332, 10)) expected = (-9.140625, 53.33087298301705) for a, b in zip(expected, xy): assert round(a - b, 6) == 0 @@ -146,7 +146,7 @@ def test_projul_tile(): test form https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py """ tms = morecantile.tms.get("WebMercatorQuad") - xy = tms._ul(486, 332, 10) + xy = tms._ul(morecantile.Tile(486, 332, 10)) expected = (-1017529.7205322663, 7044436.526761846) for a, b in zip(expected, xy): assert round(a - b, 6) == 0 @@ -191,15 +191,12 @@ def test_feature(): ################################################################################ # replicate mercantile tests -# https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py -@pytest.mark.parametrize( - "args", [(486, 332, 10), [(486, 332, 10)], [morecantile.Tile(486, 332, 10)]] -) -def test_ul(args): +def test_ul(): """test args.""" + tile = morecantile.Tile(486, 332, 10) tms = morecantile.tms.get("WebMercatorQuad") expected = (-9.140625, 53.33087298301705) - lnglat = tms.ul(*args) + lnglat = tms.ul(tile) for a, b in zip(expected, lnglat): assert round(a - b, 6) == 0 assert lnglat[0] == lnglat.x @@ -225,7 +222,7 @@ def test_bbox(args): def test_xy_tile(): """x, y for the 486-332-10 tile is correctly calculated.""" tms = morecantile.tms.get("WebMercatorQuad") - ul = tms.ul(486, 332, 10) + ul = tms.ul(morecantile.Tile(486, 332, 10)) xy = tms.xy(*ul) expected = (-1017529.7205322663, 7044436.526761846) for a, b in zip(expected, xy): From 3a8bed8f610f8c5bcfb732bdbe86014046484a94 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Wed, 16 Oct 2024 23:50:48 +0200 Subject: [PATCH 14/19] revert and update xy_bounds and bounds methods --- morecantile/models.py | 77 +++++++++++++++++++++++++-------------- morecantile/utils.py | 1 - tests/test_models.py | 2 +- tests/test_morecantile.py | 15 +++++--- 4 files changed, 60 insertions(+), 35 deletions(-) diff --git a/morecantile/models.py b/morecantile/models.py index d9cd26f..84bcd08 100644 --- a/morecantile/models.py +++ b/morecantile/models.py @@ -487,14 +487,15 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True): # Private attributes _to_geographic: pyproj.Transformer = PrivateAttr() _from_geographic: pyproj.Transformer = PrivateAttr() - _tile_matrices_idx: Dict[str, int] = PrivateAttr() + + _tile_matrices_idx: Dict[int, int] = PrivateAttr() def __init__(self, **data): """Set private attributes.""" super().__init__(**data) self._tile_matrices_idx = { - mat.id: idx for idx, mat in enumerate(self.tileMatrices) + int(mat.id): idx for idx, mat in enumerate(self.tileMatrices) } try: @@ -770,7 +771,7 @@ def custom( def matrix(self, zoom: int) -> TileMatrix: """Return the TileMatrix for a specific zoom.""" - if (idx := self._tile_matrices_idx.get(str(zoom), None)) is not None: + if (idx := self._tile_matrices_idx.get(zoom, None)) is not None: return self.tileMatrices[idx] ####################################################################### @@ -1025,58 +1026,61 @@ def tile( x, y = self.xy(lng, lat, truncate=truncate) return self._tile(x, y, zoom, ignore_coalescence=ignore_coalescence) - def _ul(self, tile: Tile) -> Coords: + def _ul(self, *tile: Tile) -> Coords: """ Return the upper left coordinate of the tile in TMS coordinate reference system. Attributes ---------- - tile: Tile object we want the upper left coordinates of. + tile: (x, y, z) tile coordinates or a Tile object we want the upper left coordinates of. Returns ------- Coords: The upper left coordinates of the input tile. """ - matrix = self.matrix(tile.z) + t = _parse_tile_arg(*tile) + + matrix = self.matrix(t.z) origin_x, origin_y = self._matrix_origin(matrix) cf = ( - matrix.get_coalesce_factor(tile.y) + matrix.get_coalesce_factor(t.y) if matrix.variableMatrixWidths is not None else 1 ) return Coords( - origin_x - + math.floor(tile.x / cf) * matrix.cellSize * cf * matrix.tileWidth, - origin_y - tile.y * matrix.cellSize * matrix.tileHeight, + origin_x + math.floor(t.x / cf) * matrix.cellSize * cf * matrix.tileWidth, + origin_y - t.y * matrix.cellSize * matrix.tileHeight, ) - def _lr(self, tile: Tile) -> Coords: + def _lr(self, *tile: Tile) -> Coords: """ Return the lower right coordinate of the tile in TMS coordinate reference system. Attributes ---------- - tile: Tile object we want the lower right coordinates of. + tile: (x, y, z) tile coordinates or a Tile object we want the lower right coordinates of. Returns ------- Coords: The lower right coordinates of the input tile. """ - matrix = self.matrix(tile.z) + t = _parse_tile_arg(*tile) + + matrix = self.matrix(t.z) origin_x, origin_y = self._matrix_origin(matrix) cf = ( - matrix.get_coalesce_factor(tile.y) + matrix.get_coalesce_factor(t.y) if matrix.variableMatrixWidths is not None else 1 ) return Coords( origin_x - + (math.floor(tile.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth, - origin_y - (tile.y + 1) * matrix.cellSize * matrix.tileHeight, + + (math.floor(t.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth, + origin_y - (t.y + 1) * matrix.cellSize * matrix.tileHeight, ) def xy_bounds(self, *tile: Tile) -> BoundingBox: @@ -1094,40 +1098,59 @@ def xy_bounds(self, *tile: Tile) -> BoundingBox: """ t = _parse_tile_arg(*tile) - left, top = self._ul(t) - right, bottom = self._lr(t) + matrix = self.matrix(t.z) + origin_x, origin_y = self._matrix_origin(matrix) + + cf = ( + matrix.get_coalesce_factor(t.y) + if matrix.variableMatrixWidths is not None + else 1 + ) + + left = origin_x + math.floor(t.x / cf) * matrix.cellSize * cf * matrix.tileWidth + top = origin_y - t.y * matrix.cellSize * matrix.tileHeight + right = ( + origin_x + + (math.floor(t.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth + ) + bottom = origin_y - (t.y + 1) * matrix.cellSize * matrix.tileHeight + return BoundingBox(left, bottom, right, top) - def ul(self, tile: Tile) -> Coords: + def ul(self, *tile: Tile) -> Coords: """ Return the upper left coordinates of the tile in geographic coordinate reference system. Attributes ---------- - tile (Tile): Tile object we want the upper left geographic coordinates of. + tile (tuple or Tile): (x, y, z) tile coordinates or a Tile object we want the upper left geographic coordinates of. Returns ------- Coords: The upper left geographic coordinates of the input tile. """ - x, y = self._ul(tile) + t = _parse_tile_arg(*tile) + + x, y = self._ul(t) return Coords(*self.lnglat(x, y)) - def lr(self, tile: Tile) -> Coords: + def lr(self, *tile: Tile) -> Coords: """ Return the lower right coordinates of the tile in geographic coordinate reference system. Attributes ---------- - tile (Tile): Tile object we want the lower right geographic coordinates of. + tile (tuple or Tile): (x, y, z) tile coordinates or a Tile object we want the lower right geographic coordinates of. Returns ------- Coords: The lower right geographic coordinates of the input tile. """ - x, y = self._lr(tile) + t = _parse_tile_arg(*tile) + + x, y = self._lr(t) return Coords(*self.lnglat(x, y)) def bounds(self, *tile: Tile) -> BoundingBox: @@ -1143,10 +1166,10 @@ def bounds(self, *tile: Tile) -> BoundingBox: BoundingBox: The bounding box of the input tile. """ - t = _parse_tile_arg(*tile) + _left, _bottom, _right, _top = self.xy_bounds(*tile) + left, top = self.lnglat(_left, _top) + right, bottom = self.lnglat(_right, _bottom) - left, top = self.ul(t) - right, bottom = self.lr(t) return BoundingBox(left, bottom, right, top) @property diff --git a/morecantile/utils.py b/morecantile/utils.py index da98fe3..5da80cb 100644 --- a/morecantile/utils.py +++ b/morecantile/utils.py @@ -32,7 +32,6 @@ def _parse_tile_arg(*args) -> Tile: """ if len(args) == 1: args = args[0] - if len(args) == 3: return Tile(*args) else: diff --git a/tests/test_models.py b/tests/test_models.py index 9e51da9..c1a84ca 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -384,7 +384,7 @@ def test_mars_local_tms(): assert syrtis_tms.geographic_crs assert syrtis_tms.model_dump(mode="json") - center = syrtis_tms.ul(Tile(1, 1, 1)) + center = syrtis_tms.ul(1, 1, 1) assert round(center.x, 6) == 76.5 assert round(center.y, 6) == 17 diff --git a/tests/test_morecantile.py b/tests/test_morecantile.py index d42f0e5..c49f8cc 100644 --- a/tests/test_morecantile.py +++ b/tests/test_morecantile.py @@ -133,7 +133,7 @@ def test_ul_tile(): test form https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py """ tms = morecantile.tms.get("WebMercatorQuad") - xy = tms.ul(morecantile.Tile(486, 332, 10)) + xy = tms.ul(486, 332, 10) expected = (-9.140625, 53.33087298301705) for a, b in zip(expected, xy): assert round(a - b, 6) == 0 @@ -146,7 +146,7 @@ def test_projul_tile(): test form https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py """ tms = morecantile.tms.get("WebMercatorQuad") - xy = tms._ul(morecantile.Tile(486, 332, 10)) + xy = tms._ul(486, 332, 10) expected = (-1017529.7205322663, 7044436.526761846) for a, b in zip(expected, xy): assert round(a - b, 6) == 0 @@ -191,12 +191,15 @@ def test_feature(): ################################################################################ # replicate mercantile tests -def test_ul(): +# https://github.com/mapbox/mercantile/blob/master/tests/test_funcs.py +@pytest.mark.parametrize( + "args", [(486, 332, 10), [(486, 332, 10)], [morecantile.Tile(486, 332, 10)]] +) +def test_ul(args): """test args.""" - tile = morecantile.Tile(486, 332, 10) tms = morecantile.tms.get("WebMercatorQuad") expected = (-9.140625, 53.33087298301705) - lnglat = tms.ul(tile) + lnglat = tms.ul(*args) for a, b in zip(expected, lnglat): assert round(a - b, 6) == 0 assert lnglat[0] == lnglat.x @@ -222,7 +225,7 @@ def test_bbox(args): def test_xy_tile(): """x, y for the 486-332-10 tile is correctly calculated.""" tms = morecantile.tms.get("WebMercatorQuad") - ul = tms.ul(morecantile.Tile(486, 332, 10)) + ul = tms.ul(486, 332, 10) xy = tms.xy(*ul) expected = (-1017529.7205322663, 7044436.526761846) for a, b in zip(expected, xy): From b3d55ff1f18ea291f3af76a62789ab633afe4ec3 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 17 Oct 2024 22:42:28 +0200 Subject: [PATCH 15/19] add benchmark --- .github/workflows/ci.yml | 35 ++++++++++++++ CONTRIBUTING.md | 7 +++ benchmarks/README.md | 63 ++++++++++++++++++++++++ benchmarks/benchmarks.py | 96 +++++++++++++++++++++++++++++++++++++ benchmarks/requirements.txt | 6 +++ pyproject.toml | 4 ++ tests/benchmarks.py | 43 +++++++++++++++++ 7 files changed, 254 insertions(+) create mode 100644 benchmarks/README.md create mode 100644 benchmarks/benchmarks.py create mode 100644 benchmarks/requirements.txt create mode 100644 tests/benchmarks.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce69c82..a368421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,41 @@ jobs: name: Python${{ matrix.python-version }} fail_ci_if_error: false + benchmark: + needs: [tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[benchmark]" + + - name: Run Benchmark + run: | + python -m pytest tests/benchmarks.py --benchmark-only --benchmark-columns 'min, max, mean, median' --benchmark-sort 'min' --benchmark-json output.json + + - name: Store and Compare benchmark result + uses: benchmark-action/github-action-benchmark@v1 + with: + name: morecantile Benchmarks + tool: 'pytest' + output-file-path: output.json + alert-threshold: '150%' + comment-on-alert: true + fail-on-alert: false + # GitHub API token to make a commit comment + github-token: ${{ secrets.GITHUB_TOKEN }} + gh-pages-branch: 'gh-benchmarks' + # Make a commit on `gh-pages` only if main + auto-push: ${{ github.ref == 'refs/heads/main' }} + benchmark-data-dir-path: dev/benchmarks + publish: needs: [tests] runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ea8ce0..d443410 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,13 @@ This repo is set to use `pre-commit` to run *isort*, *flake8*, *pydocstring*, *b $ pre-commit install ``` +##### Performance tests + +```sh +python -m pip install -e ".[benchmark]" +python -m pytest tests/benchmarks.py --benchmark-only --benchmark-columns 'min, max, mean, median' --benchmark-sort 'min' +``` + ### Docs ```bash diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000..a2d4f66 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,63 @@ + +## Benchmark + +Compare `mercantile`, `utiles` and `morecantile` + +```sh +python -m pip install -r requirements.txt +python -m pytest benchmarks.py --benchmark-only --benchmark-columns 'min, max, mean, median' --benchmark-sort 'min' +``` + +``` +----------------------------------------------------- benchmark 'bounds': 21 tests ---------------------------------------------------- +Name (time in ns) Min Max Mean Median +--------------------------------------------------------------------------------------------------------------------------------------- +test_bounds[utiles-(486, 332, 30)] 83.0000 (1.0) 52,000.0000 (52.97) 195.3371 (1.48) 208.0000 (1.60) +test_bounds[utiles-(0, 0, 0)] 83.0000 (1.00) 31,167.0000 (31.75) 193.5957 (1.47) 208.0000 (1.60) +test_bounds[utiles-(1, 0, 1)] 125.0000 (1.51) 981.6700 (1.0) 132.0177 (1.0) 130.8300 (1.01) +test_bounds[utiles-(1, 40, 7)] 125.0000 (1.51) 8,606.2500 (8.77) 132.5298 (1.00) 130.0000 (1.0) +test_bounds[utiles-(486, 332, 10)] 128.3300 (1.55) 1,600.8300 (1.63) 133.4046 (1.01) 131.6700 (1.01) +test_bounds[utiles-(1, 1, 1)] 129.5900 (1.56) 16,846.2500 (17.16) 141.9846 (1.08) 134.1700 (1.03) +test_bounds[utiles-(486, 332, 20)] 138.3300 (1.67) 1,123.3300 (1.14) 142.5420 (1.08) 141.2500 (1.09) +test_bounds[mercantile-(1, 40, 7)] 1,000.0000 (12.05) 92,416.0000 (94.14) 1,193.0112 (9.04) 1,167.0000 (8.98) +test_bounds[mercantile-(1, 1, 1)] 1,075.0000 (12.95) 48,066.8000 (48.96) 1,154.8100 (8.75) 1,141.6000 (8.78) +test_bounds[mercantile-(0, 0, 0)] 1,083.0000 (13.05) 21,041.0000 (21.43) 1,214.6049 (9.20) 1,208.0000 (9.29) +test_bounds[mercantile-(1, 0, 1)] 1,083.0000 (13.05) 75,291.0000 (76.70) 1,213.7837 (9.19) 1,208.0000 (9.29) +test_bounds[mercantile-(486, 332, 10)] 1,083.0000 (13.05) 55,750.0000 (56.79) 1,223.7598 (9.27) 1,208.0000 (9.29) +test_bounds[mercantile-(486, 332, 20)] 1,083.0000 (13.05) 89,041.0000 (90.70) 1,248.4256 (9.46) 1,209.0000 (9.30) +test_bounds[mercantile-(486, 332, 30)] 1,083.0000 (13.05) 194,958.0000 (198.60) 1,239.4339 (9.39) 1,209.0000 (9.30) +test_bounds[morecantile-(1, 0, 1)] 20,083.0000 (241.96) 132,625.0000 (135.10) 20,893.1235 (158.26) 20,667.0000 (158.98) +test_bounds[morecantile-(0, 0, 0)] 20,166.0000 (242.96) 120,333.0000 (122.58) 20,974.1961 (158.87) 20,708.0000 (159.29) +test_bounds[morecantile-(1, 1, 1)] 20,334.0000 (244.99) 60,958.0000 (62.10) 21,236.2953 (160.86) 20,750.0000 (159.62) +test_bounds[morecantile-(1, 40, 7)] 21,667.0000 (261.05) 173,458.0000 (176.70) 22,622.6537 (171.36) 22,333.0000 (171.79) +test_bounds[morecantile-(486, 332, 10)] 22,166.0000 (267.06) 205,125.0000 (208.96) 23,512.1607 (178.10) 22,875.0000 (175.96) +test_bounds[morecantile-(486, 332, 20)] 24,708.0000 (297.69) 129,250.0000 (131.66) 25,775.0796 (195.24) 25,375.0000 (195.19) +test_bounds[morecantile-(486, 332, 30)] 71,292.0000 (858.94) 205,625.0000 (209.46) 73,569.4565 (557.27) 72,667.0000 (558.98) +--------------------------------------------------------------------------------------------------------------------------------------- + +---------------------------------------------------- benchmark 'xy_bounds': 21 tests ----------------------------------------------------- +Name (time in ns) Min Max Mean Median +------------------------------------------------------------------------------------------------------------------------------------------ +test_xy_bounds[utiles-(0, 0, 0)] 83.0000 (1.0) 57,375.0000 (112.55) 145.9127 (1.57) 125.0000 (1.36) +test_xy_bounds[utiles-(1, 1, 1)] 88.3400 (1.06) 1,335.4200 (2.62) 93.1478 (1.00) 92.5000 (1.00) +test_xy_bounds[utiles-(1, 40, 7)] 88.7500 (1.07) 1,451.2500 (2.85) 93.1258 (1.0) 92.0900 (1.0) +test_xy_bounds[utiles-(1, 0, 1)] 89.5900 (1.08) 861.6600 (1.69) 93.6428 (1.01) 93.3300 (1.01) +test_xy_bounds[utiles-(486, 332, 10)] 90.0000 (1.08) 509.7950 (1.0) 93.2818 (1.00) 92.9150 (1.01) +test_xy_bounds[utiles-(486, 332, 20)] 90.4100 (1.09) 864.1700 (1.70) 94.2974 (1.01) 94.1600 (1.02) +test_xy_bounds[utiles-(486, 332, 30)] 90.4100 (1.09) 1,377.5000 (2.70) 94.7366 (1.02) 94.1600 (1.02) +test_xy_bounds[mercantile-(0, 0, 0)] 708.0000 (8.53) 42,250.0000 (82.88) 843.2292 (9.05) 833.0000 (9.05) +test_xy_bounds[mercantile-(486, 332, 10)] 731.2500 (8.81) 5,393.7500 (10.58) 770.8331 (8.28) 764.6000 (8.30) +test_xy_bounds[mercantile-(486, 332, 30)] 731.2500 (8.81) 7,316.6500 (14.35) 770.2423 (8.27) 764.6000 (8.30) +test_xy_bounds[mercantile-(1, 0, 1)] 732.1429 (8.82) 9,404.8571 (18.45) 791.7721 (8.50) 785.7143 (8.53) +test_xy_bounds[mercantile-(1, 40, 7)] 733.3000 (8.83) 5,950.0000 (11.67) 772.3273 (8.29) 766.7000 (8.33) +test_xy_bounds[mercantile-(486, 332, 20)] 733.3500 (8.84) 7,408.3000 (14.53) 773.4718 (8.31) 768.7500 (8.35) +test_xy_bounds[mercantile-(1, 1, 1)] 735.4000 (8.86) 4,762.5000 (9.34) 783.2100 (8.41) 777.1000 (8.44) +test_xy_bounds[morecantile-(0, 0, 0)] 3,042.0000 (36.65) 108,208.0000 (212.26) 3,287.1089 (35.30) 3,250.0000 (35.29) +test_xy_bounds[morecantile-(1, 0, 1)] 3,291.0000 (39.65) 68,166.0000 (133.71) 3,503.4695 (37.62) 3,459.0000 (37.56) +test_xy_bounds[morecantile-(1, 1, 1)] 3,291.0000 (39.65) 72,875.0000 (142.95) 3,506.8544 (37.66) 3,500.0000 (38.01) +test_xy_bounds[morecantile-(1, 40, 7)] 4,375.0000 (52.71) 76,375.0000 (149.82) 4,630.2893 (49.72) 4,584.0000 (49.78) +test_xy_bounds[morecantile-(486, 332, 10)] 4,875.0000 (58.73) 85,208.0000 (167.14) 5,197.3581 (55.81) 5,125.0000 (55.65) +test_xy_bounds[morecantile-(486, 332, 20)] 6,916.0000 (83.33) 78,458.0000 (153.90) 7,221.0669 (77.54) 7,166.0000 (77.82) +test_xy_bounds[morecantile-(486, 332, 30)] 52,667.0000 (634.54) 404,208.0000 (792.88) 54,749.1232 (587.90) 54,333.0000 (590.00) +------------------------------------------------------------------------------------------------------------------------------------------ +``` diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py new file mode 100644 index 0000000..f801a70 --- /dev/null +++ b/benchmarks/benchmarks.py @@ -0,0 +1,96 @@ +"""Morecantile/Mercantile/Utiles comparison benchmark + +The benchmark suite is adapted from jessekrubin/utiles +https://github.com/jessekrubin/utiles/blob/ea58b9a017a2e3528f03cc20f16ef531737b863f/utiles-pyo3/bench/test_bench.py#L17-L25 +""" +# This file is a modified version of https://github.com/jessekrubin/utiles/blob/ea58b9a017a2e3528f03cc20f16ef531737b863f/utiles-pyo3/bench/test_bench.py. +# +# The original license follows. +# +# MIT License +# +# Copyright (c) 2023 jessekrubin +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from typing import Callable, Tuple + +import mercantile +import pytest +import utiles + +import morecantile + +tms = morecantile.tms.get("WebMercatorQuad") + +TEST_TILES = ( + (0, 0, 0), + (1, 0, 1), + (1, 1, 1), + (1, 40, 7), + (486, 332, 10), + # HIGH ZOOM + (486, 332, 20), + # OUTSIDE TMS Range + (486, 332, 30), +) + + +@pytest.mark.parametrize( + "tile", + [pytest.param(t, id=str(t)) for t in TEST_TILES], +) +@pytest.mark.parametrize( + "func", + [ + pytest.param(mercantile.bounds, id="mercantile"), + pytest.param(tms.bounds, id="morecantile"), + pytest.param(utiles.bounds, id="utiles"), + ], +) +@pytest.mark.benchmark(group="bounds") +def test_bounds( + tile: Tuple[int, int, int], + func: Callable[[Tuple[int, int, int]], Tuple[float, float]], + benchmark, +) -> None: + """Benchmark bounds() method.""" + _ = benchmark(func, *tile) + + +@pytest.mark.parametrize( + "tile", + [pytest.param(t, id=str(t)) for t in TEST_TILES], +) +@pytest.mark.parametrize( + "func", + [ + pytest.param(mercantile.xy_bounds, id="mercantile"), + pytest.param(tms.xy_bounds, id="morecantile"), + pytest.param(utiles.xy_bounds, id="utiles"), + ], +) +@pytest.mark.benchmark(group="xy_bounds") +def test_xy_bounds( + tile: Tuple[int, int, int], + func: Callable[[Tuple[int, int, int]], Tuple[float, float]], + benchmark, +) -> None: + """Benchmark xy_bounds() method.""" + _ = benchmark(func, *tile) diff --git a/benchmarks/requirements.txt b/benchmarks/requirements.txt new file mode 100644 index 0000000..e11e111 --- /dev/null +++ b/benchmarks/requirements.txt @@ -0,0 +1,6 @@ +pytest +pytest-benchmark + +morecantile +mercantile +utiles diff --git a/pyproject.toml b/pyproject.toml index d004ae9..fa99611 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,10 @@ test = [ "pytest-cov", "rasterio>=1.2.1", ] +benchmark = [ + "pytest", + "pytest-benchmark", +] dev = [ "pre-commit", "bump-my-version", diff --git a/tests/benchmarks.py b/tests/benchmarks.py new file mode 100644 index 0000000..166d9de --- /dev/null +++ b/tests/benchmarks.py @@ -0,0 +1,43 @@ +"""Morecantile benchmark.""" + +import pytest + +import morecantile +from morecantile.commons import BoundingBox + +tms = morecantile.tms.get("WebMercatorQuad") + +# Test tiles from https://github.com/jessekrubin/utiles/blob/ea58b9a017a2e3528f03cc20f16ef531737b863f/utiles-pyo3/bench/test_bench.py +TEST_TILES = ( + (0, 0, 0), + (1, 0, 1), + (1, 1, 1), + (1, 40, 7), + (486, 332, 10), + # HIGH ZOOM + (486, 332, 20), + # OUTSIDE TMS Range + (486, 332, 30), +) + + +@pytest.mark.parametrize("tile", TEST_TILES) +def test_bounds(tile, benchmark): + str_tile = "Tile(x={},y={},z={})".format(*tile) + benchmark.name = f"morecantile.bounds-{str_tile}" + benchmark.fullname = f"morecantile.bounds-{str_tile}" + benchmark.group = "morecantile.bounds" + + r = benchmark(tms.bounds, *tile) + assert isinstance(r, BoundingBox) + + +@pytest.mark.parametrize("tile", TEST_TILES) +def test_xy_bounds(tile, benchmark) -> None: + str_tile = "Tile(x={},y={},z={})".format(*tile) + benchmark.name = f"morecantile.xy_bounds-{str_tile}" + benchmark.fullname = f"morecantile.xy_bounds-{str_tile}" + benchmark.group = "morecantile.xy_bounds" + + r = benchmark(tms.xy_bounds, *tile) + assert isinstance(r, BoundingBox) From 1dbb5fa5dd1a2a3e945879978c3e936f7f9d6344 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 17 Oct 2024 22:51:26 +0200 Subject: [PATCH 16/19] add benchmark in docs --- docs/mkdocs.yml | 1 + docs/src/benchmark.html | 292 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 docs/src/benchmark.html diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 98a8695..1cae0f4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -31,6 +31,7 @@ nav: - morecantile.models: api/morecantile/models.md - morecantile.utils: api/morecantile/utils.md - CLI: 'cli.md' + - Benchmarking: benchmark.html - Development - Contributing: 'contributing.md' - Release: 'release-notes.md' diff --git a/docs/src/benchmark.html b/docs/src/benchmark.html new file mode 100644 index 0000000..fb9bee3 --- /dev/null +++ b/docs/src/benchmark.html @@ -0,0 +1,292 @@ + + + + + + + Benchmarks + + + + +
+ + + + + + + From 57e9b1abd2dd4e5491cbe23780c896691bc7f0d1 Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 17 Oct 2024 22:56:35 +0200 Subject: [PATCH 17/19] update changelog --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 69a84af..37bcfdb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,8 @@ -## 7.0.0 (TBD) +## 6.1.0 (TBD) * add `_tile_matrices_idx: Dict[str, int]` private attribute to improve `matrices` lookup -* change `_ul`, `ul`, `_lr` and `lr` method to only accept `morecantile.Tile` object **breaking change** +* change `xy_bounds()` and `bounds()` methods to avoid calculation duplication ## 6.0.0 (2024-10-16) From 91e08705986097beac26a7b6f41abed412cf9fab Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 17 Oct 2024 23:06:32 +0200 Subject: [PATCH 18/19] update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 37bcfdb..d82944c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,5 @@ -## 6.1.0 (TBD) +## 6.1.0 (2024-10-17) * add `_tile_matrices_idx: Dict[str, int]` private attribute to improve `matrices` lookup * change `xy_bounds()` and `bounds()` methods to avoid calculation duplication From 0ab27a724b762764eaa4c320b2cdf219a7d1d06c Mon Sep 17 00:00:00 2001 From: vincentsarago Date: Thu, 17 Oct 2024 23:06:40 +0200 Subject: [PATCH 19/19] =?UTF-8?q?Bump=20version:=206.0.0=20=E2=86=92=206.1?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- morecantile/__init__.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/morecantile/__init__.py b/morecantile/__init__.py index b08f7a0..74bf0dd 100644 --- a/morecantile/__init__.py +++ b/morecantile/__init__.py @@ -8,7 +8,7 @@ """ -__version__ = "6.0.0" +__version__ = "6.1.0" from .commons import BoundingBox, Coords, Tile # noqa from .defaults import TileMatrixSets, tms # noqa diff --git a/pyproject.toml b/pyproject.toml index fa99611..63d7bb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ filterwarnings = [ ] [tool.bumpversion] -current_version = "6.0.0" +current_version = "6.1.0" search = "{current_version}" replace = "{new_version}"