morecantile 6.0.0__tar.gz → 6.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {morecantile-6.0.0 → morecantile-6.2.0}/.pre-commit-config.yaml +5 -10
  2. {morecantile-6.0.0 → morecantile-6.2.0}/PKG-INFO +7 -3
  3. morecantile-6.2.0/benchmarks/README.md +63 -0
  4. morecantile-6.2.0/benchmarks/benchmarks.py +96 -0
  5. morecantile-6.2.0/benchmarks/requirements.txt +6 -0
  6. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/__init__.py +1 -1
  7. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/defaults.py +1 -1
  8. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/models.py +35 -12
  9. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/utils.py +4 -6
  10. {morecantile-6.0.0 → morecantile-6.2.0}/pyproject.toml +9 -4
  11. {morecantile-6.0.0 → morecantile-6.2.0}/.gitignore +0 -0
  12. {morecantile-6.0.0 → morecantile-6.2.0}/LICENSE +0 -0
  13. {morecantile-6.0.0 → morecantile-6.2.0}/README.md +0 -0
  14. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/commons.py +0 -0
  15. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/CDB1GlobalGrid.json +0 -0
  16. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/CanadianNAD83_LCC.json +0 -0
  17. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/EuropeanETRS89_LAEAQuad.json +0 -0
  18. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/GNOSISGlobalGrid.json +0 -0
  19. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/LINZAntarticaMapTilegrid.json +0 -0
  20. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/NZTM2000Quad.json +0 -0
  21. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/README.md +0 -0
  22. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/UPSAntarcticWGS84Quad.json +0 -0
  23. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/UPSArcticWGS84Quad.json +0 -0
  24. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/UTM31WGS84Quad.json +0 -0
  25. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/WGS1984Quad.json +0 -0
  26. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/WebMercatorQuad.json +0 -0
  27. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/WorldCRS84Quad.json +0 -0
  28. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/data/WorldMercatorWGS84Quad.json +0 -0
  29. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/errors.py +0 -0
  30. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/py.typed +0 -0
  31. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/scripts/__init__.py +0 -0
  32. {morecantile-6.0.0 → morecantile-6.2.0}/morecantile/scripts/cli.py +0 -0
@@ -4,26 +4,21 @@ repos:
4
4
  hooks:
5
5
  - id: validate-pyproject
6
6
 
7
- - repo: https://github.com/psf/black
8
- rev: 22.12.0
9
- hooks:
10
- - id: black
11
- language_version: python
12
-
13
7
  - repo: https://github.com/PyCQA/isort
14
- rev: 5.12.0
8
+ rev: 5.13.2
15
9
  hooks:
16
10
  - id: isort
17
11
  language_version: python
18
12
 
19
- - repo: https://github.com/charliermarsh/ruff-pre-commit
20
- rev: v0.0.238
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.3.5
21
15
  hooks:
22
16
  - id: ruff
23
17
  args: ["--fix"]
18
+ - id: ruff-format
24
19
 
25
20
  - repo: https://github.com/pre-commit/mirrors-mypy
26
- rev: v1.4.1
21
+ rev: v1.11.2
27
22
  hooks:
28
23
  - id: mypy
29
24
  language_version: python
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: morecantile
3
- Version: 6.0.0
3
+ Version: 6.2.0
4
4
  Summary: Construct and use map tile grids (a.k.a TileMatrixSet / TMS).
5
5
  Keywords: GIS,TMS,TileMatrixSet,Map Tile
6
6
  Author-email: Vincent Sarago <vincent@developmentseed.com>
@@ -14,10 +14,13 @@ Classifier: Programming Language :: Python :: 3.9
14
14
  Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
17
18
  Classifier: Topic :: Scientific/Engineering :: GIS
18
19
  Requires-Dist: attrs
19
- Requires-Dist: pyproj~=3.1
20
+ Requires-Dist: pyproj>=3.1,<4.0
20
21
  Requires-Dist: pydantic~=2.0
22
+ Requires-Dist: pytest ; extra == "benchmark"
23
+ Requires-Dist: pytest-benchmark ; extra == "benchmark"
21
24
  Requires-Dist: pre-commit ; extra == "dev"
22
25
  Requires-Dist: bump-my-version ; extra == "dev"
23
26
  Requires-Dist: mkdocs>=1.4.3 ; extra == "docs"
@@ -32,6 +35,7 @@ Requires-Dist: pytest-cov ; extra == "test"
32
35
  Requires-Dist: rasterio>=1.2.1 ; extra == "test"
33
36
  Project-URL: Documentation, https://developmentseed.org/morecantile/
