diff --git a/.gitignore b/.gitignore index b6e4761..23077b4 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# PyCharm: +.idea diff --git a/CHANGES.md b/CHANGES.md index c21ff75..0fc537a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,27 @@ +## 2.1.4 (2021-08-20) + +* add **NZTM2000Quad** tile matrix set from LINZ (author @blacha, https://github.com/developmentseed/morecantile/pull/57) +* add **quadkey** supports (@author adrian-knauer, https://github.com/developmentseed/morecantile/pull/56) + + ```python + import morecantile + + tms = morecantile.tms.get("WebMercatorQuad") + + # Tile to Quadkey + tms.quadkey(486, 332, 10) + >>> "0313102310" + + # Quadkey to Tile + tms.quadkey_to_tile("0313102310") + >>> Tile(486, 332, 10) + ``` + +* update `NZTM2000*` CRS uri from `https://www.opengis.net/def/crs/EPSG/0/2193` to `urn:ogc:def:crs:EPSG:2193` (https://github.com/developmentseed/morecantile/pull/61) + +## 2.1.3 - Doesn't exists + ## 2.1.2 (2021-05-18) * fix wrong TMS boundingBox definition when using inverted CRS (https://github.com/developmentseed/morecantile/pull/53) diff --git a/README.md b/README.md index 0f44462..e6bfdd1 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ $ pip install git+https://github.com/developmentseed/morecantile.git - **EuropeanETRS89_LAEAQuad**: ETRS89-extended / LAEA Europe - EPGS:3035 - **LINZAntarticaMapTilegrid**: LINZ Antarctic Map Tile Grid (Ross Sea Region) - EPSG:5482 - **NZTM2000**: LINZ NZTM2000 Map Tile Grid - EPSG:2193 +- **NZTM2000Quad**: LINZ NZTM2000 Map Tile Grid - EPSG:2193 - **UPSAntarcticWGS84Quad**: Universal Polar Stereographic WGS 84 Quad for Antarctic - EPSG:5042 - **UPSArcticWGS84Quad**: Universal Polar Stereographic WGS 84 Quad for Arctic - EPSG:5041 - **UTM31WGS84Quad**: Example of UTM grid - EPSG:32631 diff --git a/docs/cli.md b/docs/cli.md index 8ace9d0..f9baace 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -51,6 +51,7 @@ Options: - CanadianNAD83_LCC - UPSArcticWGS84Quad - NZTM2000 + - NZTM2000Quad - UTM31WGS84Quad - UPSAntarcticWGS84Quad - WorldMercatorWGS84Quad @@ -91,6 +92,7 @@ Options: - CanadianNAD83_LCC - UPSArcticWGS84Quad - NZTM2000 + - NZTM2000Quad - UTM31WGS84Quad - UPSAntarcticWGS84Quad - WorldMercatorWGS84Quad @@ -149,6 +151,7 @@ Options: - CanadianNAD83_LCC - UPSArcticWGS84Quad - NZTM2000 + - NZTM2000Quad - UTM31WGS84Quad - UPSAntarcticWGS84Quad - WorldMercatorWGS84Quad diff --git a/docs/usage.md b/docs/usage.md index fe5decd..a1d8487 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -10,6 +10,7 @@ print(morecantile.tms.list()) 'CanadianNAD83_LCC', 'UPSArcticWGS84Quad', 'NZTM2000', + 'NZTM2000Quad', 'UTM31WGS84Quad', 'UPSAntarcticWGS84Quad', 'WorldMercatorWGS84Quad', diff --git a/morecantile/__init__.py b/morecantile/__init__.py index 91cf2a6..018f972 100644 --- a/morecantile/__init__.py +++ b/morecantile/__init__.py @@ -8,7 +8,7 @@ """ -__version__ = "2.1.2" +__version__ = "2.1.4" from .commons import BoundingBox, Coords, Tile # noqa from .defaults import tms # noqa diff --git a/morecantile/data/CanadianNAD83_LCC.json b/morecantile/data/CanadianNAD83_LCC.json index a6db991..e71ac4c 100644 --- a/morecantile/data/CanadianNAD83_LCC.json +++ b/morecantile/data/CanadianNAD83_LCC.json @@ -4,7 +4,7 @@ "identifier": "CanadianNAD83_LCC", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/3978", + "crs": "urn:ogc:def:crs:EPSG::3978", "lowerCorner": [ -7786476.885838887, -5153821.09213678 @@ -14,7 +14,7 @@ 7928343.534071138 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/3978", + "supportedCRS": "urn:ogc:def:crs:EPSG::3978", "tileMatrix": [ { "type": "TileMatrixType", @@ -355,4 +355,4 @@ "matrixHeight": 2625811 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/EuropeanETRS89_LAEAQuad.json b/morecantile/data/EuropeanETRS89_LAEAQuad.json index b8c234e..ec56d52 100755 --- a/morecantile/data/EuropeanETRS89_LAEAQuad.json +++ b/morecantile/data/EuropeanETRS89_LAEAQuad.json @@ -4,7 +4,7 @@ "identifier": "EuropeanETRS89_LAEAQuad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/3035", + "crs": "urn:ogc:def:crs:EPSG::3035", "lowerCorner": [ 1000000.0, 2000000.0 @@ -14,7 +14,7 @@ 5500000.0 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/3035", + "supportedCRS": "urn:ogc:def:crs:EPSG::3035", "tileMatrix": [ { "type": "TileMatrixType", @@ -225,4 +225,4 @@ "matrixHeight": 32768 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/LINZAntarticaMapTilegrid.json b/morecantile/data/LINZAntarticaMapTilegrid.json index 959f6ed..594249b 100644 --- a/morecantile/data/LINZAntarticaMapTilegrid.json +++ b/morecantile/data/LINZAntarticaMapTilegrid.json @@ -2,7 +2,7 @@ "type": "TileMatrixSetType", "title": "LINZ Antarctic Map Tile Grid (Ross Sea Region)", "identifier": "LINZAntarticaMapTilegrid", - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/5482", + "supportedCRS": "urn:ogc:def:crs:EPSG::5482", "tileMatrix": [ { "type": "TileMatrixType", diff --git a/morecantile/data/NZTM2000.json b/morecantile/data/NZTM2000.json index 6cec2dd..62eb1a6 100644 --- a/morecantile/data/NZTM2000.json +++ b/morecantile/data/NZTM2000.json @@ -3,10 +3,10 @@ "title": "LINZ NZTM2000 Map Tile Grid", "abstract": "See https://www.linz.govt.nz/data/linz-data-service/guides-and-documentation/nztm2000-map-tile-service-schema", "identifier": "NZTM2000", - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/2193", + "supportedCRS": "urn:ogc:def:crs:EPSG::2193", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/2193", + "crs": "urn:ogc:def:crs:EPSG::2193", "lowerCorner": [ 3087000, 274000 @@ -239,4 +239,4 @@ "matrixHeight": 512000 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/NZTM2000Quad.json b/morecantile/data/NZTM2000Quad.json new file mode 100644 index 0000000..d8f01da --- /dev/null +++ b/morecantile/data/NZTM2000Quad.json @@ -0,0 +1,307 @@ +{ + "type": "TileMatrixSetType", + "title": "LINZ NZTM2000Quad Map Tile Grid", + "abstract": "See https://github.com/linz/NZTM2000TileMatrixSet", + "identifier": "NZTM2000Quad", + "supportedCRS": "urn:ogc:def:crs:EPSG::2193", + "boundingBox": { + "type": "BoundingBoxType", + "crs": "urn:ogc:def:crs:EPSG::2193", + "lowerCorner": [ + 419435.9938, + -3260586.7284 + ], + "upperCorner": [ + 10438190.1652, + 6758167.443 + ] + }, + "tileMatrix": [ + { + "type": "TileMatrixType", + "identifier": "0", + "scaleDenominator": 139770566.007179, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1, + "matrixHeight": 1 + }, + { + "type": "TileMatrixType", + "identifier": "1", + "scaleDenominator": 69885283.0035895, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2, + "matrixHeight": 2 + }, + { + "type": "TileMatrixType", + "identifier": "2", + "scaleDenominator": 34942641.50179475, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4, + "matrixHeight": 4 + }, + { + "type": "TileMatrixType", + "identifier": "3", + "scaleDenominator": 17471320.750897374, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8, + "matrixHeight": 8 + }, + { + "type": "TileMatrixType", + "identifier": "4", + "scaleDenominator": 8735660.375448687, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16, + "matrixHeight": 16 + }, + { + "type": "TileMatrixType", + "identifier": "5", + "scaleDenominator": 4367830.1877243435, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32, + "matrixHeight": 32 + }, + { + "type": "TileMatrixType", + "identifier": "6", + "scaleDenominator": 2183915.0938621718, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 64, + "matrixHeight": 64 + }, + { + "type": "TileMatrixType", + "identifier": "7", + "scaleDenominator": 1091957.5469310859, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 128, + "matrixHeight": 128 + }, + { + "type": "TileMatrixType", + "identifier": "8", + "scaleDenominator": 545978.7734655429, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 256, + "matrixHeight": 256 + }, + { + "type": "TileMatrixType", + "identifier": "9", + "scaleDenominator": 272989.38673277147, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 512, + "matrixHeight": 512 + }, + { + "type": "TileMatrixType", + "identifier": "10", + "scaleDenominator": 136494.69336638573, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1024, + "matrixHeight": 1024 + }, + { + "type": "TileMatrixType", + "identifier": "11", + "scaleDenominator": 68247.34668319287, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2048, + "matrixHeight": 2048 + }, + { + "type": "TileMatrixType", + "identifier": "12", + "scaleDenominator": 34123.67334159643, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4096, + "matrixHeight": 4096 + }, + { + "type": "TileMatrixType", + "identifier": "13", + "scaleDenominator": 17061.836670798217, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8192, + "matrixHeight": 8192 + }, + { + "type": "TileMatrixType", + "identifier": "14", + "scaleDenominator": 8530.918335399108, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16384, + "matrixHeight": 16384 + }, + { + "type": "TileMatrixType", + "identifier": "15", + "scaleDenominator": 4265.459167699554, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32768, + "matrixHeight": 32768 + }, + { + "type": "TileMatrixType", + "identifier": "16", + "scaleDenominator": 2132.729583849777, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 65536, + "matrixHeight": 65536 + }, + { + "type": "TileMatrixType", + "identifier": "17", + "scaleDenominator": 1066.3647919248886, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 131072, + "matrixHeight": 131072 + }, + { + "type": "TileMatrixType", + "identifier": "18", + "scaleDenominator": 533.1823959624443, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 262144, + "matrixHeight": 262144 + }, + { + "type": "TileMatrixType", + "identifier": "19", + "scaleDenominator": 266.59119798122214, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 524288, + "matrixHeight": 524288 + }, + { + "type": "TileMatrixType", + "identifier": "20", + "scaleDenominator": 133.29559899061107, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1048576, + "matrixHeight": 1048576 + }, + { + "type": "TileMatrixType", + "identifier": "21", + "scaleDenominator": 66.64779949530553, + "topLeftCorner": [ + 10438190.1652, + -3260586.7284 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2097152, + "matrixHeight": 2097152 + } + ] +} diff --git a/morecantile/data/UPSAntarcticWGS84Quad.json b/morecantile/data/UPSAntarcticWGS84Quad.json index 0764774..3c45dd4 100644 --- a/morecantile/data/UPSAntarcticWGS84Quad.json +++ b/morecantile/data/UPSAntarcticWGS84Quad.json @@ -4,7 +4,7 @@ "identifier": "UPSAntarcticWGS84Quad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/5042", + "crs": "urn:ogc:def:crs:EPSG::5042", "lowerCorner": [ -14440759.350252, -14440759.350252 @@ -14,7 +14,7 @@ 18440759.350252 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/5042", + "supportedCRS": "urn:ogc:def:crs:EPSG::5042", "tileMatrix": [ { "type": "TileMatrixType", @@ -342,4 +342,4 @@ "matrixHeight": 16777216 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/UPSArcticWGS84Quad.json b/morecantile/data/UPSArcticWGS84Quad.json index d9a13ae..b86fb88 100644 --- a/morecantile/data/UPSArcticWGS84Quad.json +++ b/morecantile/data/UPSArcticWGS84Quad.json @@ -4,7 +4,7 @@ "identifier": "UPSArcticWGS84Quad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/5041", + "crs": "urn:ogc:def:crs:EPSG::5041", "lowerCorner": [ -14440759.350252, -14440759.350252 @@ -14,7 +14,7 @@ 18440759.350252 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/5041", + "supportedCRS": "urn:ogc:def:crs:EPSG::5041", "tileMatrix": [ { "type": "TileMatrixType", @@ -342,4 +342,4 @@ "matrixHeight": 16777216 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/UTM31WGS84Quad.json b/morecantile/data/UTM31WGS84Quad.json index 90a37dc..1bfe0ed 100644 --- a/morecantile/data/UTM31WGS84Quad.json +++ b/morecantile/data/UTM31WGS84Quad.json @@ -4,7 +4,7 @@ "identifier": "UTM31WGS84Quad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/32631", + "crs": "urn:ogc:def:crs:EPSG::32631", "lowerCorner": [ -9501965.72931276, -20003931.4586255 @@ -14,7 +14,7 @@ 20003931.4586255 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/32631", + "supportedCRS": "urn:ogc:def:crs:EPSG::32631", "tileMatrix": [ { "type": "TileMatrixType", @@ -329,4 +329,4 @@ "matrixHeight": 16777216 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/WebMercatorQuad.json b/morecantile/data/WebMercatorQuad.json index 2da06e4..8952431 100755 --- a/morecantile/data/WebMercatorQuad.json +++ b/morecantile/data/WebMercatorQuad.json @@ -4,7 +4,7 @@ "identifier": "WebMercatorQuad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/3857", + "crs": "urn:ogc:def:crs:EPSG::3857", "lowerCorner": [ -20037508.3427892, -20037508.3427892 @@ -14,7 +14,7 @@ 20037508.3427892 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/3857", + "supportedCRS": "urn:ogc:def:crs:EPSG::3857", "wellKnownScaleSet": "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible", "tileMatrix": [ { @@ -343,4 +343,4 @@ "matrixHeight": 16777216 } ] -} \ No newline at end of file +} diff --git a/morecantile/data/WorldCRS84Quad.json b/morecantile/data/WorldCRS84Quad.json new file mode 100644 index 0000000..99eb93f --- /dev/null +++ b/morecantile/data/WorldCRS84Quad.json @@ -0,0 +1,255 @@ +{ + "type": "TileMatrixSetType", + "title": "CRS84 for the World", + "identifier": "WorldCRS84Quad", + "boundingBox": { + "type": "BoundingBoxType", + "crs": "urn:ogc:def:crs:OGC::CRS84", + "lowerCorner": [ + -180, + -90 + ], + "upperCorner": [ + 180, + 90 + ] + }, + "supportedCRS": "urn:ogc:def:crs:OGC::CRS84", + "wellKnownScaleSet": "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad", + "tileMatrix": [ + { + "type": "TileMatrixType", + "identifier": "0", + "scaleDenominator": 279541132.014358, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2, + "matrixHeight": 1 + }, + { + "type": "TileMatrixType", + "identifier": "1", + "scaleDenominator": 139770566.007179, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4, + "matrixHeight": 2 + }, + { + "type": "TileMatrixType", + "identifier": "2", + "scaleDenominator": 69885283.0035897, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8, + "matrixHeight": 4 + }, + { + "type": "TileMatrixType", + "identifier": "3", + "scaleDenominator": 34942641.5017948, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16, + "matrixHeight": 8 + }, + { + "type": "TileMatrixType", + "identifier": "4", + "scaleDenominator": 17471320.7508974, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32, + "matrixHeight": 16 + }, + { + "type": "TileMatrixType", + "identifier": "5", + "scaleDenominator": 8735660.37544871, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 64, + "matrixHeight": 32 + }, + { + "type": "TileMatrixType", + "identifier": "6", + "scaleDenominator": 4367830.18772435, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 128, + "matrixHeight": 64 + }, + { + "type": "TileMatrixType", + "identifier": "7", + "scaleDenominator": 2183915.09386217, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 256, + "matrixHeight": 128 + }, + { + "type": "TileMatrixType", + "identifier": "8", + "scaleDenominator": 1091957.54693108, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 512, + "matrixHeight": 256 + }, + { + "type": "TileMatrixType", + "identifier": "9", + "scaleDenominator": 545978.773465544, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 1024, + "matrixHeight": 512 + }, + { + "type": "TileMatrixType", + "identifier": "10", + "scaleDenominator": 272989.386732772, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 2048, + "matrixHeight": 1024 + }, + { + "type": "TileMatrixType", + "identifier": "11", + "scaleDenominator": 136494.693366386, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 4096, + "matrixHeight": 2048 + }, + { + "type": "TileMatrixType", + "identifier": "12", + "scaleDenominator": 68247.346683193, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 8192, + "matrixHeight": 4096 + }, + { + "type": "TileMatrixType", + "identifier": "13", + "scaleDenominator": 34123.6733415964, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 16384, + "matrixHeight": 8192 + }, + { + "type": "TileMatrixType", + "identifier": "14", + "scaleDenominator": 17061.8366707982, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 32768, + "matrixHeight": 16384 + }, + { + "type": "TileMatrixType", + "identifier": "15", + "scaleDenominator": 8530.91833539913, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 65536, + "matrixHeight": 32768 + }, + { + "type": "TileMatrixType", + "identifier": "16", + "scaleDenominator": 4265.45916769956, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 131072, + "matrixHeight": 65536 + }, + { + "type": "TileMatrixType", + "identifier": "17", + "scaleDenominator": 2132.72958384978, + "topLeftCorner": [ + -180, + 90 + ], + "tileWidth": 256, + "tileHeight": 256, + "matrixWidth": 262144, + "matrixHeight": 131072 + } + ] +} diff --git a/morecantile/data/WorldMercatorWGS84Quad.json b/morecantile/data/WorldMercatorWGS84Quad.json index 5fe9444..187c0f2 100755 --- a/morecantile/data/WorldMercatorWGS84Quad.json +++ b/morecantile/data/WorldMercatorWGS84Quad.json @@ -4,7 +4,7 @@ "identifier": "WorldMercatorWGS84Quad", "boundingBox": { "type": "BoundingBoxType", - "crs": "http://www.opengis.net/def/crs/EPSG/0/3395", + "crs": "urn:ogc:def:crs:EPSG::3395", "lowerCorner": [ -20037508.3427892, -20037508.3427892 @@ -14,7 +14,7 @@ 20037508.3427892 ] }, - "supportedCRS": "http://www.opengis.net/def/crs/EPSG/0/3395", + "supportedCRS": "urn:ogc:def:crs:EPSG::3395", "wellKnownScaleSet": "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84", "tileMatrix": [ { @@ -252,4 +252,4 @@ "matrixHeight": 131072 } ] -} \ No newline at end of file +} diff --git a/morecantile/errors.py b/morecantile/errors.py index e32626f..ee67d47 100644 --- a/morecantile/errors.py +++ b/morecantile/errors.py @@ -19,3 +19,11 @@ class TileArgParsingError(MorecantileError): class PointOutsideTMSBounds(UserWarning): """Point is outside TMS bounds.""" + + +class NoQuadkeySupport(MorecantileError): + """Raised when a custom TileMatrixSet doesn't support quadkeys""" + + +class QuadKeyError(MorecantileError): + """Raised when errors occur in computing or parsing quad keys""" diff --git a/morecantile/models.py b/morecantile/models.py index ff08044..3feb03b 100644 --- a/morecantile/models.py +++ b/morecantile/models.py @@ -7,13 +7,18 @@ from pydantic import AnyHttpUrl, BaseModel, Field, PrivateAttr, validator from pyproj import CRS, Transformer from pyproj.enums import WktVersion -from pyproj.exceptions import CRSError from .commons import BoundingBox, Coords, Tile -from .errors import InvalidIdentifier, PointOutsideTMSBounds +from .errors import ( + InvalidIdentifier, + NoQuadkeySupport, + PointOutsideTMSBounds, + QuadKeyError, +) from .utils import ( _parse_tile_arg, bbox_to_feature, + check_quadkey_support, meters_per_unit, point_in_bbox, truncate_lnglat, @@ -44,14 +49,11 @@ def __get_validators__(cls): yield cls.validate @classmethod - def validate(cls, value: Union[CRS, AnyHttpUrl]): + def validate(cls, value: Union[CRS, AnyHttpUrl]) -> CRS: """Validate CRS.""" # If input is a string we tranlate it to CRS if not isinstance(value, CRS): - try: - return CRS.from_user_input(value) - except CRSError: - return CRS.from_epsg(value.split("/")[-1]) + return CRS.from_user_input(value) return value @@ -133,6 +135,7 @@ class TileMatrixSet(BaseModel): wellKnownScaleSet: Optional[AnyHttpUrl] = None boundingBox: Optional[TMSBoundingBox] tileMatrix: List[TileMatrix] + _is_quadtree: bool = PrivateAttr() _to_wgs84: Transformer = PrivateAttr() _from_wgs84: Transformer = PrivateAttr() @@ -143,8 +146,9 @@ class Config: json_encoders = {CRS: lambda v: CRS_to_uri(v)} def __init__(self, **data): - """create PyProj transforms.""" + """Create PyProj transforms and check if TileMatrixSet supports quadkeys.""" super().__init__(**data) + self._is_quadtree = check_quadkey_support(self.tileMatrix) self._to_wgs84 = Transformer.from_crs( self.supportedCRS, WGS84_CRS, always_xy=True ) @@ -807,3 +811,61 @@ def feature( feat["id"] = fid return feat + + def quadkey(self, *tile: Tile) -> str: + """Get the quadkey of a tile + Parameters + ---------- + tile : Tile or sequence of int + May be be either an instance of Tile or 3 ints, X, Y, Z. + Returns + ------- + str + """ + if not self._is_quadtree: + raise NoQuadkeySupport( + "This Tile Matrix Set doesn't support 2 x 2 quadkeys." + ) + + tile = _parse_tile_arg(*tile) + qk = [] + for z in range(tile.z, self.minzoom, -1): + digit = 0 + mask = 1 << (z - 1) + if tile.x & mask: + digit += 1 + if tile.y & mask: + digit += 2 + qk.append(str(digit)) + + return "".join(qk) + + def quadkey_to_tile(self, qk: str) -> Tile: + """Get the tile corresponding to a quadkey + Parameters + ---------- + qk : str + A quadkey string. + Returns + ------- + Tile + """ + if not self._is_quadtree: + raise NoQuadkeySupport( + "This Tile Matrix Set doesn't support 2 x 2 quadkeys." + ) + if len(qk) == 0: + return Tile(0, 0, 0) + xtile, ytile = 0, 0 + for i, digit in enumerate(reversed(qk)): + mask = 1 << i + if digit == "1": + xtile = xtile | mask + elif digit == "2": + ytile = ytile | mask + elif digit == "3": + xtile = xtile | mask + ytile = ytile | mask + elif digit != "0": + raise QuadKeyError("Unexpected quadkey digit: %r", digit) + return Tile(xtile, ytile, i + 1) diff --git a/morecantile/utils.py b/morecantile/utils.py index 3d3611f..bf51d14 100644 --- a/morecantile/utils.py +++ b/morecantile/utils.py @@ -1,7 +1,7 @@ """morecantile utils.""" import math -from typing import Dict, Tuple +from typing import Dict, List, Tuple from pyproj import CRS @@ -94,3 +94,20 @@ def point_in_bbox(point: Coords, bbox: BoundingBox, precision: int = 5) -> bool: and round(point.y, precision) >= round(bbox.bottom, precision) and round(point.y, precision) <= round(bbox.top, precision) ) + + +def is_power_of_two(number: int) -> bool: + """Check if a number is a power of 2""" + return (number & (number - 1) == 0) and number != 0 + + +def check_quadkey_support(tms: List) -> bool: + """Check if a Tile Matrix Set supports quadkeys""" + return all( + [ + (t.matrixWidth == t.matrixHeight) + and is_power_of_two(t.matrixWidth) + and ((t.matrixWidth * 2) == tms[i + 1].matrixWidth) + for i, t in enumerate(tms[:-1]) + ] + ) diff --git a/setup.cfg b/setup.cfg index e02dd36..d5d6f26 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.2 +current_version = 2.1.4 commit = True tag = True tag_name = {new_version} diff --git a/setup.py b/setup.py index 3da18c4..720c107 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name="morecantile", - version="2.1.2", + version="2.1.4", python_requires=">=3.7", description=u"""Construct and use map tile grids (a.k.a TileMatrixSet / TMS).""", long_description=long_description, diff --git a/tests/test_models.py b/tests/test_models.py index 0538f02..b905d68 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -10,6 +10,7 @@ from pyproj import CRS import morecantile +from morecantile.commons import Tile from morecantile.errors import InvalidIdentifier from morecantile.models import TileMatrix, TileMatrixSet @@ -97,6 +98,52 @@ def test_load(): TileMatrixSet.load("ANotValidName") +def test_quadkey_support(): + tms = TileMatrixSet.load("CanadianNAD83_LCC") + assert not tms._is_quadtree + + tms = TileMatrixSet.load("UPSArcticWGS84Quad") + assert tms._is_quadtree + + +def test_quadkey(): + tms = morecantile.tms.get("WebMercatorQuad") + expected = "0313102310" + assert tms.quadkey(486, 332, 10) == expected + + +def test_quadkey_to_tile(): + tms = morecantile.tms.get("WebMercatorQuad") + qk = "0313102310" + expected = Tile(486, 332, 10) + assert tms.quadkey_to_tile(qk) == expected + + +def test_empty_quadkey_to_tile(): + tms = morecantile.tms.get("WebMercatorQuad") + qk = "" + expected = Tile(0, 0, 0) + assert tms.quadkey_to_tile(qk) == expected + + +def test_quadkey_failure(): + tms = morecantile.tms.get("WebMercatorQuad") + with pytest.raises(morecantile.errors.QuadKeyError): + tms.quadkey_to_tile("lolwut") + + +def test_quadkey_not_supported_failure(): + tms = TileMatrixSet.load("NZTM2000") + with pytest.raises(morecantile.errors.NoQuadkeySupport): + tms.quadkey(1, 1, 1) + + +def test_quadkey_to_tile_not_supported_failure(): + tms = TileMatrixSet.load("NZTM2000") + with pytest.raises(morecantile.errors.NoQuadkeySupport): + tms.quadkey_to_tile("3") + + def test_findMatrix(): """Should raise an error when TileMatrix is not found.""" tms = morecantile.tms.get("WebMercatorQuad") @@ -158,6 +205,31 @@ def test_custom_tms_bounds_user_crs(): assert custom_tms.bounds(0, 0, 0) == (-120, 30, -110, 40) +def test_nztm_quad_is_quad(): + tms = morecantile.tms.get("NZTM2000Quad") + bound = tms.xy_bounds(morecantile.Tile(0, 0, 0)) + expected = (-3260586.7284, 419435.9938, 6758167.443, 10438190.1652) + for a, b in zip(expected, bound): + assert round(a - b, 4) == 0 + + +# NZTM2000Quad should use all the WebMercatorQuad zoom scales +def test_nztm_quad_scales(): + nztm_tms = morecantile.tms.get("NZTM2000Quad") + google_tms = morecantile.tms.get("WebMercatorQuad") + print(dir(google_tms)) + + for z in range(2, nztm_tms.maxzoom + 2): + assert ( + round( + google_tms.matrix(z).scaleDenominator + - nztm_tms.matrix(z - 2).scaleDenominator, + 4, + ) + == 0 + ) + + def test_InvertedLatLonGrids(): """Check Inverted LatLon grids.""" tms = morecantile.tms.get("NZTM2000") diff --git a/tests/test_morecantile.py b/tests/test_morecantile.py index f8f49ba..c4f54cf 100644 --- a/tests/test_morecantile.py +++ b/tests/test_morecantile.py @@ -6,12 +6,14 @@ import morecantile from morecantile.errors import InvalidIdentifier, PointOutsideTMSBounds -from morecantile.utils import meters_per_unit +from morecantile.utils import is_power_of_two, meters_per_unit + +DEFAULT_GRID_COUNT = 11 def test_default_grids(): """Morecantile.default_grids should return the correct list of grids.""" - assert len(morecantile.tms.list()) == 9 + assert len(morecantile.tms.list()) == DEFAULT_GRID_COUNT with pytest.raises(InvalidIdentifier): morecantile.tms.get("ANotValidName") @@ -19,21 +21,21 @@ def test_default_grids(): def test_register(): """Test register a new grid.""" - assert len(morecantile.tms.list()) == 9 + assert len(morecantile.tms.list()) == DEFAULT_GRID_COUNT crs = CRS.from_epsg(3031) extent = [-948.75, -543592.47, 5817.41, -3333128.95] # From https:///epsg.io/3031 tms = morecantile.TileMatrixSet.custom(extent, crs, identifier="MyCustomGrid3031") _ = morecantile.tms.register(tms) - assert len(morecantile.tms.list()) == 9 + assert len(morecantile.tms.list()) == DEFAULT_GRID_COUNT defaults = morecantile.tms.register(tms) - assert len(defaults.list()) == 10 + assert len(defaults.list()) == DEFAULT_GRID_COUNT + 1 assert "MyCustomGrid3031" in defaults.list() defaults = morecantile.tms.register([tms]) - assert len(defaults.list()) == 10 + assert len(defaults.list()) == DEFAULT_GRID_COUNT + 1 assert "MyCustomGrid3031" in defaults.list() # Check it will raise an exception if TMS is already registered @@ -42,22 +44,22 @@ def test_register(): # Do not raise is overwrite=True defaults = defaults.register(tms, overwrite=True) - assert len(defaults.list()) == 10 + assert len(defaults.list()) == DEFAULT_GRID_COUNT + 1 # make sure the default morecantile TMS are not overwriten - assert len(morecantile.defaults.default_tms.keys()) == 9 + assert len(morecantile.defaults.default_tms.keys()) == DEFAULT_GRID_COUNT # add tms in morecantile defaults (not something to do anyway) epsg3031 = morecantile.TileMatrixSet.custom(extent, crs, identifier="epsg3031") morecantile.defaults.default_tms["epsg3031"] = epsg3031 - assert len(morecantile.defaults.default_tms.keys()) == 10 + assert len(morecantile.defaults.default_tms.keys()) == DEFAULT_GRID_COUNT + 1 # make sure updating the default_tms dict has no effect on the default TileMatrixSets - assert len(morecantile.tms.list()) == 9 + assert len(morecantile.tms.list()) == DEFAULT_GRID_COUNT # Update internal TMS dict morecantile.tms.tms["MyCustomGrid3031"] = tms - assert len(morecantile.tms.list()) == 10 + assert len(morecantile.tms.list()) == DEFAULT_GRID_COUNT + 1 # make sure it doesn't propagate to the default dict assert "MyCustomGrid3031" not in morecantile.defaults.default_tms @@ -447,3 +449,8 @@ def test_extend_zoom(): more = tms.xy_bounds(2000, 2000, 30) for a, b in zip(more, merc): assert round(a - b, 7) == 0 + + +def test_is_power_of_two(): + assert is_power_of_two(8) + assert not is_power_of_two(7)