morecantile 3.3.0__py3-none-any.whl → 4.0.0a0__py3-none-any.whl
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.
- morecantile/__init__.py +1 -1
- morecantile/data/CanadianNAD83_LCC.json +269 -357
- morecantile/data/EuropeanETRS89_LAEAQuad.json +73 -248
- morecantile/data/LINZAntarticaMapTilegrid.json +64 -61
- morecantile/data/NZTM2000Quad.json +294 -303
- morecantile/data/UPSAntarcticWGS84Quad.json +259 -344
- morecantile/data/UPSArcticWGS84Quad.json +259 -344
- morecantile/data/UTM31WGS84Quad.json +249 -331
- morecantile/data/WGS1984Quad.json +324 -254
- morecantile/data/WebMercatorQuad.json +260 -345
- morecantile/data/WorldCRS84Quad.json +249 -254
- morecantile/data/WorldMercatorWGS84Quad.json +260 -345
- morecantile/defaults.py +8 -10
- morecantile/errors.py +4 -0
- morecantile/models.py +260 -149
- morecantile/scripts/cli.py +1 -1
- morecantile/utils.py +12 -0
- {morecantile-3.3.0.dist-info → morecantile-4.0.0a0.dist-info}/METADATA +56 -25
- morecantile-4.0.0a0.dist-info/RECORD +26 -0
- {morecantile-3.3.0.dist-info → morecantile-4.0.0a0.dist-info}/WHEEL +1 -1
- morecantile/data/NZTM2000.json +0 -242
- morecantile-3.3.0.dist-info/RECORD +0 -27
- {morecantile-3.3.0.dist-info → morecantile-4.0.0a0.dist-info}/LICENSE +0 -0
- {morecantile-3.3.0.dist-info → morecantile-4.0.0a0.dist-info}/entry_points.txt +0 -0
morecantile/errors.py
CHANGED
morecantile/models.py
CHANGED
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import math
|
|
4
4
|
import warnings
|
|
5
|
-
from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union
|
|
5
|
+
from typing import Any, Dict, Iterator, List, Literal, Optional, Sequence, Tuple, Union
|
|
6
6
|
|
|
7
|
+
from cachetools import LRUCache, cached
|
|
8
|
+
from cachetools.keys import hashkey
|
|
7
9
|
from pydantic import AnyHttpUrl, BaseModel, Field, PrivateAttr, validator
|
|
8
10
|
from pyproj import CRS, Transformer
|
|
9
|
-
from pyproj.enums import WktVersion
|
|
10
11
|
from pyproj.exceptions import ProjError
|
|
11
12
|
|
|
12
13
|
from morecantile.commons import BoundingBox, Coords, Tile
|
|
13
14
|
from morecantile.errors import (
|
|
15
|
+
DeprecationError,
|
|
14
16
|
InvalidZoomError,
|
|
15
|
-
MorecantileError,
|
|
16
17
|
NoQuadkeySupport,
|
|
17
18
|
PointOutsideTMSBounds,
|
|
18
19
|
QuadKeyError,
|
|
@@ -23,6 +24,7 @@ from morecantile.utils import (
|
|
|
23
24
|
check_quadkey_support,
|
|
24
25
|
meters_per_unit,
|
|
25
26
|
point_in_bbox,
|
|
27
|
+
to_rasterio_crs,
|
|
26
28
|
)
|
|
27
29
|
|
|
28
30
|
NumType = Union[float, int]
|
|
@@ -44,7 +46,8 @@ class CRSType(CRS, str):
|
|
|
44
46
|
@classmethod
|
|
45
47
|
def validate(cls, value: Union[CRS, str]) -> CRS:
|
|
46
48
|
"""Validate CRS."""
|
|
47
|
-
# If input is a string we
|
|
49
|
+
# If input is a string we translate it to CRS
|
|
50
|
+
# TODO: add NotImplementedError for ISO 19115
|
|
48
51
|
if not isinstance(value, CRS):
|
|
49
52
|
return CRS.from_user_input(value)
|
|
50
53
|
|
|
@@ -90,6 +93,11 @@ def crs_axis_inverted(crs: CRS) -> bool:
|
|
|
90
93
|
return crs.axis_info[0].abbrev.upper() in ["Y", "LAT", "N"]
|
|
91
94
|
|
|
92
95
|
|
|
96
|
+
def ordered_axis_inverted(ordered_axes: List[str]) -> bool:
|
|
97
|
+
"""Check if ordered axes have inverted AXIS (lat,lon) instead of (lon,lat)."""
|
|
98
|
+
return ordered_axes[0].upper() in ["Y", "LAT", "N"]
|
|
99
|
+
|
|
100
|
+
|
|
93
101
|
class TMSBoundingBox(BaseModel):
|
|
94
102
|
"""Bounding box"""
|
|
95
103
|
|
|
@@ -105,44 +113,124 @@ class TMSBoundingBox(BaseModel):
|
|
|
105
113
|
json_encoders = {CRS: lambda v: CRS_to_uri(v)}
|
|
106
114
|
|
|
107
115
|
|
|
116
|
+
# class variableMatrixWidth(BaseModel):
|
|
117
|
+
# """Variable Matrix Width Definition
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ref: https://github.com/opengeospatial/2D-Tile-Matrix-Set/blob/master/schemas/tms/2.0/json/variableMatrixWidth.json
|
|
121
|
+
# """
|
|
122
|
+
|
|
123
|
+
# coalesce: int = Field(..., ge=2, multiple_of=1, description="Number of tiles in width that coalesce in a single tile for these rows")
|
|
124
|
+
# minTileRow: int = Field(..., ge=0, multiple_of=1, description="First tile row where the coalescence factor applies for this tilematrix")
|
|
125
|
+
# maxTileRow: int = Field(..., ge=0, multiple_of=1, description="Last tile row where the coalescence factor applies for this tilematrix")
|
|
126
|
+
|
|
127
|
+
|
|
108
128
|
class TileMatrix(BaseModel):
|
|
109
|
-
"""Tile
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
"""Tile Matrix Definition
|
|
130
|
+
|
|
131
|
+
A tile matrix, usually corresponding to a particular zoom level of a TileMatrixSet.
|
|
132
|
+
|
|
133
|
+
ref: https://github.com/opengeospatial/2D-Tile-Matrix-Set/blob/master/schemas/tms/2.0/json/tileMatrix.json
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
title: Optional[str] = Field(
|
|
137
|
+
description="Title of this tile matrix, normally used for display to a human"
|
|
138
|
+
)
|
|
139
|
+
description: Optional[str] = Field(
|
|
140
|
+
description="Brief narrative description of this tile matrix set, normally available for display to a human"
|
|
141
|
+
)
|
|
142
|
+
keywords: Optional[List[str]] = Field(
|
|
143
|
+
description="Unordered list of one or more commonly used or formalized word(s) or phrase(s) used to describe this dataset"
|
|
144
|
+
)
|
|
145
|
+
id: str = Field(
|
|
146
|
+
...,
|
|
147
|
+
regex=r"^[0-9]+$",
|
|
148
|
+
description="Identifier selecting one of the scales defined in the TileMatrixSet and representing the scaleDenominator the tile. Implementation of 'identifier'",
|
|
149
|
+
)
|
|
150
|
+
scaleDenominator: float = Field(
|
|
151
|
+
..., description="Scale denominator of this tile matrix"
|
|
152
|
+
)
|
|
153
|
+
cellSize: float = Field(..., description="Cell size of this tile matrix")
|
|
154
|
+
cornerOfOrigin: Optional[Literal["topLeft", "bottomLeft"]] = Field(
|
|
155
|
+
description="The corner of the tile matrix (_topLeft_ or _bottomLeft_) used as the origin for numbering tile rows and columns. This corner is also a corner of the (0, 0) tile."
|
|
156
|
+
)
|
|
157
|
+
pointOfOrigin: BoundsType = Field(
|
|
158
|
+
...,
|
|
159
|
+
description="Precise position in CRS coordinates of the corner of origin (e.g. the top-left corner) for this tile matrix. This position is also a corner of the (0, 0) tile. In previous version, this was 'topLeftCorner' and 'cornerOfOrigin' did not exist.",
|
|
160
|
+
)
|
|
161
|
+
tileWidth: int = Field(
|
|
162
|
+
...,
|
|
163
|
+
ge=1,
|
|
164
|
+
multiple_of=1,
|
|
165
|
+
description="Width of each tile of this tile matrix in pixels",
|
|
166
|
+
)
|
|
167
|
+
tileHeight: int = Field(
|
|
168
|
+
...,
|
|
169
|
+
ge=1,
|
|
170
|
+
multiple_of=1,
|
|
171
|
+
description="Height of each tile of this tile matrix in pixels",
|
|
172
|
+
)
|
|
173
|
+
matrixWidth: int = Field(
|
|
174
|
+
...,
|
|
175
|
+
ge=1,
|
|
176
|
+
multiple_of=1,
|
|
177
|
+
description="Width of the matrix (number of tiles in width)",
|
|
178
|
+
)
|
|
179
|
+
matrixHeight: int = Field(
|
|
180
|
+
...,
|
|
181
|
+
ge=1,
|
|
182
|
+
multiple_of=1,
|
|
183
|
+
description="Height of the matrix (number of tiles in height)",
|
|
184
|
+
)
|
|
185
|
+
# variableMatrixWidths: Optional[List[variableMatrixWidth]] = Field(description="Describes the rows that has variable matrix width")
|
|
122
186
|
|
|
123
187
|
class Config:
|
|
124
|
-
"""Forbid additional items like
|
|
188
|
+
"""Forbid additional items like variableMatrixWidths."""
|
|
125
189
|
|
|
126
190
|
extra = "forbid"
|
|
127
191
|
|
|
128
192
|
|
|
129
193
|
class TileMatrixSet(BaseModel):
|
|
130
|
-
"""Tile
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
194
|
+
"""Tile Matrix Set Definition
|
|
195
|
+
|
|
196
|
+
A definition of a tile matrix set following the Tile Matrix Set standard.
|
|
197
|
+
For tileset metadata, such a description (in `tileMatrixSet` property) is only required for offline use,
|
|
198
|
+
as an alternative to a link with a `http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme` relation type.
|
|
199
|
+
|
|
200
|
+
ref: https://github.com/opengeospatial/2D-Tile-Matrix-Set/blob/master/schemas/tms/2.0/json/tileMatrixSet.json
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
title: Optional[str] = Field(
|
|
205
|
+
description="Title of this tile matrix set, normally used for display to a human"
|
|
206
|
+
)
|
|
207
|
+
description: Optional[str] = Field(
|
|
208
|
+
description="Brief narrative description of this tile matrix set, normally available for display to a human"
|
|
209
|
+
)
|
|
210
|
+
keywords: Optional[List[str]] = Field(
|
|
211
|
+
description="Unordered list of one or more commonly used or formalized word(s) or phrase(s) used to describe this tile matrix set"
|
|
212
|
+
)
|
|
213
|
+
id: Optional[str] = Field(
|
|
214
|
+
regex=r"^[\w\d_\-]+$",
|
|
215
|
+
description="Tile matrix set identifier. Implementation of 'identifier'",
|
|
216
|
+
)
|
|
217
|
+
uri: Optional[str] = Field(
|
|
218
|
+
description="Reference to an official source for this tileMatrixSet"
|
|
219
|
+
)
|
|
220
|
+
orderedAxes: Optional[List[str]]
|
|
221
|
+
crs: CRSType = Field(..., description="Coordinate Reference System (CRS)")
|
|
222
|
+
wellKnownScaleSet: Optional[AnyHttpUrl] = Field(
|
|
223
|
+
description="Reference to a well-known scale set"
|
|
224
|
+
)
|
|
225
|
+
boundingBox: Optional[TMSBoundingBox] = Field(
|
|
226
|
+
description="Minimum bounding rectangle surrounding the tile matrix set, in the supported CRS"
|
|
227
|
+
)
|
|
228
|
+
tileMatrices: List[TileMatrix] = Field(
|
|
229
|
+
..., description="Describes scale levels and its tile matrices"
|
|
230
|
+
)
|
|
141
231
|
|
|
142
232
|
# Private attributes
|
|
143
233
|
_is_quadtree: bool = PrivateAttr()
|
|
144
|
-
|
|
145
|
-
# CRS transformation attributes
|
|
146
234
|
_geographic_crs: CRSType = PrivateAttr(default=WGS84_CRS)
|
|
147
235
|
_to_geographic: Transformer = PrivateAttr()
|
|
148
236
|
_from_geographic: Transformer = PrivateAttr()
|
|
@@ -155,18 +243,23 @@ class TileMatrixSet(BaseModel):
|
|
|
155
243
|
|
|
156
244
|
def __init__(self, **data):
|
|
157
245
|
"""Create PyProj transforms and check if TileMatrixSet supports quadkeys."""
|
|
246
|
+
if {"supportedCRS", "topLeftCorner"}.intersection(data):
|
|
247
|
+
raise DeprecationError(
|
|
248
|
+
"Tile Matrix Set must be version 2.0. Use morecantile <4.0 for TMS 1.0 support"
|
|
249
|
+
)
|
|
250
|
+
|
|
158
251
|
super().__init__(**data)
|
|
159
252
|
|
|
160
|
-
self._is_quadtree = check_quadkey_support(self.
|
|
253
|
+
self._is_quadtree = check_quadkey_support(self.tileMatrices)
|
|
161
254
|
|
|
162
255
|
self._geographic_crs = data.get("_geographic_crs", WGS84_CRS)
|
|
163
256
|
|
|
164
257
|
try:
|
|
165
258
|
self._to_geographic = Transformer.from_crs(
|
|
166
|
-
self.
|
|
259
|
+
self.crs, self._geographic_crs, always_xy=True
|
|
167
260
|
)
|
|
168
261
|
self._from_geographic = Transformer.from_crs(
|
|
169
|
-
self._geographic_crs, self.
|
|
262
|
+
self._geographic_crs, self.crs, always_xy=True
|
|
170
263
|
)
|
|
171
264
|
except ProjError:
|
|
172
265
|
warnings.warn(
|
|
@@ -177,51 +270,103 @@ class TileMatrixSet(BaseModel):
|
|
|
177
270
|
self._to_geographic = None
|
|
178
271
|
self._from_geographic = None
|
|
179
272
|
|
|
180
|
-
@validator("
|
|
273
|
+
@validator("tileMatrices")
|
|
181
274
|
def sort_tile_matrices(cls, v):
|
|
182
275
|
"""Sort matrices by identifier"""
|
|
183
|
-
return sorted(v, key=lambda m: int(m.
|
|
276
|
+
return sorted(v, key=lambda m: int(m.id))
|
|
184
277
|
|
|
185
278
|
def __iter__(self):
|
|
186
279
|
"""Iterate over matrices"""
|
|
187
|
-
for matrix in self.
|
|
280
|
+
for matrix in self.tileMatrices:
|
|
188
281
|
yield matrix
|
|
189
282
|
|
|
190
283
|
def __repr__(self):
|
|
191
284
|
"""Simplify default pydantic model repr."""
|
|
192
|
-
return f"<TileMatrixSet title='{self.title}'
|
|
285
|
+
return f"<TileMatrixSet title='{self.title}' id='{self.id}'>"
|
|
193
286
|
|
|
194
287
|
@property
|
|
195
|
-
def
|
|
196
|
-
"""
|
|
197
|
-
return self.
|
|
288
|
+
def geographic_crs(self) -> CRSType:
|
|
289
|
+
"""Return the TMS's geographic CRS."""
|
|
290
|
+
return self._geographic_crs
|
|
198
291
|
|
|
199
292
|
@property
|
|
200
293
|
def rasterio_crs(self):
|
|
201
294
|
"""Return rasterio CRS."""
|
|
295
|
+
return to_rasterio_crs(self.crs)
|
|
202
296
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return rasterio.crs.CRS.from_wkt(self.crs.to_wkt(WktVersion.WKT1_GDAL))
|
|
208
|
-
else:
|
|
209
|
-
return rasterio.crs.CRS.from_wkt(self.crs.to_wkt())
|
|
297
|
+
@property
|
|
298
|
+
def rasterio_geographic_crs(self):
|
|
299
|
+
"""Return the geographic CRS as a rasterio CRS."""
|
|
300
|
+
return to_rasterio_crs(self._geographic_crs)
|
|
210
301
|
|
|
211
302
|
@property
|
|
212
303
|
def minzoom(self) -> int:
|
|
213
304
|
"""TileMatrixSet minimum TileMatrix identifier"""
|
|
214
|
-
return int(self.
|
|
305
|
+
return int(self.tileMatrices[0].id)
|
|
215
306
|
|
|
216
307
|
@property
|
|
217
308
|
def maxzoom(self) -> int:
|
|
218
309
|
"""TileMatrixSet maximum TileMatrix identifier"""
|
|
219
|
-
return int(self.
|
|
310
|
+
return int(self.tileMatrices[-1].id)
|
|
220
311
|
|
|
221
312
|
@property
|
|
222
313
|
def _invert_axis(self) -> bool:
|
|
223
314
|
"""Check if CRS has inverted AXIS (lat,lon) instead of (lon,lat)."""
|
|
224
|
-
return
|
|
315
|
+
return (
|
|
316
|
+
ordered_axis_inverted(self.orderedAxes)
|
|
317
|
+
if self.orderedAxes
|
|
318
|
+
else crs_axis_inverted(self.crs)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def from_v1(cls, tms: Dict) -> "TileMatrixSet":
|
|
323
|
+
"""
|
|
324
|
+
Makes a TMS from a v1 TMS definition
|
|
325
|
+
|
|
326
|
+
Attributes
|
|
327
|
+
----------
|
|
328
|
+
supportedCRS: CRSType
|
|
329
|
+
Tile Matrix Set coordinate reference system
|
|
330
|
+
title: str
|
|
331
|
+
Title of TMS
|
|
332
|
+
abstract: str (optional)
|
|
333
|
+
Abstract of CRS
|
|
334
|
+
keywords: str (optional)
|
|
335
|
+
Keywords
|
|
336
|
+
identifier: str
|
|
337
|
+
TMS Identifier
|
|
338
|
+
wellKnownScaleSet: AnyHttpUrl (optional)
|
|
339
|
+
WKSS URL
|
|
340
|
+
boundingBox: TMSBoundingBox (optional)
|
|
341
|
+
Bounding box of TMS
|
|
342
|
+
tileMatrix: List[TileMatrix]
|
|
343
|
+
List of Tile Matrices
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
--------
|
|
347
|
+
TileMatrixSet
|
|
348
|
+
"""
|
|
349
|
+
v2_tms = tms.copy()
|
|
350
|
+
|
|
351
|
+
del v2_tms["type"]
|
|
352
|
+
|
|
353
|
+
v2_tms["crs"] = v2_tms.pop("supportedCRS")
|
|
354
|
+
v2_tms["tileMatrices"] = v2_tms.pop("tileMatrix")
|
|
355
|
+
v2_tms["id"] = v2_tms.pop("identifier")
|
|
356
|
+
mpu = meters_per_unit(CRS.from_user_input(v2_tms["crs"]))
|
|
357
|
+
for i in range(len(v2_tms["tileMatrices"])):
|
|
358
|
+
v2_tms["tileMatrices"][i]["cellSize"] = (
|
|
359
|
+
v2_tms["tileMatrices"][i]["scaleDenominator"] * 0.00028 / mpu
|
|
360
|
+
)
|
|
361
|
+
v2_tms["tileMatrices"][i]["pointOfOrigin"] = v2_tms["tileMatrices"][i].pop(
|
|
362
|
+
"topLeftCorner"
|
|
363
|
+
)
|
|
364
|
+
v2_tms["tileMatrices"][i]["id"] = v2_tms["tileMatrices"][i].pop(
|
|
365
|
+
"identifier"
|
|
366
|
+
)
|
|
367
|
+
del v2_tms["tileMatrices"][i]["type"]
|
|
368
|
+
|
|
369
|
+
return TileMatrixSet(**v2_tms)
|
|
225
370
|
|
|
226
371
|
@classmethod
|
|
227
372
|
def custom(
|
|
@@ -234,9 +379,11 @@ class TileMatrixSet(BaseModel):
|
|
|
234
379
|
extent_crs: Optional[CRS] = None,
|
|
235
380
|
minzoom: int = 0,
|
|
236
381
|
maxzoom: int = 24,
|
|
237
|
-
title: str =
|
|
238
|
-
|
|
382
|
+
title: Optional[str] = None,
|
|
383
|
+
id: Optional[str] = None,
|
|
384
|
+
ordered_axes: Optional[List[str]] = None,
|
|
239
385
|
geographic_crs: CRS = WGS84_CRS,
|
|
386
|
+
**kwargs: Any,
|
|
240
387
|
):
|
|
241
388
|
"""
|
|
242
389
|
Construct a custom TileMatrixSet.
|
|
@@ -262,12 +409,14 @@ class TileMatrixSet(BaseModel):
|
|
|
262
409
|
Tile Matrix Set minimum zoom level (default is 0).
|
|
263
410
|
maxzoom: int
|
|
264
411
|
Tile Matrix Set maximum zoom level (default is 24).
|
|
265
|
-
title: str
|
|
266
|
-
Tile Matrix Set title
|
|
267
|
-
|
|
268
|
-
Tile Matrix Set identifier
|
|
412
|
+
title: str, optional
|
|
413
|
+
Tile Matrix Set title
|
|
414
|
+
id: str, optional
|
|
415
|
+
Tile Matrix Set identifier
|
|
269
416
|
geographic_crs: pyproj.CRS
|
|
270
417
|
Geographic (lat,lon) coordinate reference system (default is EPSG:4326)
|
|
418
|
+
kwargs: Any
|
|
419
|
+
Attributes to forward to the TileMatrixSet
|
|
271
420
|
|
|
272
421
|
Returns:
|
|
273
422
|
--------
|
|
@@ -276,55 +425,35 @@ class TileMatrixSet(BaseModel):
|
|
|
276
425
|
"""
|
|
277
426
|
matrix_scale = matrix_scale or [1, 1]
|
|
278
427
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"identifier": identifier,
|
|
282
|
-
"supportedCRS": crs,
|
|
283
|
-
"tileMatrix": [],
|
|
284
|
-
"_geographic_crs": geographic_crs,
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
is_inverted = crs_axis_inverted(crs)
|
|
288
|
-
|
|
289
|
-
if is_inverted:
|
|
290
|
-
tms["boundingBox"] = TMSBoundingBox(
|
|
291
|
-
crs=extent_crs or crs,
|
|
292
|
-
lowerCorner=[extent[1], extent[0]],
|
|
293
|
-
upperCorner=[extent[3], extent[2]],
|
|
294
|
-
)
|
|
428
|
+
if ordered_axes:
|
|
429
|
+
is_inverted = ordered_axis_inverted(ordered_axes)
|
|
295
430
|
else:
|
|
296
|
-
|
|
297
|
-
crs=extent_crs or crs,
|
|
298
|
-
lowerCorner=[extent[0], extent[1]],
|
|
299
|
-
upperCorner=[extent[2], extent[3]],
|
|
300
|
-
)
|
|
431
|
+
is_inverted = crs_axis_inverted(crs)
|
|
301
432
|
|
|
302
433
|
if extent_crs:
|
|
303
434
|
transform = Transformer.from_crs(extent_crs, crs, always_xy=True)
|
|
304
|
-
|
|
305
|
-
bbox = BoundingBox(
|
|
306
|
-
*transform.transform_bounds(left, bottom, right, top, densify_pts=21)
|
|
307
|
-
)
|
|
308
|
-
else:
|
|
309
|
-
bbox = BoundingBox(*extent)
|
|
435
|
+
extent = transform.transform_bounds(*extent, densify_pts=21)
|
|
310
436
|
|
|
437
|
+
bbox = BoundingBox(*extent)
|
|
311
438
|
x_origin = bbox.left if not is_inverted else bbox.top
|
|
312
439
|
y_origin = bbox.top if not is_inverted else bbox.left
|
|
313
|
-
|
|
314
440
|
width = abs(bbox.right - bbox.left)
|
|
315
441
|
height = abs(bbox.top - bbox.bottom)
|
|
316
442
|
mpu = meters_per_unit(crs)
|
|
443
|
+
|
|
444
|
+
tile_matrices: List[TileMatrix] = []
|
|
317
445
|
for zoom in range(minzoom, maxzoom + 1):
|
|
318
446
|
res = max(
|
|
319
447
|
width / (tile_width * matrix_scale[0]) / 2.0**zoom,
|
|
320
448
|
height / (tile_height * matrix_scale[1]) / 2.0**zoom,
|
|
321
449
|
)
|
|
322
|
-
|
|
450
|
+
tile_matrices.append(
|
|
323
451
|
TileMatrix(
|
|
324
452
|
**{
|
|
325
|
-
"
|
|
453
|
+
"id": str(zoom),
|
|
326
454
|
"scaleDenominator": res * mpu / 0.00028,
|
|
327
|
-
"
|
|
455
|
+
"cellSize": res,
|
|
456
|
+
"pointOfOrigin": [x_origin, y_origin],
|
|
328
457
|
"tileWidth": tile_width,
|
|
329
458
|
"tileHeight": tile_height,
|
|
330
459
|
"matrixWidth": matrix_scale[0] * 2**zoom,
|
|
@@ -333,22 +462,29 @@ class TileMatrixSet(BaseModel):
|
|
|
333
462
|
)
|
|
334
463
|
)
|
|
335
464
|
|
|
336
|
-
return cls(
|
|
465
|
+
return cls(
|
|
466
|
+
crs=crs,
|
|
467
|
+
tileMatrices=tile_matrices,
|
|
468
|
+
id=id,
|
|
469
|
+
title=title,
|
|
470
|
+
_geographic_crs=geographic_crs,
|
|
471
|
+
**kwargs,
|
|
472
|
+
)
|
|
337
473
|
|
|
338
474
|
def matrix(self, zoom: int) -> TileMatrix:
|
|
339
475
|
"""Return the TileMatrix for a specific zoom."""
|
|
340
|
-
for m in self.
|
|
341
|
-
if m.
|
|
476
|
+
for m in self.tileMatrices:
|
|
477
|
+
if m.id == str(zoom):
|
|
342
478
|
return m
|
|
343
479
|
|
|
344
480
|
matrix_scale = list(
|
|
345
481
|
{
|
|
346
482
|
round(
|
|
347
|
-
self.
|
|
348
|
-
/ self.
|
|
483
|
+
self.tileMatrices[idx].scaleDenominator
|
|
484
|
+
/ self.tileMatrices[idx - 1].scaleDenominator,
|
|
349
485
|
2,
|
|
350
486
|
)
|
|
351
|
-
for idx in range(1, len(self.
|
|
487
|
+
for idx in range(1, len(self.tileMatrices))
|
|
352
488
|
}
|
|
353
489
|
)
|
|
354
490
|
if len(matrix_scale) > 1:
|
|
@@ -361,14 +497,15 @@ class TileMatrixSet(BaseModel):
|
|
|
361
497
|
UserWarning,
|
|
362
498
|
)
|
|
363
499
|
|
|
364
|
-
tile_matrix = self.
|
|
500
|
+
tile_matrix = self.tileMatrices[-1]
|
|
365
501
|
factor = 1 / matrix_scale[0]
|
|
366
|
-
while not str(zoom) == tile_matrix.
|
|
502
|
+
while not str(zoom) == tile_matrix.id:
|
|
367
503
|
tile_matrix = TileMatrix(
|
|
368
504
|
**{
|
|
369
|
-
"
|
|
505
|
+
"id": str(int(tile_matrix.id) + 1),
|
|
370
506
|
"scaleDenominator": tile_matrix.scaleDenominator / factor,
|
|
371
|
-
"
|
|
507
|
+
"cellSize": tile_matrix.cellSize / factor,
|
|
508
|
+
"pointOfOrigin": tile_matrix.pointOfOrigin,
|
|
372
509
|
"tileWidth": tile_matrix.tileWidth,
|
|
373
510
|
"tileHeight": tile_matrix.tileHeight,
|
|
374
511
|
"matrixWidth": int(tile_matrix.matrixWidth * factor),
|
|
@@ -515,10 +652,10 @@ class TileMatrixSet(BaseModel):
|
|
|
515
652
|
res = self._resolution(matrix)
|
|
516
653
|
|
|
517
654
|
origin_x = (
|
|
518
|
-
matrix.
|
|
655
|
+
matrix.pointOfOrigin[1] if self._invert_axis else matrix.pointOfOrigin[0]
|
|
519
656
|
)
|
|
520
657
|
origin_y = (
|
|
521
|
-
matrix.
|
|
658
|
+
matrix.pointOfOrigin[0] if self._invert_axis else matrix.pointOfOrigin[1]
|
|
522
659
|
)
|
|
523
660
|
|
|
524
661
|
xtile = (
|
|
@@ -587,10 +724,10 @@ class TileMatrixSet(BaseModel):
|
|
|
587
724
|
res = self._resolution(matrix)
|
|
588
725
|
|
|
589
726
|
origin_x = (
|
|
590
|
-
matrix.
|
|
727
|
+
matrix.pointOfOrigin[1] if self._invert_axis else matrix.pointOfOrigin[0]
|
|
591
728
|
)
|
|
592
729
|
origin_y = (
|
|
593
|
-
matrix.
|
|
730
|
+
matrix.pointOfOrigin[0] if self._invert_axis else matrix.pointOfOrigin[1]
|
|
594
731
|
)
|
|
595
732
|
|
|
596
733
|
xcoord = origin_x + t.x * res * matrix.tileWidth
|
|
@@ -656,50 +793,19 @@ class TileMatrixSet(BaseModel):
|
|
|
656
793
|
@property
|
|
657
794
|
def xy_bbox(self):
|
|
658
795
|
"""Return TMS bounding box in TileMatrixSet's CRS."""
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
self.boundingBox.lowerCorner[1]
|
|
662
|
-
if self._invert_axis
|
|
663
|
-
else self.boundingBox.lowerCorner[0]
|
|
664
|
-
)
|
|
665
|
-
bottom = (
|
|
666
|
-
self.boundingBox.lowerCorner[0]
|
|
667
|
-
if self._invert_axis
|
|
668
|
-
else self.boundingBox.lowerCorner[1]
|
|
669
|
-
)
|
|
670
|
-
right = (
|
|
671
|
-
self.boundingBox.upperCorner[1]
|
|
672
|
-
if self._invert_axis
|
|
673
|
-
else self.boundingBox.upperCorner[0]
|
|
674
|
-
)
|
|
675
|
-
top = (
|
|
676
|
-
self.boundingBox.upperCorner[0]
|
|
677
|
-
if self._invert_axis
|
|
678
|
-
else self.boundingBox.upperCorner[1]
|
|
679
|
-
)
|
|
680
|
-
if self.boundingBox.crs != self.crs:
|
|
681
|
-
transform = Transformer.from_crs(
|
|
682
|
-
self.boundingBox.crs, self.crs, always_xy=True
|
|
683
|
-
)
|
|
684
|
-
left, bottom, right, top = transform.transform_bounds(
|
|
685
|
-
left,
|
|
686
|
-
bottom,
|
|
687
|
-
right,
|
|
688
|
-
top,
|
|
689
|
-
densify_pts=21,
|
|
690
|
-
)
|
|
796
|
+
zoom = self.minzoom
|
|
797
|
+
matrix = self.matrix(zoom)
|
|
691
798
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
matrix = self.matrix(zoom)
|
|
695
|
-
left, top = self._ul(Tile(0, 0, zoom))
|
|
696
|
-
right, bottom = self._ul(
|
|
697
|
-
Tile(matrix.matrixWidth, matrix.matrixHeight, zoom)
|
|
698
|
-
)
|
|
799
|
+
left, top = self._ul(Tile(0, 0, zoom))
|
|
800
|
+
right, bottom = self._ul(Tile(matrix.matrixWidth, matrix.matrixHeight, zoom))
|
|
699
801
|
|
|
700
802
|
return BoundingBox(left, bottom, right, top)
|
|
701
803
|
|
|
702
804
|
@property
|
|
805
|
+
@cached( # type: ignore
|
|
806
|
+
LRUCache(maxsize=512),
|
|
807
|
+
key=lambda self: hashkey(self.id, self.tileMatrices[0].pointOfOrigin),
|
|
808
|
+
)
|
|
703
809
|
def bbox(self):
|
|
704
810
|
"""Return TMS bounding box in geographic coordinate reference system."""
|
|
705
811
|
left, bottom, right, top = self.xy_bbox
|
|
@@ -744,7 +850,7 @@ class TileMatrixSet(BaseModel):
|
|
|
744
850
|
zooms : int or sequence of int
|
|
745
851
|
One or more zoom levels.
|
|
746
852
|
truncate : bool, optional
|
|
747
|
-
Whether or not to truncate inputs to
|
|
853
|
+
Whether or not to truncate inputs to TMS limits.
|
|
748
854
|
|
|
749
855
|
Yields
|
|
750
856
|
------
|
|
@@ -778,13 +884,18 @@ class TileMatrixSet(BaseModel):
|
|
|
778
884
|
n = min(self.bbox.top, n)
|
|
779
885
|
|
|
780
886
|
for z in zooms:
|
|
781
|
-
|
|
887
|
+
nw_tile = self.tile(
|
|
782
888
|
w + LL_EPSILON, n - LL_EPSILON, z
|
|
783
889
|
) # Not in mercantile
|
|
784
|
-
|
|
890
|
+
se_tile = self.tile(e - LL_EPSILON, s + LL_EPSILON, z)
|
|
891
|
+
|
|
892
|
+
minx = min(nw_tile.x, se_tile.x)
|
|
893
|
+
maxx = max(nw_tile.x, se_tile.x)
|
|
894
|
+
miny = min(nw_tile.y, se_tile.y)
|
|
895
|
+
maxy = max(nw_tile.y, se_tile.y)
|
|
785
896
|
|
|
786
|
-
for i in range(
|
|
787
|
-
for j in range(
|
|
897
|
+
for i in range(minx, maxx + 1):
|
|
898
|
+
for j in range(miny, maxy + 1):
|
|
788
899
|
yield Tile(i, j, z)
|
|
789
900
|
|
|
790
901
|
def feature(
|
|
@@ -851,7 +962,7 @@ class TileMatrixSet(BaseModel):
|
|
|
851
962
|
"geometry": geom,
|
|
852
963
|
"properties": {
|
|
853
964
|
"title": f"XYZ tile {xyz}",
|
|
854
|
-
"grid_name": self.
|
|
965
|
+
"grid_name": self.id,
|
|
855
966
|
"grid_crs": self.crs.to_string(),
|
|
856
967
|
},
|
|
857
968
|
}
|
|
@@ -1063,8 +1174,8 @@ class TileMatrixSet(BaseModel):
|
|
|
1063
1174
|
tile : Tile or sequence of int
|
|
1064
1175
|
May be be either an instance of Tile or 3 ints, X, Y, Z.
|
|
1065
1176
|
zoom : int, optional
|
|
1066
|
-
Determines the *zoom* level of the returned
|
|
1067
|
-
This defaults to one
|
|
1177
|
+
Determines the *zoom* level of the returned child tiles.
|
|
1178
|
+
This defaults to one higher than the tile (the immediate children).
|
|
1068
1179
|
|
|
1069
1180
|
Returns
|
|
1070
1181
|
-------
|
morecantile/scripts/cli.py
CHANGED
morecantile/utils.py
CHANGED
|
@@ -4,6 +4,7 @@ import math
|
|
|
4
4
|
from typing import Dict, List
|
|
5
5
|
|
|
6
6
|
from pyproj import CRS
|
|
7
|
+
from pyproj.enums import WktVersion
|
|
7
8
|
|
|
8
9
|
from morecantile.commons import BoundingBox, Coords, Tile
|
|
9
10
|
from morecantile.errors import TileArgParsingError
|
|
@@ -100,3 +101,14 @@ def check_quadkey_support(tms: List) -> bool:
|
|
|
100
101
|
for i, t in enumerate(tms[:-1])
|
|
101
102
|
]
|
|
102
103
|
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def to_rasterio_crs(incrs: CRS):
|
|
107
|
+
"""Convert a pyproj CRS to a rasterio CRS"""
|
|
108
|
+
from rasterio import crs
|
|
109
|
+
from rasterio.env import GDALVersion
|
|
110
|
+
|
|
111
|
+
if GDALVersion.runtime().major < 3:
|
|
112
|
+
return crs.CRS.from_wkt(incrs.to_wkt(WktVersion.WKT1_GDAL))
|
|
113
|
+
else:
|
|
114
|
+
return crs.CRS.from_wkt(incrs.to_wkt())
|