34
37
  Project-URL: Source, https://github.com/developmentseed/morecantile
38
+ Provides-Extra: benchmark
35
39
  Provides-Extra: dev
36
40
  Provides-Extra: docs
37
41
  Provides-Extra: rasterio
@@ -0,0 +1,63 @@
1
+
2
+ ## Benchmark
3
+
4
+ Compare `mercantile`, `utiles` and `morecantile`
5
+
6
+ ```sh
7
+ python -m pip install -r requirements.txt
8
+ python -m pytest benchmarks.py --benchmark-only --benchmark-columns 'min, max, mean, median' --benchmark-sort 'min'
9
+ ```
10
+
11
+ ```
12
+ ----------------------------------------------------- benchmark 'bounds': 21 tests ----------------------------------------------------
13
+ Name (time in ns) Min Max Mean Median
14
+ ---------------------------------------------------------------------------------------------------------------------------------------
15
+ test_bounds[utiles-(486, 332, 30)] 83.0000 (1.0) 52,000.0000 (52.97) 195.3371 (1.48) 208.0000 (1.60)
16
+ test_bounds[utiles-(0, 0, 0)] 83.0000 (1.00) 31,167.0000 (31.75) 193.5957 (1.47) 208.0000 (1.60)
17
+ test_bounds[utiles-(1, 0, 1)] 125.0000 (1.51) 981.6700 (1.0) 132.0177 (1.0) 130.8300 (1.01)
18
+ test_bounds[utiles-(1, 40, 7)] 125.0000 (1.51) 8,606.2500 (8.77) 132.5298 (1.00) 130.0000 (1.0)
19
+ test_bounds[utiles-(486, 332, 10)] 128.3300 (1.55) 1,600.8300 (1.63) 133.4046 (1.01) 131.6700 (1.01)
20
+ test_bounds[utiles-(1, 1, 1)] 129.5900 (1.56) 16,846.2500 (17.16) 141.9846 (1.08) 134.1700 (1.03)
21
+ test_bounds[utiles-(486, 332, 20)] 138.3300 (1.67) 1,123.3300 (1.14) 142.5420 (1.08) 141.2500 (1.09)
22
+ 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)
23
+ 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)
24
+ 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)
25
+ 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)
26
+ 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)
27
+ 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)
28
+ 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)
29
+ 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)
30
+ 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)
31
+ 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)
32
+ 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)
33
+ 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)
34
+ 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)
35
+ 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)
36
+ ---------------------------------------------------------------------------------------------------------------------------------------
37
+
38
+ ---------------------------------------------------- benchmark 'xy_bounds': 21 tests -----------------------------------------------------
39
+ Name (time in ns) Min Max Mean Median
40
+ ------------------------------------------------------------------------------------------------------------------------------------------
41
+ test_xy_bounds[utiles-(0, 0, 0)] 83.0000 (1.0) 57,375.0000 (112.55) 145.9127 (1.57) 125.0000 (1.36)
42
+ test_xy_bounds[utiles-(1, 1, 1)] 88.3400 (1.06) 1,335.4200 (2.62) 93.1478 (1.00) 92.5000 (1.00)
43
+ test_xy_bounds[utiles-(1, 40, 7)] 88.7500 (1.07) 1,451.2500 (2.85) 93.1258 (1.0) 92.0900 (1.0)
44
+ test_xy_bounds[utiles-(1, 0, 1)] 89.5900 (1.08) 861.6600 (1.69) 93.6428 (1.01) 93.3300 (1.01)
45
+ test_xy_bounds[utiles-(486, 332, 10)] 90.0000 (1.08) 509.7950 (1.0) 93.2818 (1.00) 92.9150 (1.01)
46
+ test_xy_bounds[utiles-(486, 332, 20)] 90.4100 (1.09) 864.1700 (1.70) 94.2974 (1.01) 94.1600 (1.02)
47
+ test_xy_bounds[utiles-(486, 332, 30)] 90.4100 (1.09) 1,377.5000 (2.70) 94.7366 (1.02) 94.1600 (1.02)
48
+ test_xy_bounds[mercantile-(0, 0, 0)] 708.0000 (8.53) 42,250.0000 (82.88) 843.2292 (9.05) 833.0000 (9.05)
49
+ test_xy_bounds[mercantile-(486, 332, 10)] 731.2500 (8.81) 5,393.7500 (10.58) 770.8331 (8.28) 764.6000 (8.30)
50
+ test_xy_bounds[mercantile-(486, 332, 30)] 731.2500 (8.81) 7,316.6500 (14.35) 770.2423 (8.27) 764.6000 (8.30)
51
+ test_xy_bounds[mercantile-(1, 0, 1)] 732.1429 (8.82) 9,404.8571 (18.45) 791.7721 (8.50) 785.7143 (8.53)
52
+ test_xy_bounds[mercantile-(1, 40, 7)] 733.3000 (8.83) 5,950.0000 (11.67) 772.3273 (8.29) 766.7000 (8.33)
53
+ test_xy_bounds[mercantile-(486, 332, 20)] 733.3500 (8.84) 7,408.3000 (14.53) 773.4718 (8.31) 768.7500 (8.35)
54
+ test_xy_bounds[mercantile-(1, 1, 1)] 735.4000 (8.86) 4,762.5000 (9.34) 783.2100 (8.41) 777.1000 (8.44)
55
+ 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)
56
+ 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)
57
+ 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)
58
+ 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)
59
+ 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)
60
+ 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)
61
+ 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)
62
+ ------------------------------------------------------------------------------------------------------------------------------------------
63
+ ```
@@ -0,0 +1,96 @@
1
+ """Morecantile/Mercantile/Utiles comparison benchmark
2
+
3
+ The benchmark suite is adapted from jessekrubin/utiles
4
+ https://github.com/jessekrubin/utiles/blob/ea58b9a017a2e3528f03cc20f16ef531737b863f/utiles-pyo3/bench/test_bench.py#L17-L25
5
+ """
6
+ # This file is a modified version of https://github.com/jessekrubin/utiles/blob/ea58b9a017a2e3528f03cc20f16ef531737b863f/utiles-pyo3/bench/test_bench.py.
7
+ #
8
+ # The original license follows.
9
+ #
10
+ # MIT License
11
+ #
12
+ # Copyright (c) 2023 jessekrubin
13
+ #
14
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ # of this software and associated documentation files (the "Software"), to deal
16
+ # in the Software without restriction, including without limitation the rights
17
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ # copies of the Software, and to permit persons to whom the Software is
19
+ # furnished to do so, subject to the following conditions:
20
+ #
21
+ # The above copyright notice and this permission notice shall be included in all
22
+ # copies or substantial portions of the Software.
23
+ #
24
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ # SOFTWARE.
31
+
32
+ from typing import Callable, Tuple
33
+
34
+ import mercantile
35
+ import pytest
36
+ import utiles
37
+
38
+ import morecantile
39
+
40
+ tms = morecantile.tms.get("WebMercatorQuad")
41
+
42
+ TEST_TILES = (
43
+ (0, 0, 0),
44
+ (1, 0, 1),
45
+ (1, 1, 1),
46
+ (1, 40, 7),
47
+ (486, 332, 10),
48
+ # HIGH ZOOM
49
+ (486, 332, 20),
50
+ # OUTSIDE TMS Range
51
+ (486, 332, 30),
52
+ )
53
+
54
+
55
+ @pytest.mark.parametrize(
56
+ "tile",
57
+ [pytest.param(t, id=str(t)) for t in TEST_TILES],
58
+ )
59
+ @pytest.mark.parametrize(
60
+ "func",
61
+ [
62
+ pytest.param(mercantile.bounds, id="mercantile"),
63
+ pytest.param(tms.bounds, id="morecantile"),
64
+ pytest.param(utiles.bounds, id="utiles"),
65
+ ],
66
+ )
67
+ @pytest.mark.benchmark(group="bounds")
68
+ def test_bounds(
69
+ tile: Tuple[int, int, int],
70
+ func: Callable[[Tuple[int, int, int]], Tuple[float, float]],
71
+ benchmark,
72
+ ) -> None:
73
+ """Benchmark bounds() method."""
74
+ _ = benchmark(func, *tile)
75
+
76
+
77
+ @pytest.mark.parametrize(
78
+ "tile",
79
+ [pytest.param(t, id=str(t)) for t in TEST_TILES],
80
+ )
81
+ @pytest.mark.parametrize(
82
+ "func",
83
+ [
84
+ pytest.param(mercantile.xy_bounds, id="mercantile"),
85
+ pytest.param(tms.xy_bounds, id="morecantile"),
86
+ pytest.param(utiles.xy_bounds, id="utiles"),
87
+ ],
88
+ )
89
+ @pytest.mark.benchmark(group="xy_bounds")
90
+ def test_xy_bounds(
91
+ tile: Tuple[int, int, int],
92
+ func: Callable[[Tuple[int, int, int]], Tuple[float, float]],
93
+ benchmark,
94
+ ) -> None:
95
+ """Benchmark xy_bounds() method."""
96
+ _ = benchmark(func, *tile)
@@ -0,0 +1,6 @@
1
+ pytest
2
+ pytest-benchmark
3
+
4
+ morecantile
5
+ mercantile
6
+ utiles
@@ -8,7 +8,7 @@ Refs:
8
8
 
