morecantile 3.2.5__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 +11 -13
- morecantile/errors.py +4 -0
- morecantile/models.py +299 -186
- morecantile/scripts/cli.py +5 -5
- morecantile/utils.py +17 -5
- {morecantile-3.2.5.dist-info → morecantile-4.0.0a0.dist-info}/METADATA +60 -27
- morecantile-4.0.0a0.dist-info/RECORD +26 -0
- {morecantile-3.2.5.dist-info → morecantile-4.0.0a0.dist-info}/WHEEL +1 -1
- morecantile/data/NZTM2000.json +0 -242
- morecantile-3.2.5.dist-info/RECORD +0 -27
- {morecantile-3.2.5.dist-info → morecantile-4.0.0a0.dist-info}/LICENSE +0 -0
- {morecantile-3.2.5.dist-info → morecantile-4.0.0a0.dist-info}/entry_points.txt +0 -0
morecantile/models.py
CHANGED
|
@@ -2,26 +2,29 @@
|
|
|
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
|
-
from .commons import BoundingBox, Coords, Tile
|
|
13
|
-
from .errors import (
|
|
13
|
+
from morecantile.commons import BoundingBox, Coords, Tile
|
|
14
|
+
from morecantile.errors import (
|
|
15
|
+
DeprecationError,
|
|
14
16
|
InvalidZoomError,
|
|
15
17
|
NoQuadkeySupport,
|
|
16
18
|
PointOutsideTMSBounds,
|
|
17
19
|
QuadKeyError,
|
|
18
20
|
)
|
|
19
|
-
from .utils import (
|
|
21
|
+
from morecantile.utils import (
|
|
20
22
|
_parse_tile_arg,
|
|
21
23
|
bbox_to_feature,
|
|
22
24
|
check_quadkey_support,
|
|
23
25
|
meters_per_unit,
|
|
24
26
|
point_in_bbox,
|
|
27
|
+
to_rasterio_crs,
|
|
25
28
|
)
|
|
26
29
|
|
|
27
30
|
NumType = Union[float, int]
|
|
@@ -43,7 +46,8 @@ class CRSType(CRS, str):
|
|
|
43
46
|
@classmethod
|
|
44
47
|
def validate(cls, value: Union[CRS, str]) -> CRS:
|
|
45
48
|
"""Validate CRS."""
|
|
46
|
-
# If input is a string we
|
|
49
|
+
# If input is a string we translate it to CRS
|
|
50
|
+
# TODO: add NotImplementedError for ISO 19115
|
|
47
51
|
if not isinstance(value, CRS):
|
|
48
52
|
return CRS.from_user_input(value)
|
|
49
53
|
|
|
@@ -89,6 +93,11 @@ def crs_axis_inverted(crs: CRS) -> bool:
|
|
|
89
93
|
return crs.axis_info[0].abbrev.upper() in ["Y", "LAT", "N"]
|
|
90
94
|
|
|
91
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
|
+
|
|
92
101
|
class TMSBoundingBox(BaseModel):
|
|
93
102
|
"""Bounding box"""
|
|
94
103
|
|
|
@@ -104,44 +113,124 @@ class TMSBoundingBox(BaseModel):
|
|
|
104
113
|
json_encoders = {CRS: lambda v: CRS_to_uri(v)}
|
|
105
114
|
|
|
106
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
|
+
|
|
107
128
|
class TileMatrix(BaseModel):
|
|
108
|
-
"""Tile
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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")
|
|
121
186
|
|
|
122
187
|
class Config:
|
|
123
|
-
"""Forbid additional items like
|
|
188
|
+
"""Forbid additional items like variableMatrixWidths."""
|
|
124
189
|
|
|
125
190
|
extra = "forbid"
|
|
126
191
|
|
|
127
192
|
|
|
128
193
|
class TileMatrixSet(BaseModel):
|
|
129
|
-
"""Tile
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
)
|
|
140
231
|
|
|
141
232
|
# Private attributes
|
|
142
233
|
_is_quadtree: bool = PrivateAttr()
|
|
143
|
-
|
|
144
|
-
# CRS transformation attributes
|
|
145
234
|
_geographic_crs: CRSType = PrivateAttr(default=WGS84_CRS)
|
|
146
235
|
_to_geographic: Transformer = PrivateAttr()
|
|
147
236
|
_from_geographic: Transformer = PrivateAttr()
|
|
@@ -154,18 +243,23 @@ class TileMatrixSet(BaseModel):
|
|
|
154
243
|
|
|
155
244
|
def __init__(self, **data):
|
|
156
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
|
+
|
|
157
251
|
super().__init__(**data)
|
|
158
252
|
|
|
159
|
-
self._is_quadtree = check_quadkey_support(self.
|
|
253
|
+
self._is_quadtree = check_quadkey_support(self.tileMatrices)
|
|
160
254
|
|
|
161
255
|
self._geographic_crs = data.get("_geographic_crs", WGS84_CRS)
|
|
162
256
|
|
|
163
257
|
try:
|
|
164
258
|
self._to_geographic = Transformer.from_crs(
|
|
165
|
-
self.
|
|
259
|
+
self.crs, self._geographic_crs, always_xy=True
|
|
166
260
|
)
|
|
167
261
|
self._from_geographic = Transformer.from_crs(
|
|
168
|
-
self._geographic_crs, self.
|
|
262
|
+
self._geographic_crs, self.crs, always_xy=True
|
|
169
263
|
)
|
|
170
264
|
except ProjError:
|
|
171
265
|
warnings.warn(
|
|
@@ -176,51 +270,103 @@ class TileMatrixSet(BaseModel):
|
|
|
176
270
|
self._to_geographic = None
|
|
177
271
|
self._from_geographic = None
|
|
178
272
|
|
|
179
|
-
@validator("
|
|
273
|
+
@validator("tileMatrices")
|
|
180
274
|
def sort_tile_matrices(cls, v):
|
|
181
275
|
"""Sort matrices by identifier"""
|
|
182
|
-
return sorted(v, key=lambda m: int(m.
|
|
276
|
+
return sorted(v, key=lambda m: int(m.id))
|
|
183
277
|
|
|
184
278
|
def __iter__(self):
|
|
185
279
|
"""Iterate over matrices"""
|
|
186
|
-
for matrix in self.
|
|
280
|
+
for matrix in self.tileMatrices:
|
|
187
281
|
yield matrix
|
|
188
282
|
|
|
189
283
|
def __repr__(self):
|
|
190
284
|
"""Simplify default pydantic model repr."""
|
|
191
|
-
return f"<TileMatrixSet title='{self.title}'
|
|
285
|
+
return f"<TileMatrixSet title='{self.title}' id='{self.id}'>"
|
|
192
286
|
|
|
193
287
|
@property
|
|
194
|
-
def
|
|
195
|
-
"""
|
|
196
|
-
return self.
|
|
288
|
+
def geographic_crs(self) -> CRSType:
|
|
289
|
+
"""Return the TMS's geographic CRS."""
|
|
290
|
+
return self._geographic_crs
|
|
197
291
|
|
|
198
292
|
@property
|
|
199
293
|
def rasterio_crs(self):
|
|
200
294
|
"""Return rasterio CRS."""
|
|
295
|
+
return to_rasterio_crs(self.crs)
|
|
201
296
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return rasterio.crs.CRS.from_wkt(self.crs.to_wkt(WktVersion.WKT1_GDAL))
|
|
207
|
-
else:
|
|
208
|
-
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)
|
|
209
301
|
|
|
210
302
|
@property
|
|
211
303
|
def minzoom(self) -> int:
|
|
212
304
|
"""TileMatrixSet minimum TileMatrix identifier"""
|
|
213
|
-
return int(self.
|
|
305
|
+
return int(self.tileMatrices[0].id)
|
|
214
306
|
|
|
215
307
|
@property
|
|
216
308
|
def maxzoom(self) -> int:
|
|
217
309
|
"""TileMatrixSet maximum TileMatrix identifier"""
|
|
218
|
-
return int(self.
|
|
310
|
+
return int(self.tileMatrices[-1].id)
|
|
219
311
|
|
|
220
312
|
@property
|
|
221
313
|
def _invert_axis(self) -> bool:
|
|
222
314
|
"""Check if CRS has inverted AXIS (lat,lon) instead of (lon,lat)."""
|
|
223
|
-
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)
|
|
224
370
|
|
|
225
371
|
@classmethod
|
|
226
372
|
def custom(
|
|
@@ -229,13 +375,15 @@ class TileMatrixSet(BaseModel):
|
|
|
229
375
|
crs: CRS,
|
|
230
376
|
tile_width: int = 256,
|
|
231
377
|
tile_height: int = 256,
|
|
232
|
-
matrix_scale: List =
|
|
378
|
+
matrix_scale: Optional[List] = None,
|
|
233
379
|
extent_crs: Optional[CRS] = None,
|
|
234
380
|
minzoom: int = 0,
|
|
235
381
|
maxzoom: int = 24,
|
|
236
|
-
title: str =
|
|
237
|
-
|
|
382
|
+
title: Optional[str] = None,
|
|
383
|
+
id: Optional[str] = None,
|
|
384
|
+
ordered_axes: Optional[List[str]] = None,
|
|
238
385
|
geographic_crs: CRS = WGS84_CRS,
|
|
386
|
+
**kwargs: Any,
|
|
239
387
|
):
|
|
240
388
|
"""
|
|
241
389
|
Construct a custom TileMatrixSet.
|
|
@@ -261,118 +409,109 @@ class TileMatrixSet(BaseModel):
|
|
|
261
409
|
Tile Matrix Set minimum zoom level (default is 0).
|
|
262
410
|
maxzoom: int
|
|
263
411
|
Tile Matrix Set maximum zoom level (default is 24).
|
|
264
|
-
title: str
|
|
265
|
-
Tile Matrix Set title
|
|
266
|
-
|
|
267
|
-
Tile Matrix Set identifier
|
|
412
|
+
title: str, optional
|
|
413
|
+
Tile Matrix Set title
|
|
414
|
+
id: str, optional
|
|
415
|
+
Tile Matrix Set identifier
|
|
268
416
|
geographic_crs: pyproj.CRS
|
|
269
417
|
Geographic (lat,lon) coordinate reference system (default is EPSG:4326)
|
|
418
|
+
kwargs: Any
|
|
419
|
+
Attributes to forward to the TileMatrixSet
|
|
270
420
|
|
|
271
421
|
Returns:
|
|
272
422
|
--------
|
|
273
423
|
TileMatrixSet
|
|
274
424
|
|
|
275
425
|
"""
|
|
276
|
-
|
|
277
|
-
"title": title,
|
|
278
|
-
"identifier": identifier,
|
|
279
|
-
"supportedCRS": crs,
|
|
280
|
-
"tileMatrix": [],
|
|
281
|
-
"_geographic_crs": geographic_crs,
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
is_inverted = crs_axis_inverted(crs)
|
|
426
|
+
matrix_scale = matrix_scale or [1, 1]
|
|
285
427
|
|
|
286
|
-
if
|
|
287
|
-
|
|
288
|
-
crs=extent_crs or crs,
|
|
289
|
-
lowerCorner=[extent[1], extent[0]],
|
|
290
|
-
upperCorner=[extent[3], extent[2]],
|
|
291
|
-
)
|
|
428
|
+
if ordered_axes:
|
|
429
|
+
is_inverted = ordered_axis_inverted(ordered_axes)
|
|
292
430
|
else:
|
|
293
|
-
|
|
294
|
-
crs=extent_crs or crs,
|
|
295
|
-
lowerCorner=[extent[0], extent[1]],
|
|
296
|
-
upperCorner=[extent[2], extent[3]],
|
|
297
|
-
)
|
|
431
|
+
is_inverted = crs_axis_inverted(crs)
|
|
298
432
|
|
|
299
433
|
if extent_crs:
|
|
300
434
|
transform = Transformer.from_crs(extent_crs, crs, always_xy=True)
|
|
301
|
-
|
|
302
|
-
bbox = BoundingBox(
|
|
303
|
-
*transform.transform_bounds(left, bottom, right, top, densify_pts=21)
|
|
304
|
-
)
|
|
305
|
-
else:
|
|
306
|
-
bbox = BoundingBox(*extent)
|
|
435
|
+
extent = transform.transform_bounds(*extent, densify_pts=21)
|
|
307
436
|
|
|
437
|
+
bbox = BoundingBox(*extent)
|
|
308
438
|
x_origin = bbox.left if not is_inverted else bbox.top
|
|
309
439
|
y_origin = bbox.top if not is_inverted else bbox.left
|
|
310
|
-
|
|
311
440
|
width = abs(bbox.right - bbox.left)
|
|
312
441
|
height = abs(bbox.top - bbox.bottom)
|
|
313
442
|
mpu = meters_per_unit(crs)
|
|
443
|
+
|
|
444
|
+
tile_matrices: List[TileMatrix] = []
|
|
314
445
|
for zoom in range(minzoom, maxzoom + 1):
|
|
315
446
|
res = max(
|
|
316
447
|
width / (tile_width * matrix_scale[0]) / 2.0**zoom,
|
|
317
448
|
height / (tile_height * matrix_scale[1]) / 2.0**zoom,
|
|
318
449
|
)
|
|
319
|
-
|
|
450
|
+
tile_matrices.append(
|
|
320
451
|
TileMatrix(
|
|
321
|
-
**
|
|
322
|
-
|
|
323
|
-
scaleDenominator
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
452
|
+
**{
|
|
453
|
+
"id": str(zoom),
|
|
454
|
+
"scaleDenominator": res * mpu / 0.00028,
|
|
455
|
+
"cellSize": res,
|
|
456
|
+
"pointOfOrigin": [x_origin, y_origin],
|
|
457
|
+
"tileWidth": tile_width,
|
|
458
|
+
"tileHeight": tile_height,
|
|
459
|
+
"matrixWidth": matrix_scale[0] * 2**zoom,
|
|
460
|
+
"matrixHeight": matrix_scale[1] * 2**zoom,
|
|
461
|
+
}
|
|
330
462
|
)
|
|
331
463
|
)
|
|
332
464
|
|
|
333
|
-
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
|
+
)
|
|
334
473
|
|
|
335
474
|
def matrix(self, zoom: int) -> TileMatrix:
|
|
336
475
|
"""Return the TileMatrix for a specific zoom."""
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
2,
|
|
348
|
-
)
|
|
349
|
-
for idx in range(1, len(self.tileMatrix))
|
|
350
|
-
}
|
|
351
|
-
)
|
|
352
|
-
if len(matrix_scale) > 1:
|
|
353
|
-
raise Exception(
|
|
354
|
-
f"TileMatrix not found for level: {zoom} - Unable to construct tileMatrix for TMS with variable scale"
|
|
476
|
+
for m in self.tileMatrices:
|
|
477
|
+
if m.id == str(zoom):
|
|
478
|
+
return m
|
|
479
|
+
|
|
480
|
+
matrix_scale = list(
|
|
481
|
+
{
|
|
482
|
+
round(
|
|
483
|
+
self.tileMatrices[idx].scaleDenominator
|
|
484
|
+
/ self.tileMatrices[idx - 1].scaleDenominator,
|
|
485
|
+
2,
|
|
355
486
|
)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
487
|
+
for idx in range(1, len(self.tileMatrices))
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
if len(matrix_scale) > 1:
|
|
491
|
+
raise InvalidZoomError(
|
|
492
|
+
f"TileMatrix not found for level: {zoom} - Unable to construct tileMatrix for TMS with variable scale"
|
|
360
493
|
)
|
|
361
494
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
495
|
+
warnings.warn(
|
|
496
|
+
f"TileMatrix not found for level: {zoom} - Creating values from TMS Scale.",
|
|
497
|
+
UserWarning,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
tile_matrix = self.tileMatrices[-1]
|
|
501
|
+
factor = 1 / matrix_scale[0]
|
|
502
|
+
while not str(zoom) == tile_matrix.id:
|
|
503
|
+
tile_matrix = TileMatrix(
|
|
504
|
+
**{
|
|
505
|
+
"id": str(int(tile_matrix.id) + 1),
|
|
506
|
+
"scaleDenominator": tile_matrix.scaleDenominator / factor,
|
|
507
|
+
"cellSize": tile_matrix.cellSize / factor,
|
|
508
|
+
"pointOfOrigin": tile_matrix.pointOfOrigin,
|
|
509
|
+
"tileWidth": tile_matrix.tileWidth,
|
|
510
|
+
"tileHeight": tile_matrix.tileHeight,
|
|
511
|
+
"matrixWidth": int(tile_matrix.matrixWidth * factor),
|
|
512
|
+
"matrixHeight": int(tile_matrix.matrixHeight * factor),
|
|
513
|
+
}
|
|
514
|
+
)
|
|
376
515
|
|
|
377
516
|
return tile_matrix
|
|
378
517
|
|
|
@@ -513,10 +652,10 @@ class TileMatrixSet(BaseModel):
|
|
|
513
652
|
res = self._resolution(matrix)
|
|
514
653
|
|
|
515
654
|
origin_x = (
|
|
516
|
-
matrix.
|
|
655
|
+
matrix.pointOfOrigin[1] if self._invert_axis else matrix.pointOfOrigin[0]
|
|
517
656
|
)
|
|
518
657
|
origin_y = (
|
|
519
|
-
matrix.
|
|
658
|
+
matrix.pointOfOrigin[0] if self._invert_axis else matrix.pointOfOrigin[1]
|
|
520
659
|
)
|
|
521
660
|
|
|
522
661
|
xtile = (
|
|
@@ -585,10 +724,10 @@ class TileMatrixSet(BaseModel):
|
|
|
585
724
|
res = self._resolution(matrix)
|
|
586
725
|
|
|
587
726
|
origin_x = (
|
|
588
|
-
matrix.
|
|
727
|
+
matrix.pointOfOrigin[1] if self._invert_axis else matrix.pointOfOrigin[0]
|
|
589
728
|
)
|
|
590
729
|
origin_y = (
|
|
591
|
-
matrix.
|
|
730
|
+
matrix.pointOfOrigin[0] if self._invert_axis else matrix.pointOfOrigin[1]
|
|
592
731
|
)
|
|
593
732
|
|
|
594
733
|
xcoord = origin_x + t.x * res * matrix.tileWidth
|
|
@@ -654,50 +793,19 @@ class TileMatrixSet(BaseModel):
|
|
|
654
793
|
@property
|
|
655
794
|
def xy_bbox(self):
|
|
656
795
|
"""Return TMS bounding box in TileMatrixSet's CRS."""
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
self.boundingBox.lowerCorner[1]
|
|
660
|
-
if self._invert_axis
|
|
661
|
-
else self.boundingBox.lowerCorner[0]
|
|
662
|
-
)
|
|
663
|
-
bottom = (
|
|
664
|
-
self.boundingBox.lowerCorner[0]
|
|
665
|
-
if self._invert_axis
|
|
666
|
-
else self.boundingBox.lowerCorner[1]
|
|
667
|
-
)
|
|
668
|
-
right = (
|
|
669
|
-
self.boundingBox.upperCorner[1]
|
|
670
|
-
if self._invert_axis
|
|
671
|
-
else self.boundingBox.upperCorner[0]
|
|
672
|
-
)
|
|
673
|
-
top = (
|
|
674
|
-
self.boundingBox.upperCorner[0]
|
|
675
|
-
if self._invert_axis
|
|
676
|
-
else self.boundingBox.upperCorner[1]
|
|
677
|
-
)
|
|
678
|
-
if self.boundingBox.crs != self.crs:
|
|
679
|
-
transform = Transformer.from_crs(
|
|
680
|
-
self.boundingBox.crs, self.crs, always_xy=True
|
|
681
|
-
)
|
|
682
|
-
left, bottom, right, top = transform.transform_bounds(
|
|
683
|
-
left,
|
|
684
|
-
bottom,
|
|
685
|
-
right,
|
|
686
|
-
top,
|
|
687
|
-
densify_pts=21,
|
|
688
|
-
)
|
|
796
|
+
zoom = self.minzoom
|
|
797
|
+
matrix = self.matrix(zoom)
|
|
689
798
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
matrix = self.matrix(zoom)
|
|
693
|
-
left, top = self._ul(Tile(0, 0, zoom))
|
|
694
|
-
right, bottom = self._ul(
|
|
695
|
-
Tile(matrix.matrixWidth, matrix.matrixHeight, zoom)
|
|
696
|
-
)
|
|
799
|
+
left, top = self._ul(Tile(0, 0, zoom))
|
|
800
|
+
right, bottom = self._ul(Tile(matrix.matrixWidth, matrix.matrixHeight, zoom))
|
|
697
801
|
|
|
698
802
|
return BoundingBox(left, bottom, right, top)
|
|
699
803
|
|
|
700
804
|
@property
|
|
805
|
+
@cached( # type: ignore
|
|
806
|
+
LRUCache(maxsize=512),
|
|
807
|
+
key=lambda self: hashkey(self.id, self.tileMatrices[0].pointOfOrigin),
|
|
808
|
+
)
|
|
701
809
|
def bbox(self):
|
|
702
810
|
"""Return TMS bounding box in geographic coordinate reference system."""
|
|
703
811
|
left, bottom, right, top = self.xy_bbox
|
|
@@ -742,7 +850,7 @@ class TileMatrixSet(BaseModel):
|
|
|
742
850
|
zooms : int or sequence of int
|
|
743
851
|
One or more zoom levels.
|
|
744
852
|
truncate : bool, optional
|
|
745
|
-
Whether or not to truncate inputs to
|
|
853
|
+
Whether or not to truncate inputs to TMS limits.
|
|
746
854
|
|
|
747
855
|
Yields
|
|
748
856
|
------
|
|
@@ -776,20 +884,25 @@ class TileMatrixSet(BaseModel):
|
|
|
776
884
|
n = min(self.bbox.top, n)
|
|
777
885
|
|
|
778
886
|
for z in zooms:
|
|
779
|
-
|
|
887
|
+
nw_tile = self.tile(
|
|
780
888
|
w + LL_EPSILON, n - LL_EPSILON, z
|
|
781
889
|
) # Not in mercantile
|
|
782
|
-
|
|
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)
|
|
783
896
|
|
|
784
|
-
for i in range(
|
|
785
|
-
for j in range(
|
|
897
|
+
for i in range(minx, maxx + 1):
|
|
898
|
+
for j in range(miny, maxy + 1):
|
|
786
899
|
yield Tile(i, j, z)
|
|
787
900
|
|
|
788
901
|
def feature(
|
|
789
902
|
self,
|
|
790
903
|
tile: Tile,
|
|
791
904
|
fid: Optional[str] = None,
|
|
792
|
-
props: Dict =
|
|
905
|
+
props: Optional[Dict] = None,
|
|
793
906
|
buffer: Optional[NumType] = None,
|
|
794
907
|
precision: Optional[int] = None,
|
|
795
908
|
projected: bool = False,
|
|
@@ -849,7 +962,7 @@ class TileMatrixSet(BaseModel):
|
|
|
849
962
|
"geometry": geom,
|
|
850
963
|
"properties": {
|
|
851
964
|
"title": f"XYZ tile {xyz}",
|
|
852
|
-
"grid_name": self.
|
|
965
|
+
"grid_name": self.id,
|
|
853
966
|
"grid_crs": self.crs.to_string(),
|
|
854
967
|
},
|
|
855
968
|
}
|
|
@@ -1061,8 +1174,8 @@ class TileMatrixSet(BaseModel):
|
|
|
1061
1174
|
tile : Tile or sequence of int
|
|
1062
1175
|
May be be either an instance of Tile or 3 ints, X, Y, Z.
|
|
1063
1176
|
zoom : int, optional
|
|
1064
|
-
Determines the *zoom* level of the returned
|
|
1065
|
-
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).
|
|
1066
1179
|
|
|
1067
1180
|
Returns
|
|
1068
1181
|
-------
|