9
9
  """
10
10
 
11
- __version__ = "6.0.0"
11
+ __version__ = "6.2.0"
12
12
 
13
13
  from .commons import BoundingBox, Coords, Tile # noqa
14
14
  from .defaults import TileMatrixSets, tms # noqa
@@ -55,7 +55,7 @@ class TileMatrixSets:
55
55
  """Register TileMatrixSet(s)."""
56
56
  for identifier in custom_tms.keys():
57
57
  if identifier in self.tms and not overwrite:
58
- raise Exception(f"{identifier} is already a registered TMS.")
58
+ raise InvalidIdentifier(f"{identifier} is already a registered TMS.")
59
59
 
60
60
  return TileMatrixSets({**self.tms, **custom_tms})
61
61
 
@@ -488,10 +488,16 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
488
488
  _to_geographic: pyproj.Transformer = PrivateAttr()
489
489
  _from_geographic: pyproj.Transformer = PrivateAttr()
490
490
 
491
+ _tile_matrices_idx: Dict[int, int] = PrivateAttr()
492
+
491
493
  def __init__(self, **data):
492
494
  """Set private attributes."""
493
495
  super().__init__(**data)
494
496
 
497
+ self._tile_matrices_idx = {
498
+ int(mat.id): idx for idx, mat in enumerate(self.tileMatrices)
499
+ }
500
+
495
501
  try:
496
502
  self._to_geographic = pyproj.Transformer.from_crs(
497
503
  self.crs._pyproj_crs, self.crs._pyproj_crs.geodetic_crs, always_xy=True
@@ -504,6 +510,7 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
504
510
  "Could not create coordinate Transformer from input CRS to the given geographic CRS"
505
511
  "some methods might not be available.",
506
512
  UserWarning,
513
+ stacklevel=1,
507
514
  )
508
515
  self._to_geographic = None
509
516
  self._from_geographic = None
@@ -531,10 +538,8 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
531
538
  def is_variable(self) -> bool:
532
539
  """Check if TMS has variable width matrix."""
533
540
  return any(
534
- [
535
- True if matrix.variableMatrixWidths is not None else False
536
- for matrix in self.tileMatrices
537
- ]
541
+ True if matrix.variableMatrixWidths is not None else False
542
+ for matrix in self.tileMatrices
538
543
  )
539
544
 
540
545
  def __iter__(self):
@@ -765,9 +770,8 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
765
770
 
766
771
  def matrix(self, zoom: int) -> TileMatrix:
767
772
  """Return the TileMatrix for a specific zoom."""
768
- for m in self.tileMatrices:
769
- if m.id == str(zoom):
770
- return m
773
+ if (idx := self._tile_matrices_idx.get(zoom, None)) is not None:
774
+ return self.tileMatrices[idx]
771
775
 
772
776
  #######################################################################
773
777
  # If user wants a deeper matrix we calculate it
@@ -795,6 +799,7 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
795
799
  warnings.warn(
796
800
  f"TileMatrix not found for level: {zoom} - Creating values from TMS Scale.",
797
801
  UserWarning,
802
+ stacklevel=1,
798
803
  )
799
804
 
800
805
  # TODO: what if we want to construct a matrix for a level up ?
@@ -889,6 +894,7 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
889
894
  warnings.warn(
890
895
  f"Point ({x}, {y}) is outside TMS bounds {list(self.xy_bbox)}.",
891
896
  PointOutsideTMSBounds,
897
+ stacklevel=1,
892
898
  )
893
899
 
894
900
  lng, lat = self._to_geographic.transform(x, y)
@@ -908,6 +914,7 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
908
914
  warnings.warn(
909
915
  f"Point ({lng}, {lat}) is outside TMS bounds {list(self.bbox)}.",
910
916
  PointOutsideTMSBounds,
917
+ stacklevel=1,
911
918
  )
912
919
 
913
920
  x, y = self._from_geographic.transform(lng, lat)
@@ -1093,8 +1100,23 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
1093
1100
  """
1094
1101
  t = _parse_tile_arg(*tile)
1095
1102
 
1096
- left, top = self._ul(t)
1097
- right, bottom = self._lr(t)
1103
+ matrix = self.matrix(t.z)
1104
+ origin_x, origin_y = self._matrix_origin(matrix)
1105
+
1106
+ cf = (
1107
+ matrix.get_coalesce_factor(t.y)
1108
+ if matrix.variableMatrixWidths is not None
1109
+ else 1
1110
+ )
1111
+
1112
+ left = origin_x + math.floor(t.x / cf) * matrix.cellSize * cf * matrix.tileWidth
1113
+ top = origin_y - t.y * matrix.cellSize * matrix.tileHeight
1114
+ right = (
1115
+ origin_x
1116
+ + (math.floor(t.x / cf) + 1) * matrix.cellSize * cf * matrix.tileWidth
1117
+ )
1118
+ bottom = origin_y - (t.y + 1) * matrix.cellSize * matrix.tileHeight
1119
+
1098
1120
  return BoundingBox(left, bottom, right, top)
1099
1121
 
1100
1122
  def ul(self, *tile: Tile) -> Coords:
@@ -1146,10 +1168,10 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
1146
1168
  BoundingBox: The bounding box of the input tile.
1147
1169
 
1148
1170
  """
1149
- t = _parse_tile_arg(*tile)
1171
+ _left, _bottom, _right, _top = self.xy_bounds(*tile)
1172
+ left, top = self.lnglat(_left, _top)
1173
+ right, bottom = self.lnglat(_right, _bottom)
1150
1174
 
1151
- left, top = self.ul(t)
1152
- right, bottom = self.lr(t)
1153
1175
  return BoundingBox(left, bottom, right, top)
1154
1176
 
1155
1177
  @property
@@ -1352,6 +1374,7 @@ class TileMatrixSet(BaseModel, arbitrary_types_allowed=True):
1352
1374
  "CRS is no longer part of the GeoJSON specification."
1353
1375
  "Other projection than EPSG:4326 might not be supported.",
1354
1376
  UserWarning,
1377
+ stacklevel=1,
1355
1378
  )
1356
1379
  feat.update(
1357
1380
  {
@@ -118,12 +118,10 @@ def is_power_of_two(number: int) -> bool:
118
118
  def check_quadkey_support(tms: List) -> bool:
119
119
  """Check if a Tile Matrix Set supports quadkeys"""
120
120
  return all(
121
- [
122
- (t.matrixWidth == t.matrixHeight)
123
- and is_power_of_two(t.matrixWidth)
124
- and ((t.matrixWidth * 2) == tms[i + 1].matrixWidth)
125
- for i, t in enumerate(tms[:-1])
126
- ]
121
+ (t.matrixWidth == t.matrixHeight)
122
+ and is_power_of_two(t.matrixWidth)
123
+ and ((t.matrixWidth * 2) == tms[i + 1].matrixWidth)
124
+ for i, t in enumerate(tms[:-1])
127
125
  )
128
126
 
129
127
 
@@ -17,12 +17,13 @@ classifiers = [
17
17
  "Programming Language :: Python :: 3.10",
18
18
  "Programming Language :: Python :: 3.11",
19
19
  "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
20
21
  "Topic :: Scientific/Engineering :: GIS",
21
22
  ]
22
23
  dynamic = ["version"]
23
24
  dependencies = [
24
25
  "attrs",
25
- "pyproj~=3.1",
26
+ "pyproj>=3.1,<4.0",
26
27
  "pydantic~=2.0",
27
28
  ]
28
29
 
@@ -36,6 +37,10 @@ test = [
36
37
  "pytest-cov",
37
38
  "rasterio>=1.2.1",
38
39
  ]
40
+ benchmark = [
41
+ "pytest",
42
+ "pytest-benchmark",
43
+ ]
39
44
  dev = [
40
45
  "pre-commit",
41
46
  "bump-my-version",
@@ -91,7 +96,7 @@ default_section = "THIRDPARTY"
91
96
  [tool.mypy]
92
97
  no_strict_optional = true
93
98
 
94
- [tool.ruff]
99
+ [tool.ruff.lint]
95
100
  select = [
96
101
  "D1", # pydocstyle errors
97
102
  "E", # pycodestyle errors
@@ -106,7 +111,7 @@ ignore = [
106
111
  "B905", # ignore zip() without an explicit strict= parameter, only support with python >3.10
107
112
  ]
108
113
 
109
- [tool.ruff.per-file-ignores]
114
+ [tool.ruff.lint.per-file-ignores]
110
115
  "tests/*.py" = ["D1"]
111
116
 
112
117
 
@@ -116,7 +121,7 @@ filterwarnings = [
116
121
  ]
117
122
 
118
123
  [tool.bumpversion]
119
- current_version = "6.0.0"
124
+ current_version = "6.2.0"
120
125
 
121
126
  search = "{current_version}"
122
127
  replace = "{new_version}"
File without changes
File without changes
File without changes