MapProxy 2.1.0__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.
- MapProxy-2.1.0.dist-info/AUTHORS.txt +33 -0
- MapProxy-2.1.0.dist-info/COPYING.txt +60 -0
- MapProxy-2.1.0.dist-info/LICENSE.txt +202 -0
- MapProxy-2.1.0.dist-info/METADATA +165 -0
- MapProxy-2.1.0.dist-info/RECORD +459 -0
- MapProxy-2.1.0.dist-info/WHEEL +5 -0
- MapProxy-2.1.0.dist-info/entry_points.txt +4 -0
- MapProxy-2.1.0.dist-info/top_level.txt +1 -0
- mapproxy/__init__.py +0 -0
- mapproxy/cache/__init__.py +36 -0
- mapproxy/cache/azureblob.py +145 -0
- mapproxy/cache/base.py +120 -0
- mapproxy/cache/compact.py +665 -0
- mapproxy/cache/couchdb.py +301 -0
- mapproxy/cache/dummy.py +36 -0
- mapproxy/cache/file.py +200 -0
- mapproxy/cache/geopackage.py +647 -0
- mapproxy/cache/legend.py +87 -0
- mapproxy/cache/mbtiles.py +411 -0
- mapproxy/cache/meta.py +80 -0
- mapproxy/cache/path.py +261 -0
- mapproxy/cache/redis.py +152 -0
- mapproxy/cache/renderd.py +100 -0
- mapproxy/cache/riak.py +206 -0
- mapproxy/cache/s3.py +209 -0
- mapproxy/cache/tile.py +736 -0
- mapproxy/client/__init__.py +0 -0
- mapproxy/client/arcgis.py +82 -0
- mapproxy/client/cgi.py +141 -0
- mapproxy/client/http.py +295 -0
- mapproxy/client/log.py +33 -0
- mapproxy/client/tile.py +158 -0
- mapproxy/client/wms.py +255 -0
- mapproxy/compat/__init__.py +0 -0
- mapproxy/compat/image.py +86 -0
- mapproxy/config/__init__.py +22 -0
- mapproxy/config/config-schema.json +813 -0
- mapproxy/config/config.py +213 -0
- mapproxy/config/coverage.py +108 -0
- mapproxy/config/defaults.py +102 -0
- mapproxy/config/loader.py +2399 -0
- mapproxy/config/spec.py +657 -0
- mapproxy/config/validator.py +242 -0
- mapproxy/config_template/__init__.py +0 -0
- mapproxy/config_template/base_config/config.wsgi +10 -0
- mapproxy/config_template/base_config/full_example.yaml +598 -0
- mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
- mapproxy/config_template/base_config/log.ini +35 -0
- mapproxy/config_template/base_config/mapproxy.yaml +60 -0
- mapproxy/config_template/base_config/seed.yaml +27 -0
- mapproxy/exception.py +149 -0
- mapproxy/featureinfo.py +251 -0
- mapproxy/grid.py +1199 -0
- mapproxy/image/__init__.py +549 -0
- mapproxy/image/fonts/DejaVuSans.ttf +0 -0
- mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
- mapproxy/image/fonts/LICENSE +99 -0
- mapproxy/image/fonts/__init__.py +0 -0
- mapproxy/image/mask.py +79 -0
- mapproxy/image/merge.py +323 -0
- mapproxy/image/message.py +357 -0
- mapproxy/image/opts.py +185 -0
- mapproxy/image/tile.py +171 -0
- mapproxy/image/transform.py +350 -0
- mapproxy/layer.py +489 -0
- mapproxy/multiapp.py +230 -0
- mapproxy/proj.py +309 -0
- mapproxy/request/__init__.py +18 -0
- mapproxy/request/arcgis.py +268 -0
- mapproxy/request/base.py +466 -0
- mapproxy/request/tile.py +131 -0
- mapproxy/request/wms/__init__.py +829 -0
- mapproxy/request/wms/exception.py +107 -0
- mapproxy/request/wmts.py +442 -0
- mapproxy/response.py +237 -0
- mapproxy/script/__init__.py +0 -0
- mapproxy/script/conf/__init__.py +0 -0
- mapproxy/script/conf/app.py +222 -0
- mapproxy/script/conf/caches.py +44 -0
- mapproxy/script/conf/geopackage.py +136 -0
- mapproxy/script/conf/layers.py +54 -0
- mapproxy/script/conf/seeds.py +36 -0
- mapproxy/script/conf/sources.py +88 -0
- mapproxy/script/conf/utils.py +148 -0
- mapproxy/script/defrag.py +187 -0
- mapproxy/script/export.py +337 -0
- mapproxy/script/grids.py +198 -0
- mapproxy/script/scales.py +134 -0
- mapproxy/script/util.py +410 -0
- mapproxy/script/wms_capabilities.py +160 -0
- mapproxy/seed/__init__.py +0 -0
- mapproxy/seed/cachelock.py +127 -0
- mapproxy/seed/cleanup.py +191 -0
- mapproxy/seed/config.py +481 -0
- mapproxy/seed/script.py +391 -0
- mapproxy/seed/seeder.py +551 -0
- mapproxy/seed/spec.py +66 -0
- mapproxy/seed/util.py +266 -0
- mapproxy/service/__init__.py +14 -0
- mapproxy/service/base.py +45 -0
- mapproxy/service/demo.py +364 -0
- mapproxy/service/kml.py +333 -0
- mapproxy/service/ows.py +39 -0
- mapproxy/service/template_helper.py +55 -0
- mapproxy/service/templates/demo/capabilities_demo.html +18 -0
- mapproxy/service/templates/demo/demo.html +181 -0
- mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
- mapproxy/service/templates/demo/static/img/blank.gif +0 -0
- mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
- mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
- mapproxy/service/templates/demo/static/logo.png +0 -0
- mapproxy/service/templates/demo/static/ol.css +345 -0
- mapproxy/service/templates/demo/static/ol.js +4 -0
- mapproxy/service/templates/demo/static/proj4.min.js +1 -0
- mapproxy/service/templates/demo/static/proj4defs.js +1 -0
- mapproxy/service/templates/demo/static/site.css +137 -0
- mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
- mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
- mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
- mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
- mapproxy/service/templates/demo/static.html +34 -0
- mapproxy/service/templates/demo/tms_demo.html +117 -0
- mapproxy/service/templates/demo/wms_demo.html +144 -0
- mapproxy/service/templates/demo/wmts_demo.html +118 -0
- mapproxy/service/templates/tms_capabilities.xml +13 -0
- mapproxy/service/templates/tms_exception.xml +4 -0
- mapproxy/service/templates/tms_root_resource.xml +7 -0
- mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
- mapproxy/service/templates/wms100capabilities.xml +112 -0
- mapproxy/service/templates/wms100exception.xml +4 -0
- mapproxy/service/templates/wms110capabilities.xml +152 -0
- mapproxy/service/templates/wms110exception.xml +5 -0
- mapproxy/service/templates/wms111capabilities.xml +183 -0
- mapproxy/service/templates/wms111exception.xml +5 -0
- mapproxy/service/templates/wms130capabilities.xml +326 -0
- mapproxy/service/templates/wms130exception.xml +8 -0
- mapproxy/service/templates/wmts100capabilities.xml +155 -0
- mapproxy/service/templates/wmts100exception.xml +9 -0
- mapproxy/service/tile.py +540 -0
- mapproxy/service/wms.py +868 -0
- mapproxy/service/wmts.py +387 -0
- mapproxy/source/__init__.py +83 -0
- mapproxy/source/arcgis.py +39 -0
- mapproxy/source/error.py +40 -0
- mapproxy/source/mapnik.py +262 -0
- mapproxy/source/tile.py +97 -0
- mapproxy/source/wms.py +273 -0
- mapproxy/srs.py +734 -0
- mapproxy/template.py +54 -0
- mapproxy/test/__init__.py +0 -0
- mapproxy/test/conftest.py +8 -0
- mapproxy/test/helper.py +255 -0
- mapproxy/test/http.py +511 -0
- mapproxy/test/image.py +219 -0
- mapproxy/test/mocker.py +2291 -0
- mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
- mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
- mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
- mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
- mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
- mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
- mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
- mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
- mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
- mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
- mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
- mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
- mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
- mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
- mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
- mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
- mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
- mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
- mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
- mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
- mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
- mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
- mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
- mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
- mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
- mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
- mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
- mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
- mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
- mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
- mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
- mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
- mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
- mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
- mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
- mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
- mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
- mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
- mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
- mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
- mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
- mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
- mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
- mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
- mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
- mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
- mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
- mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
- mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
- mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
- mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
- mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
- mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
- mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
- mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
- mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
- mapproxy/test/schemas/xml.xsd +287 -0
- mapproxy/test/system/__init__.py +98 -0
- mapproxy/test/system/fixture/arcgis.yaml +57 -0
- mapproxy/test/system/fixture/auth.yaml +70 -0
- mapproxy/test/system/fixture/cache.mbtiles +0 -0
- mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
- mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
- mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
- mapproxy/test/system/fixture/cache_coverage.yaml +84 -0
- mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
- mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
- mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
- mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
- mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
- mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
- mapproxy/test/system/fixture/cache_s3.yaml +58 -0
- mapproxy/test/system/fixture/cache_source.yaml +81 -0
- mapproxy/test/system/fixture/combined_sources.yaml +130 -0
- mapproxy/test/system/fixture/coverage.yaml +77 -0
- mapproxy/test/system/fixture/demo.yaml +135 -0
- mapproxy/test/system/fixture/dimension.yaml +59 -0
- mapproxy/test/system/fixture/disable_storage.yaml +25 -0
- mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
- mapproxy/test/system/fixture/formats.yaml +72 -0
- mapproxy/test/system/fixture/inspire.yaml +101 -0
- mapproxy/test/system/fixture/inspire_full.yaml +124 -0
- mapproxy/test/system/fixture/kml_layer.yaml +66 -0
- mapproxy/test/system/fixture/layer.yaml +260 -0
- mapproxy/test/system/fixture/layergroups.yaml +57 -0
- mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
- mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
- mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
- mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
- mapproxy/test/system/fixture/mapserver.yaml +23 -0
- mapproxy/test/system/fixture/minimal_cgi.py +16 -0
- mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
- mapproxy/test/system/fixture/multi_cache_layers.yaml +111 -0
- mapproxy/test/system/fixture/multiapp1.yaml +20 -0
- mapproxy/test/system/fixture/multiapp2.yaml +19 -0
- mapproxy/test/system/fixture/renderd_client.yaml +55 -0
- mapproxy/test/system/fixture/scalehints.yaml +70 -0
- mapproxy/test/system/fixture/seed.yaml +94 -0
- mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
- mapproxy/test/system/fixture/seed_old.yaml +12 -0
- mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
- mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
- mapproxy/test/system/fixture/seedonly.yaml +51 -0
- mapproxy/test/system/fixture/sld.yaml +35 -0
- mapproxy/test/system/fixture/source_errors.yaml +84 -0
- mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
- mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
- mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
- mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
- mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
- mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
- mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
- mapproxy/test/system/fixture/util_grids.yaml +29 -0
- mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
- mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
- mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
- mapproxy/test/system/fixture/watermark.yaml +50 -0
- mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
- mapproxy/test/system/fixture/wms_versions.yaml +38 -0
- mapproxy/test/system/fixture/wmts.yaml +134 -0
- mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
- mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
- mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
- mapproxy/test/system/test_arcgis.py +156 -0
- mapproxy/test/system/test_auth.py +1133 -0
- mapproxy/test/system/test_behind_proxy.py +75 -0
- mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
- mapproxy/test/system/test_cache_azureblob.py +127 -0
- mapproxy/test/system/test_cache_band_merge.py +103 -0
- mapproxy/test/system/test_cache_coverage.py +168 -0
- mapproxy/test/system/test_cache_geopackage.py +144 -0
- mapproxy/test/system/test_cache_grid_names.py +89 -0
- mapproxy/test/system/test_cache_mbtiles.py +85 -0
- mapproxy/test/system/test_cache_s3.py +115 -0
- mapproxy/test/system/test_cache_source.py +146 -0
- mapproxy/test/system/test_combined_sources.py +335 -0
- mapproxy/test/system/test_coverage.py +140 -0
- mapproxy/test/system/test_decorate_img.py +214 -0
- mapproxy/test/system/test_demo.py +56 -0
- mapproxy/test/system/test_demo_with_extra_service.py +57 -0
- mapproxy/test/system/test_dimensions.py +279 -0
- mapproxy/test/system/test_disable_storage.py +42 -0
- mapproxy/test/system/test_formats.py +219 -0
- mapproxy/test/system/test_inspire_vs.py +173 -0
- mapproxy/test/system/test_kml.py +264 -0
- mapproxy/test/system/test_layergroups.py +160 -0
- mapproxy/test/system/test_legendgraphic.py +308 -0
- mapproxy/test/system/test_mapnik.py +162 -0
- mapproxy/test/system/test_mapserver.py +83 -0
- mapproxy/test/system/test_mixed_mode_format.py +201 -0
- mapproxy/test/system/test_multi_cache_layers.py +169 -0
- mapproxy/test/system/test_multiapp.py +92 -0
- mapproxy/test/system/test_refresh.py +206 -0
- mapproxy/test/system/test_renderd_client.py +304 -0
- mapproxy/test/system/test_response_headers.py +54 -0
- mapproxy/test/system/test_scalehints.py +140 -0
- mapproxy/test/system/test_seed.py +425 -0
- mapproxy/test/system/test_seed_only.py +93 -0
- mapproxy/test/system/test_sld.py +120 -0
- mapproxy/test/system/test_source_errors.py +377 -0
- mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
- mapproxy/test/system/test_tms.py +277 -0
- mapproxy/test/system/test_tms_origin.py +46 -0
- mapproxy/test/system/test_util_conf.py +434 -0
- mapproxy/test/system/test_util_export.py +210 -0
- mapproxy/test/system/test_util_grids.py +88 -0
- mapproxy/test/system/test_util_wms_capabilities.py +182 -0
- mapproxy/test/system/test_watermark.py +91 -0
- mapproxy/test/system/test_wms.py +1616 -0
- mapproxy/test/system/test_wms_srs_extent.py +165 -0
- mapproxy/test/system/test_wms_version.py +85 -0
- mapproxy/test/system/test_wmsc.py +116 -0
- mapproxy/test/system/test_wmts.py +334 -0
- mapproxy/test/system/test_wmts_dimensions.py +206 -0
- mapproxy/test/system/test_wmts_restful.py +198 -0
- mapproxy/test/system/test_xslt_featureinfo.py +423 -0
- mapproxy/test/test_http_helper.py +217 -0
- mapproxy/test/unit/__init__.py +0 -0
- mapproxy/test/unit/epsg +2 -0
- mapproxy/test/unit/polygons/polygons.dbf +0 -0
- mapproxy/test/unit/polygons/polygons.shp +0 -0
- mapproxy/test/unit/polygons/polygons.shx +0 -0
- mapproxy/test/unit/test_async.py +242 -0
- mapproxy/test/unit/test_auth.py +430 -0
- mapproxy/test/unit/test_cache.py +1356 -0
- mapproxy/test/unit/test_cache_azureblob.py +97 -0
- mapproxy/test/unit/test_cache_compact.py +324 -0
- mapproxy/test/unit/test_cache_couchdb.py +118 -0
- mapproxy/test/unit/test_cache_geopackage.py +256 -0
- mapproxy/test/unit/test_cache_redis.py +123 -0
- mapproxy/test/unit/test_cache_riak.py +80 -0
- mapproxy/test/unit/test_cache_s3.py +93 -0
- mapproxy/test/unit/test_cache_tile.py +477 -0
- mapproxy/test/unit/test_client.py +488 -0
- mapproxy/test/unit/test_client_arcgis.py +74 -0
- mapproxy/test/unit/test_client_cgi.py +140 -0
- mapproxy/test/unit/test_collections.py +116 -0
- mapproxy/test/unit/test_concat_legends.py +37 -0
- mapproxy/test/unit/test_conf_loader.py +1267 -0
- mapproxy/test/unit/test_conf_validator.py +427 -0
- mapproxy/test/unit/test_config.py +118 -0
- mapproxy/test/unit/test_decorate_img.py +185 -0
- mapproxy/test/unit/test_exceptions.py +270 -0
- mapproxy/test/unit/test_featureinfo.py +313 -0
- mapproxy/test/unit/test_file_lock_load.py +49 -0
- mapproxy/test/unit/test_geom.py +512 -0
- mapproxy/test/unit/test_grid.py +1279 -0
- mapproxy/test/unit/test_image.py +1051 -0
- mapproxy/test/unit/test_image_mask.py +181 -0
- mapproxy/test/unit/test_image_messages.py +209 -0
- mapproxy/test/unit/test_image_options.py +160 -0
- mapproxy/test/unit/test_isodate.py +118 -0
- mapproxy/test/unit/test_multiapp.py +163 -0
- mapproxy/test/unit/test_ogr_reader.py +51 -0
- mapproxy/test/unit/test_request.py +745 -0
- mapproxy/test/unit/test_request_wmts.py +178 -0
- mapproxy/test/unit/test_response.py +78 -0
- mapproxy/test/unit/test_seed.py +365 -0
- mapproxy/test/unit/test_seed_cachelock.py +91 -0
- mapproxy/test/unit/test_srs.py +215 -0
- mapproxy/test/unit/test_tiled_source.py +122 -0
- mapproxy/test/unit/test_tilefilter.py +31 -0
- mapproxy/test/unit/test_times.py +25 -0
- mapproxy/test/unit/test_timeutils.py +50 -0
- mapproxy/test/unit/test_util_conf_utils.py +75 -0
- mapproxy/test/unit/test_utils.py +476 -0
- mapproxy/test/unit/test_wms_capabilities.py +44 -0
- mapproxy/test/unit/test_wms_layer.py +113 -0
- mapproxy/test/unit/test_yaml.py +68 -0
- mapproxy/tilefilter.py +61 -0
- mapproxy/util/__init__.py +0 -0
- mapproxy/util/async_.py +229 -0
- mapproxy/util/collections.py +134 -0
- mapproxy/util/coverage.py +337 -0
- mapproxy/util/ext/__init__.py +14 -0
- mapproxy/util/ext/dictspec/__init__.py +1 -0
- mapproxy/util/ext/dictspec/spec.py +131 -0
- mapproxy/util/ext/dictspec/test/__init__.py +0 -0
- mapproxy/util/ext/dictspec/test/test_validator.py +278 -0
- mapproxy/util/ext/dictspec/validator.py +194 -0
- mapproxy/util/ext/local.py +198 -0
- mapproxy/util/ext/lockfile.py +140 -0
- mapproxy/util/ext/odict.py +321 -0
- mapproxy/util/ext/serving.py +491 -0
- mapproxy/util/ext/tempita/__init__.py +1093 -0
- mapproxy/util/ext/tempita/_looper.py +163 -0
- mapproxy/util/ext/tempita/string_utils.py +24 -0
- mapproxy/util/ext/wmsparse/__init__.py +3 -0
- mapproxy/util/ext/wmsparse/duration.py +600 -0
- mapproxy/util/ext/wmsparse/parse.py +307 -0
- mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
- mapproxy/util/ext/wmsparse/test/test_parse.py +111 -0
- mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
- mapproxy/util/ext/wmsparse/test/wms-example-111.xml +90 -0
- mapproxy/util/ext/wmsparse/test/wms-example-130.xml +120 -0
- mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
- mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
- mapproxy/util/ext/wmsparse/util.py +189 -0
- mapproxy/util/fs.py +164 -0
- mapproxy/util/geom.py +307 -0
- mapproxy/util/lib.py +117 -0
- mapproxy/util/lock.py +171 -0
- mapproxy/util/ogr.py +247 -0
- mapproxy/util/py.py +75 -0
- mapproxy/util/times.py +78 -0
- mapproxy/util/yaml.py +58 -0
- mapproxy/version.py +33 -0
- mapproxy/wsgiapp.py +167 -0
mapproxy/grid.py
ADDED
|
@@ -0,0 +1,1199 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2010 Omniscale <http://omniscale.de>
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
(Meta-)Tile grids (data and calculations).
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import division
|
|
20
|
+
import math
|
|
21
|
+
|
|
22
|
+
from mapproxy.srs import SRS, get_epsg_num, merge_bbox, bbox_equals
|
|
23
|
+
from mapproxy.util.collections import ImmutableDictList
|
|
24
|
+
|
|
25
|
+
geodetic_epsg_codes = [4326]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GridError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NoTiles(GridError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_resolution(bbox, size):
|
|
37
|
+
"""
|
|
38
|
+
Calculate the highest resolution needed to draw the bbox
|
|
39
|
+
into an image with given size.
|
|
40
|
+
|
|
41
|
+
>>> get_resolution((-180,-90,180,90), (256, 256))
|
|
42
|
+
0.703125
|
|
43
|
+
|
|
44
|
+
:returns: the resolution
|
|
45
|
+
:rtype: float
|
|
46
|
+
"""
|
|
47
|
+
w = abs(bbox[0] - bbox[2])
|
|
48
|
+
h = abs(bbox[1] - bbox[3])
|
|
49
|
+
return min(w/size[0], h/size[1])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def tile_grid_for_epsg(epsg, bbox=None, tile_size=(256, 256), res=None):
|
|
53
|
+
"""
|
|
54
|
+
Create a tile grid that matches the given epsg code:
|
|
55
|
+
|
|
56
|
+
:param epsg: the epsg code
|
|
57
|
+
:type epsg: 'EPSG:0000', '0000' or 0000
|
|
58
|
+
:param bbox: the bbox of the grid
|
|
59
|
+
:param tile_size: the size of each tile
|
|
60
|
+
:param res: a list with all resolutions
|
|
61
|
+
"""
|
|
62
|
+
epsg = get_epsg_num(epsg)
|
|
63
|
+
if epsg in geodetic_epsg_codes:
|
|
64
|
+
return TileGrid(epsg, is_geodetic=True, bbox=bbox, tile_size=tile_size, res=res)
|
|
65
|
+
return TileGrid(epsg, bbox=bbox, tile_size=tile_size, res=res)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# defer loading of default bbox since custom proj settings
|
|
69
|
+
# are not loaded on import time
|
|
70
|
+
class _default_bboxs(object):
|
|
71
|
+
_defaults = {
|
|
72
|
+
4326: (-180, -90, 180, 90),
|
|
73
|
+
}
|
|
74
|
+
for epsg_num in (900913, 3857, 102100, 102113):
|
|
75
|
+
_defaults[epsg_num] = (-20037508.342789244,
|
|
76
|
+
-20037508.342789244,
|
|
77
|
+
20037508.342789244,
|
|
78
|
+
20037508.342789244)
|
|
79
|
+
defaults = None
|
|
80
|
+
|
|
81
|
+
def get(self, key, default=None):
|
|
82
|
+
try:
|
|
83
|
+
return self[key]
|
|
84
|
+
except KeyError:
|
|
85
|
+
return default
|
|
86
|
+
|
|
87
|
+
def __getitem__(self, key):
|
|
88
|
+
if self.defaults is None:
|
|
89
|
+
defaults = {}
|
|
90
|
+
for epsg, bbox in self._defaults.items():
|
|
91
|
+
defaults[SRS(epsg)] = bbox
|
|
92
|
+
self.defaults = defaults
|
|
93
|
+
return self.defaults[key]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
default_bboxs = _default_bboxs()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def tile_grid(srs=None, bbox=None, bbox_srs=None, tile_size=(256, 256),
|
|
100
|
+
res=None, res_factor=2.0, threshold_res=None,
|
|
101
|
+
num_levels=None, min_res=None, max_res=None,
|
|
102
|
+
stretch_factor=1.15, max_shrink_factor=4.0,
|
|
103
|
+
align_with=None, origin='ll', name=None
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
This function creates a new TileGrid.
|
|
107
|
+
"""
|
|
108
|
+
if srs is None:
|
|
109
|
+
srs = 'EPSG:900913'
|
|
110
|
+
srs = SRS(srs)
|
|
111
|
+
|
|
112
|
+
if not bbox:
|
|
113
|
+
bbox = default_bboxs.get(srs)
|
|
114
|
+
if not bbox:
|
|
115
|
+
raise ValueError('need a bbox for grid with %s' % srs)
|
|
116
|
+
|
|
117
|
+
bbox = grid_bbox(bbox, srs=srs, bbox_srs=bbox_srs)
|
|
118
|
+
|
|
119
|
+
if res:
|
|
120
|
+
if isinstance(res, list):
|
|
121
|
+
if isinstance(res[0], (tuple, list)):
|
|
122
|
+
# named resolutions
|
|
123
|
+
res = sorted(res, key=lambda x: x[1], reverse=True)
|
|
124
|
+
else:
|
|
125
|
+
res = sorted(res, reverse=True)
|
|
126
|
+
assert min_res is None
|
|
127
|
+
assert max_res is None
|
|
128
|
+
assert align_with is None
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError("res is not a list, use res_factor for float values")
|
|
131
|
+
|
|
132
|
+
elif align_with is not None:
|
|
133
|
+
res = aligned_resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size,
|
|
134
|
+
align_with)
|
|
135
|
+
else:
|
|
136
|
+
res = resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size)
|
|
137
|
+
|
|
138
|
+
origin = origin_from_string(origin)
|
|
139
|
+
|
|
140
|
+
return TileGrid(srs, bbox=bbox, tile_size=tile_size, res=res, threshold_res=threshold_res,
|
|
141
|
+
stretch_factor=stretch_factor, max_shrink_factor=max_shrink_factor,
|
|
142
|
+
origin=origin, name=name)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
ORIGIN_UL = 'ul'
|
|
146
|
+
ORIGIN_LL = 'll'
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def origin_from_string(origin):
|
|
150
|
+
if origin is None:
|
|
151
|
+
origin = ORIGIN_LL
|
|
152
|
+
elif origin.lower() in ('ll', 'sw'):
|
|
153
|
+
origin = ORIGIN_LL
|
|
154
|
+
elif origin.lower() in ('ul', 'nw'):
|
|
155
|
+
origin = ORIGIN_UL
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError("unknown origin value '%s'" % origin)
|
|
158
|
+
return origin
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def aligned_resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
|
|
162
|
+
bbox=None, tile_size=(256, 256), align_with=None):
|
|
163
|
+
|
|
164
|
+
alinged_res = align_with.resolutions
|
|
165
|
+
res = list(alinged_res)
|
|
166
|
+
|
|
167
|
+
if not min_res:
|
|
168
|
+
width = bbox[2] - bbox[0]
|
|
169
|
+
height = bbox[3] - bbox[1]
|
|
170
|
+
min_res = max(width/tile_size[0], height/tile_size[1])
|
|
171
|
+
|
|
172
|
+
res = [r for r in res if r <= min_res]
|
|
173
|
+
|
|
174
|
+
if max_res:
|
|
175
|
+
res = [r for r in res if r >= max_res]
|
|
176
|
+
|
|
177
|
+
if num_levels:
|
|
178
|
+
res = res[:num_levels]
|
|
179
|
+
|
|
180
|
+
factor_calculated = res[0]/res[1]
|
|
181
|
+
if res_factor == 'sqrt2' and round(factor_calculated, 8) != round(math.sqrt(2), 8):
|
|
182
|
+
if round(factor_calculated, 8) == 2.0:
|
|
183
|
+
new_res = []
|
|
184
|
+
for r in res:
|
|
185
|
+
new_res.append(r)
|
|
186
|
+
new_res.append(r/math.sqrt(2))
|
|
187
|
+
res = new_res
|
|
188
|
+
elif res_factor == 2.0 and round(factor_calculated, 8) != round(2.0, 8):
|
|
189
|
+
if round(factor_calculated, 8) == round(math.sqrt(2), 8):
|
|
190
|
+
res = res[::2]
|
|
191
|
+
return res
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
|
|
195
|
+
bbox=None, tile_size=(256, 256)):
|
|
196
|
+
if res_factor == 'sqrt2':
|
|
197
|
+
res_factor = math.sqrt(2)
|
|
198
|
+
|
|
199
|
+
res = []
|
|
200
|
+
if not min_res:
|
|
201
|
+
width = bbox[2] - bbox[0]
|
|
202
|
+
height = bbox[3] - bbox[1]
|
|
203
|
+
min_res = max(width/tile_size[0], height/tile_size[1])
|
|
204
|
+
|
|
205
|
+
if max_res:
|
|
206
|
+
if num_levels:
|
|
207
|
+
res_step = (math.log10(min_res) - math.log10(max_res)) / (num_levels-1)
|
|
208
|
+
res = [10**(math.log10(min_res) - res_step*i) for i in range(num_levels)]
|
|
209
|
+
else:
|
|
210
|
+
res = [min_res]
|
|
211
|
+
while True:
|
|
212
|
+
next_res = res[-1]/res_factor
|
|
213
|
+
if max_res >= next_res:
|
|
214
|
+
break
|
|
215
|
+
res.append(next_res)
|
|
216
|
+
else:
|
|
217
|
+
if not num_levels:
|
|
218
|
+
num_levels = 20 if res_factor != math.sqrt(2) else 40
|
|
219
|
+
res = [min_res]
|
|
220
|
+
while len(res) < num_levels:
|
|
221
|
+
res.append(res[-1]/res_factor)
|
|
222
|
+
|
|
223
|
+
return res
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def grid_bbox(bbox, bbox_srs, srs):
|
|
227
|
+
bbox = bbox_tuple(bbox)
|
|
228
|
+
if bbox_srs:
|
|
229
|
+
bbox = SRS(bbox_srs).transform_bbox_to(srs, bbox)
|
|
230
|
+
return bbox
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def bbox_tuple(bbox):
|
|
234
|
+
"""
|
|
235
|
+
>>> bbox_tuple('20,-30,40,-10')
|
|
236
|
+
(20.0, -30.0, 40.0, -10.0)
|
|
237
|
+
>>> bbox_tuple([20,-30,40,-10])
|
|
238
|
+
(20.0, -30.0, 40.0, -10.0)
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
if isinstance(bbox, str):
|
|
242
|
+
bbox = bbox.split(',')
|
|
243
|
+
bbox = tuple(map(float, bbox))
|
|
244
|
+
return bbox
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def bbox_width(bbox):
|
|
248
|
+
return bbox[2] - bbox[0]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def bbox_height(bbox):
|
|
252
|
+
return bbox[3] - bbox[1]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def bbox_size(bbox):
|
|
256
|
+
return bbox_width(bbox), bbox_height(bbox)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class NamedGridList(ImmutableDictList):
|
|
260
|
+
def __init__(self, items):
|
|
261
|
+
tmp = []
|
|
262
|
+
for i, value in enumerate(items):
|
|
263
|
+
if isinstance(value, (tuple, list)):
|
|
264
|
+
name, value = value
|
|
265
|
+
else:
|
|
266
|
+
name = str('%02d' % i)
|
|
267
|
+
tmp.append((name, value))
|
|
268
|
+
ImmutableDictList.__init__(self, tmp)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class TileGrid(object):
|
|
272
|
+
"""
|
|
273
|
+
This class represents a regular tile grid. The first level (0) contains a single
|
|
274
|
+
tile, the origin is bottom-left.
|
|
275
|
+
|
|
276
|
+
:ivar levels: the number of levels
|
|
277
|
+
:ivar tile_size: the size of each tile in pixel
|
|
278
|
+
:type tile_size: ``int(with), int(height)``
|
|
279
|
+
:ivar srs: the srs of the grid
|
|
280
|
+
:type srs: `SRS`
|
|
281
|
+
:ivar bbox: the bbox of the grid, tiles may overlap this bbox
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
spheroid_a = 6378137.0 # for 900913
|
|
285
|
+
flipped_y_axis = False
|
|
286
|
+
|
|
287
|
+
def __init__(self, srs=900913, bbox=None, tile_size=(256, 256), res=None,
|
|
288
|
+
threshold_res=None, is_geodetic=False, levels=None,
|
|
289
|
+
stretch_factor=1.15, max_shrink_factor=4.0, origin='ll',
|
|
290
|
+
name=None):
|
|
291
|
+
"""
|
|
292
|
+
:param stretch_factor: allow images to be scaled up by this factor
|
|
293
|
+
before the next level will be selected
|
|
294
|
+
:param max_shrink_factor: allow images to be scaled down by this
|
|
295
|
+
factor before NoTiles is raised
|
|
296
|
+
|
|
297
|
+
>>> grid = TileGrid(srs=900913)
|
|
298
|
+
>>> [round(x, 2) for x in grid.bbox]
|
|
299
|
+
[-20037508.34, -20037508.34, 20037508.34, 20037508.34]
|
|
300
|
+
"""
|
|
301
|
+
if isinstance(srs, (int, str)):
|
|
302
|
+
srs = SRS(srs)
|
|
303
|
+
self.srs = srs
|
|
304
|
+
self.tile_size = tile_size
|
|
305
|
+
self.origin = origin_from_string(origin)
|
|
306
|
+
self.name = name
|
|
307
|
+
|
|
308
|
+
if self.origin == 'ul':
|
|
309
|
+
self.flipped_y_axis = True
|
|
310
|
+
|
|
311
|
+
self.is_geodetic = is_geodetic
|
|
312
|
+
|
|
313
|
+
self.stretch_factor = stretch_factor
|
|
314
|
+
self.max_shrink_factor = max_shrink_factor
|
|
315
|
+
|
|
316
|
+
if levels is None:
|
|
317
|
+
self.levels = 20
|
|
318
|
+
else:
|
|
319
|
+
self.levels = levels
|
|
320
|
+
|
|
321
|
+
if bbox is None:
|
|
322
|
+
bbox = self._calc_bbox()
|
|
323
|
+
self.bbox = bbox
|
|
324
|
+
|
|
325
|
+
factor = None
|
|
326
|
+
|
|
327
|
+
if res is None:
|
|
328
|
+
factor = 2.0
|
|
329
|
+
res = self._calc_res(factor=factor)
|
|
330
|
+
elif res == 'sqrt2':
|
|
331
|
+
if levels is None:
|
|
332
|
+
self.levels = 40
|
|
333
|
+
factor = math.sqrt(2)
|
|
334
|
+
res = self._calc_res(factor=factor)
|
|
335
|
+
elif is_float(res):
|
|
336
|
+
factor = float(res)
|
|
337
|
+
res = self._calc_res(factor=factor)
|
|
338
|
+
|
|
339
|
+
self.levels = len(res)
|
|
340
|
+
self.resolutions = NamedGridList(res)
|
|
341
|
+
|
|
342
|
+
self.threshold_res = None
|
|
343
|
+
if threshold_res:
|
|
344
|
+
self.threshold_res = sorted(threshold_res)
|
|
345
|
+
|
|
346
|
+
self.grid_sizes = self._calc_grids()
|
|
347
|
+
|
|
348
|
+
def _calc_grids(self):
|
|
349
|
+
width = self.bbox[2] - self.bbox[0]
|
|
350
|
+
height = self.bbox[3] - self.bbox[1]
|
|
351
|
+
grids = []
|
|
352
|
+
for idx, res in self.resolutions.iteritems():
|
|
353
|
+
x = max(math.ceil(width // res / self.tile_size[0]), 1)
|
|
354
|
+
y = max(math.ceil(height // res / self.tile_size[1]), 1)
|
|
355
|
+
grids.append((idx, (int(x), int(y))))
|
|
356
|
+
return NamedGridList(grids)
|
|
357
|
+
|
|
358
|
+
def _calc_bbox(self):
|
|
359
|
+
if self.is_geodetic:
|
|
360
|
+
return (-180.0, -90.0, 180.0, 90.0)
|
|
361
|
+
else:
|
|
362
|
+
circum = 2 * math.pi * self.spheroid_a
|
|
363
|
+
offset = circum / 2.0
|
|
364
|
+
return (-offset, -offset, offset, offset)
|
|
365
|
+
|
|
366
|
+
def _calc_res(self, factor=None):
|
|
367
|
+
width = self.bbox[2] - self.bbox[0]
|
|
368
|
+
height = self.bbox[3] - self.bbox[1]
|
|
369
|
+
initial_res = max(width/self.tile_size[0], height/self.tile_size[1])
|
|
370
|
+
if factor is None:
|
|
371
|
+
return pyramid_res_level(initial_res, levels=self.levels)
|
|
372
|
+
else:
|
|
373
|
+
return pyramid_res_level(initial_res, factor, levels=self.levels)
|
|
374
|
+
|
|
375
|
+
def resolution(self, level):
|
|
376
|
+
"""
|
|
377
|
+
Returns the resolution of the `level` in units/pixel.
|
|
378
|
+
|
|
379
|
+
:param level: the zoom level index (zero is top)
|
|
380
|
+
|
|
381
|
+
>>> grid = TileGrid(SRS(900913))
|
|
382
|
+
>>> '%.5f' % grid.resolution(0)
|
|
383
|
+
'156543.03393'
|
|
384
|
+
>>> '%.5f' % grid.resolution(1)
|
|
385
|
+
'78271.51696'
|
|
386
|
+
>>> '%.5f' % grid.resolution(4)
|
|
387
|
+
'9783.93962'
|
|
388
|
+
"""
|
|
389
|
+
return self.resolutions[level]
|
|
390
|
+
|
|
391
|
+
def closest_level(self, res):
|
|
392
|
+
"""
|
|
393
|
+
Returns the level index that offers the required resolution.
|
|
394
|
+
|
|
395
|
+
:param res: the required resolution
|
|
396
|
+
:returns: the level with the requested or higher resolution
|
|
397
|
+
|
|
398
|
+
>>> grid = TileGrid(SRS(900913))
|
|
399
|
+
>>> grid.stretch_factor = 1.1
|
|
400
|
+
>>> l1_res = grid.resolution(1)
|
|
401
|
+
>>> [grid.closest_level(x) for x in (320000.0, 160000.0, l1_res+50, l1_res, \
|
|
402
|
+
l1_res-50, l1_res*0.91, l1_res*0.89, 8000.0)]
|
|
403
|
+
[0, 0, 1, 1, 1, 1, 2, 5]
|
|
404
|
+
"""
|
|
405
|
+
prev_l_res = self.resolutions[0]
|
|
406
|
+
threshold = None
|
|
407
|
+
thresholds = []
|
|
408
|
+
if self.threshold_res:
|
|
409
|
+
thresholds = self.threshold_res[:]
|
|
410
|
+
threshold = thresholds.pop()
|
|
411
|
+
# skip thresholds above first res
|
|
412
|
+
while threshold > prev_l_res and thresholds:
|
|
413
|
+
threshold = thresholds.pop()
|
|
414
|
+
|
|
415
|
+
threshold_result = None
|
|
416
|
+
for level, l_res in enumerate(self.resolutions):
|
|
417
|
+
if threshold and prev_l_res > threshold >= l_res:
|
|
418
|
+
if res > threshold:
|
|
419
|
+
return level-1
|
|
420
|
+
elif res >= l_res:
|
|
421
|
+
return level
|
|
422
|
+
threshold = thresholds.pop() if thresholds else None
|
|
423
|
+
|
|
424
|
+
if threshold_result is not None:
|
|
425
|
+
# Use previous level that was within stretch_factor,
|
|
426
|
+
# but only if this level res is smaller then res.
|
|
427
|
+
# This fixes selection for resolutions that are closer together then stretch_factor.
|
|
428
|
+
#
|
|
429
|
+
if l_res < res:
|
|
430
|
+
return threshold_result
|
|
431
|
+
|
|
432
|
+
if l_res <= res*self.stretch_factor:
|
|
433
|
+
# l_res within stretch_factor
|
|
434
|
+
# remember this level, check for thresholds or better res in next loop
|
|
435
|
+
threshold_result = level
|
|
436
|
+
prev_l_res = l_res
|
|
437
|
+
return level
|
|
438
|
+
|
|
439
|
+
def tile(self, x, y, level):
|
|
440
|
+
"""
|
|
441
|
+
Returns the tile id for the given point.
|
|
442
|
+
|
|
443
|
+
>>> grid = TileGrid(SRS(900913))
|
|
444
|
+
>>> grid.tile(1000, 1000, 0)
|
|
445
|
+
(0, 0, 0)
|
|
446
|
+
>>> grid.tile(1000, 1000, 1)
|
|
447
|
+
(1, 1, 1)
|
|
448
|
+
>>> grid = TileGrid(SRS(900913), tile_size=(512, 512))
|
|
449
|
+
>>> grid.tile(1000, 1000, 2)
|
|
450
|
+
(2, 2, 2)
|
|
451
|
+
"""
|
|
452
|
+
res = self.resolution(level)
|
|
453
|
+
x = x - self.bbox[0]
|
|
454
|
+
if self.flipped_y_axis:
|
|
455
|
+
y = self.bbox[3] - y
|
|
456
|
+
else:
|
|
457
|
+
y = y - self.bbox[1]
|
|
458
|
+
tile_x = x/float(res*self.tile_size[0])
|
|
459
|
+
tile_y = y/float(res*self.tile_size[1])
|
|
460
|
+
return (int(math.floor(tile_x)), int(math.floor(tile_y)), level)
|
|
461
|
+
|
|
462
|
+
def flip_tile_coord(self, tile_coord):
|
|
463
|
+
"""
|
|
464
|
+
Flip the tile coord on the y-axis. (Switch between bottom-left and top-left
|
|
465
|
+
origin.)
|
|
466
|
+
|
|
467
|
+
>>> grid = TileGrid(SRS(900913))
|
|
468
|
+
>>> grid.flip_tile_coord((0, 1, 1))
|
|
469
|
+
(0, 0, 1)
|
|
470
|
+
>>> grid.flip_tile_coord((1, 3, 2))
|
|
471
|
+
(1, 0, 2)
|
|
472
|
+
"""
|
|
473
|
+
(x, y, z) = tile_coord
|
|
474
|
+
return (x, self.grid_sizes[z][1]-1-y, z)
|
|
475
|
+
|
|
476
|
+
def supports_access_with_origin(self, origin):
|
|
477
|
+
if origin_from_string(origin) == self.origin:
|
|
478
|
+
return True
|
|
479
|
+
|
|
480
|
+
# check for each level if the top and bottom coordinates of the tiles
|
|
481
|
+
# match the bbox of the grid. only in this case we can flip y-axis
|
|
482
|
+
# without any issues
|
|
483
|
+
|
|
484
|
+
# allow for some rounding errors in the _tiles_bbox calculations
|
|
485
|
+
delta = max(abs(self.bbox[1]), abs(self.bbox[3])) / 1e12
|
|
486
|
+
|
|
487
|
+
for level, grid_size in enumerate(self.grid_sizes):
|
|
488
|
+
level_bbox = self._tiles_bbox([(0, 0, level),
|
|
489
|
+
(grid_size[0] - 1, grid_size[1] - 1, level)])
|
|
490
|
+
|
|
491
|
+
if abs(self.bbox[1] - level_bbox[1]) > delta or abs(self.bbox[3] - level_bbox[3]) > delta:
|
|
492
|
+
return False
|
|
493
|
+
return True
|
|
494
|
+
|
|
495
|
+
def origin_tile(self, level, origin):
|
|
496
|
+
assert self.supports_access_with_origin(origin), 'tile origins are incompatible'
|
|
497
|
+
tile = (0, 0, level)
|
|
498
|
+
|
|
499
|
+
if origin_from_string(origin) == self.origin:
|
|
500
|
+
return tile
|
|
501
|
+
|
|
502
|
+
return self.flip_tile_coord(tile)
|
|
503
|
+
|
|
504
|
+
def get_affected_tiles(self, bbox, size, req_srs=None):
|
|
505
|
+
"""
|
|
506
|
+
Get a list with all affected tiles for a bbox and output size.
|
|
507
|
+
|
|
508
|
+
:returns: the bbox, the size and a list with tile coordinates, sorted row-wise
|
|
509
|
+
:rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
|
|
510
|
+
|
|
511
|
+
>>> grid = TileGrid()
|
|
512
|
+
>>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
|
|
513
|
+
>>> tile_size = (256, 256)
|
|
514
|
+
>>> grid.get_affected_tiles(bbox, tile_size)
|
|
515
|
+
... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
|
|
516
|
+
((-20037508.342789244, -20037508.342789244,\
|
|
517
|
+
20037508.342789244, 20037508.342789244), (1, 1),\
|
|
518
|
+
<generator object ...>)
|
|
519
|
+
"""
|
|
520
|
+
src_bbox, level = self.get_affected_bbox_and_level(bbox, size, req_srs=req_srs)
|
|
521
|
+
return self.get_affected_level_tiles(src_bbox, level)
|
|
522
|
+
|
|
523
|
+
def get_affected_bbox_and_level(self, bbox, size, req_srs=None):
|
|
524
|
+
if req_srs and req_srs != self.srs:
|
|
525
|
+
src_bbox = req_srs.transform_bbox_to(self.srs, bbox)
|
|
526
|
+
else:
|
|
527
|
+
src_bbox = bbox
|
|
528
|
+
|
|
529
|
+
if not bbox_intersects(self.bbox, src_bbox):
|
|
530
|
+
raise NoTiles()
|
|
531
|
+
|
|
532
|
+
res = get_resolution(src_bbox, size)
|
|
533
|
+
level = self.closest_level(res)
|
|
534
|
+
|
|
535
|
+
if res > self.resolutions[0]*self.max_shrink_factor:
|
|
536
|
+
raise NoTiles()
|
|
537
|
+
|
|
538
|
+
return src_bbox, level
|
|
539
|
+
|
|
540
|
+
def get_affected_level_tiles(self, bbox, level):
|
|
541
|
+
"""
|
|
542
|
+
Get a list with all affected tiles for a `bbox` in the given `level`.
|
|
543
|
+
:returns: the bbox, the size and a list with tile coordinates, sorted row-wise
|
|
544
|
+
:rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
|
|
545
|
+
|
|
546
|
+
>>> grid = TileGrid()
|
|
547
|
+
>>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
|
|
548
|
+
>>> grid.get_affected_level_tiles(bbox, 0)
|
|
549
|
+
... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
|
|
550
|
+
((-20037508.342789244, -20037508.342789244,\
|
|
551
|
+
20037508.342789244, 20037508.342789244), (1, 1),\
|
|
552
|
+
<generator object ...>)
|
|
553
|
+
"""
|
|
554
|
+
# remove 1/10 of a pixel so we don't get a tiles we only touch
|
|
555
|
+
delta = self.resolutions[level] / 10.0
|
|
556
|
+
x0, y0, _ = self.tile(bbox[0]+delta, bbox[1]+delta, level)
|
|
557
|
+
x1, y1, _ = self.tile(bbox[2]-delta, bbox[3]-delta, level)
|
|
558
|
+
try:
|
|
559
|
+
return self._tile_iter(x0, y0, x1, y1, level)
|
|
560
|
+
except IndexError:
|
|
561
|
+
raise GridError('Invalid BBOX')
|
|
562
|
+
|
|
563
|
+
def _tile_iter(self, x0, y0, x1, y1, level):
|
|
564
|
+
xs = list(range(x0, x1+1))
|
|
565
|
+
if self.flipped_y_axis:
|
|
566
|
+
y0, y1 = y1, y0
|
|
567
|
+
ys = list(range(y0, y1+1))
|
|
568
|
+
else:
|
|
569
|
+
ys = list(range(y1, y0-1, -1))
|
|
570
|
+
|
|
571
|
+
ll = (xs[0], ys[-1], level)
|
|
572
|
+
ur = (xs[-1], ys[0], level)
|
|
573
|
+
|
|
574
|
+
abbox = self._tiles_bbox([ll, ur])
|
|
575
|
+
return (abbox, (len(xs), len(ys)),
|
|
576
|
+
_create_tile_list(xs, ys, level, self.grid_sizes[level]))
|
|
577
|
+
|
|
578
|
+
def _tiles_bbox(self, tiles):
|
|
579
|
+
"""
|
|
580
|
+
Returns the bbox of multiple tiles.
|
|
581
|
+
The tiles should be ordered row-wise, bottom-up.
|
|
582
|
+
|
|
583
|
+
:param tiles: ordered list of tiles
|
|
584
|
+
:returns: the bbox of all tiles
|
|
585
|
+
"""
|
|
586
|
+
ll_bbox = self.tile_bbox(tiles[0])
|
|
587
|
+
ur_bbox = self.tile_bbox(tiles[-1])
|
|
588
|
+
return merge_bbox(ll_bbox, ur_bbox)
|
|
589
|
+
|
|
590
|
+
def tile_bbox(self, tile_coord, limit=False):
|
|
591
|
+
"""
|
|
592
|
+
Returns the bbox of the given tile.
|
|
593
|
+
|
|
594
|
+
>>> grid = TileGrid(SRS(900913))
|
|
595
|
+
>>> [round(x, 2) for x in grid.tile_bbox((0, 0, 0))]
|
|
596
|
+
[-20037508.34, -20037508.34, 20037508.34, 20037508.34]
|
|
597
|
+
>>> [round(x, 2) for x in grid.tile_bbox((1, 1, 1))]
|
|
598
|
+
[0.0, 0.0, 20037508.34, 20037508.34]
|
|
599
|
+
"""
|
|
600
|
+
x, y, z = tile_coord
|
|
601
|
+
res = self.resolution(z)
|
|
602
|
+
|
|
603
|
+
x0 = self.bbox[0] + round(x * res * self.tile_size[0], 12)
|
|
604
|
+
x1 = x0 + round(res * self.tile_size[0], 12)
|
|
605
|
+
|
|
606
|
+
if self.flipped_y_axis:
|
|
607
|
+
y1 = self.bbox[3] - round(y * res * self.tile_size[1], 12)
|
|
608
|
+
y0 = y1 - round(res * self.tile_size[1], 12)
|
|
609
|
+
else:
|
|
610
|
+
y0 = self.bbox[1] + round(y * res * self.tile_size[1], 12)
|
|
611
|
+
y1 = y0 + round(res * self.tile_size[1], 12)
|
|
612
|
+
|
|
613
|
+
if limit:
|
|
614
|
+
return (
|
|
615
|
+
max(x0, self.bbox[0]),
|
|
616
|
+
max(y0, self.bbox[1]),
|
|
617
|
+
min(x1, self.bbox[2]),
|
|
618
|
+
min(y1, self.bbox[3])
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
return x0, y0, x1, y1
|
|
622
|
+
|
|
623
|
+
def limit_tile(self, tile_coord):
|
|
624
|
+
"""
|
|
625
|
+
Check if the `tile_coord` is in the grid.
|
|
626
|
+
|
|
627
|
+
:returns: the `tile_coord` if it is within the ``grid``,
|
|
628
|
+
otherwise ``None``.
|
|
629
|
+
|
|
630
|
+
>>> grid = TileGrid(SRS(900913))
|
|
631
|
+
>>> grid.limit_tile((-1, 0, 2)) is None
|
|
632
|
+
True
|
|
633
|
+
>>> grid.limit_tile((1, 2, 1)) is None
|
|
634
|
+
True
|
|
635
|
+
>>> grid.limit_tile((1, 2, 2))
|
|
636
|
+
(1, 2, 2)
|
|
637
|
+
"""
|
|
638
|
+
x, y, z = tile_coord
|
|
639
|
+
if isinstance(z, str):
|
|
640
|
+
if z not in self.grid_sizes:
|
|
641
|
+
return None
|
|
642
|
+
elif z < 0 or z >= self.levels:
|
|
643
|
+
return None
|
|
644
|
+
grid = self.grid_sizes[z]
|
|
645
|
+
if x < 0 or y < 0 or x >= grid[0] or y >= grid[1]:
|
|
646
|
+
return None
|
|
647
|
+
return x, y, z
|
|
648
|
+
|
|
649
|
+
def __repr__(self):
|
|
650
|
+
return '%s(%r, (%.4f, %.4f, %.4f, %.4f),...)' % (
|
|
651
|
+
self.__class__.__name__, self.srs, self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3])
|
|
652
|
+
|
|
653
|
+
def is_subset_of(self, other):
|
|
654
|
+
"""
|
|
655
|
+
Returns ``True`` if every tile in `self` is present in `other`.
|
|
656
|
+
Tile coordinates might differ and `other` may contain more
|
|
657
|
+
tiles (more levels, larger bbox).
|
|
658
|
+
"""
|
|
659
|
+
if self.srs != other.srs:
|
|
660
|
+
return False
|
|
661
|
+
|
|
662
|
+
if self.tile_size != other.tile_size:
|
|
663
|
+
return False
|
|
664
|
+
|
|
665
|
+
# check if all level tiles from self align with (affected)
|
|
666
|
+
# tiles from other
|
|
667
|
+
for self_level, self_level_res in self.resolutions.iteritems():
|
|
668
|
+
level_size = (
|
|
669
|
+
self.grid_sizes[self_level][0] * self.tile_size[0],
|
|
670
|
+
self.grid_sizes[self_level][1] * self.tile_size[1]
|
|
671
|
+
)
|
|
672
|
+
level_bbox = self._tiles_bbox([
|
|
673
|
+
(0, 0, self_level),
|
|
674
|
+
(self.grid_sizes[self_level][0] - 1, self.grid_sizes[self_level][1] - 1, self_level)
|
|
675
|
+
])
|
|
676
|
+
|
|
677
|
+
try:
|
|
678
|
+
bbox, level = other.get_affected_bbox_and_level(level_bbox, level_size)
|
|
679
|
+
except NoTiles:
|
|
680
|
+
return False
|
|
681
|
+
try:
|
|
682
|
+
bbox, grid_size, tiles = other.get_affected_level_tiles(level_bbox, level)
|
|
683
|
+
except GridError:
|
|
684
|
+
return False
|
|
685
|
+
|
|
686
|
+
if other.resolution(level) != self_level_res:
|
|
687
|
+
return False
|
|
688
|
+
if not bbox_equals(bbox, level_bbox):
|
|
689
|
+
return False
|
|
690
|
+
|
|
691
|
+
return True
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _create_tile_list(xs, ys, level, grid_size):
|
|
695
|
+
"""
|
|
696
|
+
Returns an iterator tile_coords for the given tile ranges (`xs` and `ys`).
|
|
697
|
+
If the one tile_coord is negative or out of the `grid_size` bound,
|
|
698
|
+
the coord is None.
|
|
699
|
+
"""
|
|
700
|
+
x_limit = grid_size[0]
|
|
701
|
+
y_limit = grid_size[1]
|
|
702
|
+
for y in ys:
|
|
703
|
+
for x in xs:
|
|
704
|
+
if x < 0 or y < 0 or x >= x_limit or y >= y_limit:
|
|
705
|
+
yield None
|
|
706
|
+
else:
|
|
707
|
+
yield x, y, level
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def is_float(x):
|
|
711
|
+
try:
|
|
712
|
+
float(x)
|
|
713
|
+
return True
|
|
714
|
+
except TypeError:
|
|
715
|
+
return False
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def pyramid_res_level(initial_res, factor=2.0, levels=20):
|
|
719
|
+
"""
|
|
720
|
+
Return resolutions of an image pyramid.
|
|
721
|
+
|
|
722
|
+
:param initial_res: the resolution of the top level (0)
|
|
723
|
+
:param factor: the factor between each level, for tms access 2
|
|
724
|
+
:param levels: number of resolutions to generate
|
|
725
|
+
|
|
726
|
+
>>> list(pyramid_res_level(10000, levels=5))
|
|
727
|
+
[10000.0, 5000.0, 2500.0, 1250.0, 625.0]
|
|
728
|
+
>>> [round(x, 4) for x in
|
|
729
|
+
... pyramid_res_level(10000, factor=1/0.75, levels=5)]
|
|
730
|
+
[10000.0, 7500.0, 5625.0, 4218.75, 3164.0625]
|
|
731
|
+
"""
|
|
732
|
+
return [initial_res/factor**n for n in range(levels)]
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class MetaGrid(object):
|
|
736
|
+
"""
|
|
737
|
+
This class contains methods to calculate bbox, etc. of metatiles.
|
|
738
|
+
|
|
739
|
+
:param grid: the grid to use for the metatiles
|
|
740
|
+
:param meta_size: the number of tiles a metatile consist
|
|
741
|
+
:type meta_size: ``(x_size, y_size)``
|
|
742
|
+
:param meta_buffer: the buffer size in pixel that is added to each metatile.
|
|
743
|
+
the number is added to all four borders.
|
|
744
|
+
this buffer may improve the handling of lables overlapping (meta)tile borders.
|
|
745
|
+
:type meta_buffer: pixel
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
def __init__(self, grid, meta_size, meta_buffer=0):
|
|
749
|
+
self.grid = grid
|
|
750
|
+
self.meta_size = meta_size or 0
|
|
751
|
+
self.meta_buffer = meta_buffer
|
|
752
|
+
|
|
753
|
+
def _meta_bbox(self, tile_coord=None, tiles=None, limit_to_bbox=True):
|
|
754
|
+
"""
|
|
755
|
+
Returns the bbox of the metatile that contains `tile_coord`.
|
|
756
|
+
|
|
757
|
+
:type tile_coord: ``(x, y, z)``
|
|
758
|
+
|
|
759
|
+
>>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
|
|
760
|
+
>>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 2))[0]]
|
|
761
|
+
[-20037508.34, -20037508.34, 0.0, 0.0]
|
|
762
|
+
>>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
|
|
763
|
+
>>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 0))[0]]
|
|
764
|
+
[-20037508.34, -20037508.34, 20037508.34, 20037508.34]
|
|
765
|
+
"""
|
|
766
|
+
if tiles:
|
|
767
|
+
assert tile_coord is None
|
|
768
|
+
level = tiles[0][2]
|
|
769
|
+
bbox = self.grid._tiles_bbox(tiles)
|
|
770
|
+
else:
|
|
771
|
+
level = tile_coord[2]
|
|
772
|
+
bbox = self.unbuffered_meta_bbox(tile_coord)
|
|
773
|
+
return self._buffered_bbox(bbox, level, limit_to_bbox)
|
|
774
|
+
|
|
775
|
+
def unbuffered_meta_bbox(self, tile_coord):
|
|
776
|
+
x, y, z = tile_coord
|
|
777
|
+
|
|
778
|
+
meta_size = self._meta_size(z)
|
|
779
|
+
|
|
780
|
+
return self.grid._tiles_bbox([(tile_coord),
|
|
781
|
+
(x+meta_size[0]-1, y+meta_size[1]-1, z)])
|
|
782
|
+
|
|
783
|
+
def _buffered_bbox(self, bbox, level, limit_to_grid_bbox=True):
|
|
784
|
+
minx, miny, maxx, maxy = bbox
|
|
785
|
+
|
|
786
|
+
buffers = (0, 0, 0, 0)
|
|
787
|
+
if self.meta_buffer > 0:
|
|
788
|
+
res = self.grid.resolution(level)
|
|
789
|
+
minx -= self.meta_buffer * res
|
|
790
|
+
miny -= self.meta_buffer * res
|
|
791
|
+
maxx += self.meta_buffer * res
|
|
792
|
+
maxy += self.meta_buffer * res
|
|
793
|
+
buffers = [self.meta_buffer, self.meta_buffer, self.meta_buffer, self.meta_buffer]
|
|
794
|
+
|
|
795
|
+
if limit_to_grid_bbox:
|
|
796
|
+
if self.grid.bbox[0] > minx:
|
|
797
|
+
delta = self.grid.bbox[0] - minx
|
|
798
|
+
buffers[0] = buffers[0] - int(round(delta / res, 5))
|
|
799
|
+
minx = self.grid.bbox[0]
|
|
800
|
+
if self.grid.bbox[1] > miny:
|
|
801
|
+
delta = self.grid.bbox[1] - miny
|
|
802
|
+
buffers[1] = buffers[1] - int(round(delta / res, 5))
|
|
803
|
+
miny = self.grid.bbox[1]
|
|
804
|
+
if self.grid.bbox[2] < maxx:
|
|
805
|
+
delta = maxx - self.grid.bbox[2]
|
|
806
|
+
buffers[2] = buffers[2] - int(round(delta / res, 5))
|
|
807
|
+
maxx = self.grid.bbox[2]
|
|
808
|
+
if self.grid.bbox[3] < maxy:
|
|
809
|
+
delta = maxy - self.grid.bbox[3]
|
|
810
|
+
buffers[3] = buffers[3] - int(round(delta / res, 5))
|
|
811
|
+
maxy = self.grid.bbox[3]
|
|
812
|
+
return (minx, miny, maxx, maxy), tuple(buffers)
|
|
813
|
+
|
|
814
|
+
def meta_tile(self, tile_coord):
|
|
815
|
+
"""
|
|
816
|
+
Returns the meta tile for `tile_coord`.
|
|
817
|
+
"""
|
|
818
|
+
tile_coord = self.main_tile(tile_coord)
|
|
819
|
+
level = tile_coord[2]
|
|
820
|
+
bbox, buffers = self._meta_bbox(tile_coord)
|
|
821
|
+
grid_size = self._meta_size(level)
|
|
822
|
+
size = self._size_from_buffered_bbox(bbox, level)
|
|
823
|
+
|
|
824
|
+
tile_patterns = self._tiles_pattern(tile=tile_coord, grid_size=grid_size, buffers=buffers)
|
|
825
|
+
|
|
826
|
+
return MetaTile(bbox=bbox, size=size, tile_patterns=tile_patterns,
|
|
827
|
+
grid_size=grid_size
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
def minimal_meta_tile(self, tiles):
|
|
831
|
+
"""
|
|
832
|
+
Returns a MetaTile that contains all `tiles` plus ``meta_buffer``,
|
|
833
|
+
but nothing more.
|
|
834
|
+
"""
|
|
835
|
+
|
|
836
|
+
tiles, grid_size, bounds = self._full_tile_list(tiles)
|
|
837
|
+
tiles = list(tiles)
|
|
838
|
+
bbox, buffers = self._meta_bbox(tiles=bounds)
|
|
839
|
+
|
|
840
|
+
level = tiles[0][2]
|
|
841
|
+
size = self._size_from_buffered_bbox(bbox, level)
|
|
842
|
+
|
|
843
|
+
tile_pattern = self._tiles_pattern(tiles=tiles, grid_size=grid_size, buffers=buffers)
|
|
844
|
+
|
|
845
|
+
return MetaTile(
|
|
846
|
+
bbox=bbox,
|
|
847
|
+
size=size,
|
|
848
|
+
tile_patterns=tile_pattern,
|
|
849
|
+
grid_size=grid_size,
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
def _size_from_buffered_bbox(self, bbox, level):
|
|
853
|
+
# meta_size * tile_size + 2*buffer does not work,
|
|
854
|
+
# since the buffer can get truncated at the grid border
|
|
855
|
+
res = self.grid.resolution(level)
|
|
856
|
+
width = int(round((bbox[2] - bbox[0]) / res))
|
|
857
|
+
height = int(round((bbox[3] - bbox[1]) / res))
|
|
858
|
+
return width, height
|
|
859
|
+
|
|
860
|
+
def _full_tile_list(self, tiles):
|
|
861
|
+
"""
|
|
862
|
+
Return a complete list of all tiles that a minimal meta tile with `tiles` contains.
|
|
863
|
+
|
|
864
|
+
>>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
|
|
865
|
+
>>> mgrid._full_tile_list([(0, 0, 2), (1, 1, 2)])
|
|
866
|
+
([(0, 1, 2), (1, 1, 2), (0, 0, 2), (1, 0, 2)], (2, 2), ((0, 0, 2), (1, 1, 2)))
|
|
867
|
+
"""
|
|
868
|
+
tile = tiles.pop()
|
|
869
|
+
z = tile[2]
|
|
870
|
+
minx = maxx = tile[0]
|
|
871
|
+
miny = maxy = tile[1]
|
|
872
|
+
|
|
873
|
+
for tile in tiles:
|
|
874
|
+
x, y = tile[:2]
|
|
875
|
+
minx = min(minx, x)
|
|
876
|
+
maxx = max(maxx, x)
|
|
877
|
+
miny = min(miny, y)
|
|
878
|
+
maxy = max(maxy, y)
|
|
879
|
+
|
|
880
|
+
grid_size = 1+maxx-minx, 1+maxy-miny
|
|
881
|
+
|
|
882
|
+
if self.grid.flipped_y_axis:
|
|
883
|
+
ys = range(miny, maxy+1)
|
|
884
|
+
else:
|
|
885
|
+
ys = range(maxy, miny-1, -1)
|
|
886
|
+
xs = range(minx, maxx+1)
|
|
887
|
+
|
|
888
|
+
bounds = (minx, miny, z), (maxx, maxy, z)
|
|
889
|
+
|
|
890
|
+
return list(_create_tile_list(xs, ys, z, (maxx+1, maxy+1))), grid_size, bounds
|
|
891
|
+
|
|
892
|
+
def main_tile(self, tile_coord):
|
|
893
|
+
x, y, z = tile_coord
|
|
894
|
+
|
|
895
|
+
meta_size = self._meta_size(z)
|
|
896
|
+
|
|
897
|
+
x0 = x//meta_size[0] * meta_size[0]
|
|
898
|
+
y0 = y//meta_size[1] * meta_size[1]
|
|
899
|
+
|
|
900
|
+
return x0, y0, z
|
|
901
|
+
|
|
902
|
+
def tile_list(self, main_tile):
|
|
903
|
+
tile_grid = self._meta_size(main_tile[2])
|
|
904
|
+
return self._meta_tile_list(main_tile, tile_grid)
|
|
905
|
+
|
|
906
|
+
def _meta_tile_list(self, main_tile, tile_grid):
|
|
907
|
+
"""
|
|
908
|
+
>>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
|
|
909
|
+
>>> mgrid._meta_tile_list((0, 1, 3), (2, 2))
|
|
910
|
+
[(0, 1, 3), (1, 1, 3), (0, 0, 3), (1, 0, 3)]
|
|
911
|
+
"""
|
|
912
|
+
minx, miny, z = self.main_tile(main_tile)
|
|
913
|
+
maxx = minx + tile_grid[0] - 1
|
|
914
|
+
maxy = miny + tile_grid[1] - 1
|
|
915
|
+
if self.grid.flipped_y_axis:
|
|
916
|
+
ys = range(miny, maxy+1)
|
|
917
|
+
else:
|
|
918
|
+
ys = range(maxy, miny-1, -1)
|
|
919
|
+
xs = range(minx, maxx+1)
|
|
920
|
+
|
|
921
|
+
return list(_create_tile_list(xs, ys, z, self.grid.grid_sizes[z]))
|
|
922
|
+
|
|
923
|
+
def _tiles_pattern(self, grid_size, buffers, tile=None, tiles=None):
|
|
924
|
+
"""
|
|
925
|
+
Returns the tile pattern for the given list of tiles.
|
|
926
|
+
The result contains for each tile the ``tile_coord`` and the upper-left
|
|
927
|
+
pixel coordinate of the tile in the meta tile image.
|
|
928
|
+
|
|
929
|
+
>>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
|
|
930
|
+
>>> tiles = list(mgrid._tiles_pattern(tiles=[(0, 1, 2), (1, 1, 2)],
|
|
931
|
+
... grid_size=(2, 1),
|
|
932
|
+
... buffers=(0, 0, 10, 10)))
|
|
933
|
+
>>> tiles[0], tiles[-1]
|
|
934
|
+
(((0, 1, 2), (0, 10)), ((1, 1, 2), (256, 10)))
|
|
935
|
+
|
|
936
|
+
>>> tiles = list(mgrid._tiles_pattern(tile=(1, 1, 2),
|
|
937
|
+
... grid_size=(2, 2),
|
|
938
|
+
... buffers=(10, 20, 30, 40)))
|
|
939
|
+
>>> tiles[0], tiles[-1]
|
|
940
|
+
(((0, 1, 2), (10, 40)), ((1, 0, 2), (266, 296)))
|
|
941
|
+
|
|
942
|
+
"""
|
|
943
|
+
if tile:
|
|
944
|
+
tiles = self._meta_tile_list(tile, grid_size)
|
|
945
|
+
|
|
946
|
+
for i in range(grid_size[1]):
|
|
947
|
+
for j in range(grid_size[0]):
|
|
948
|
+
yield tiles[j+i*grid_size[0]], (
|
|
949
|
+
j*self.grid.tile_size[0] + buffers[0],
|
|
950
|
+
i*self.grid.tile_size[1] + buffers[3])
|
|
951
|
+
|
|
952
|
+
def _meta_size(self, level):
|
|
953
|
+
grid_size = self.grid.grid_sizes[level]
|
|
954
|
+
return min(self.meta_size[0], grid_size[0]), min(self.meta_size[1], grid_size[1])
|
|
955
|
+
|
|
956
|
+
def get_affected_level_tiles(self, bbox, level):
|
|
957
|
+
"""
|
|
958
|
+
Get a list with all affected tiles for a `bbox` in the given `level`.
|
|
959
|
+
|
|
960
|
+
:returns: the bbox, the size and a list with tile coordinates, sorted row-wise
|
|
961
|
+
:rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
|
|
962
|
+
|
|
963
|
+
>>> grid = MetaGrid(TileGrid(), (2, 2))
|
|
964
|
+
>>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
|
|
965
|
+
>>> grid.get_affected_level_tiles(bbox, 0)
|
|
966
|
+
... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
|
|
967
|
+
((-20037508.342789244, -20037508.342789244,\
|
|
968
|
+
20037508.342789244, 20037508.342789244), (1, 1),\
|
|
969
|
+
<generator object ...>)
|
|
970
|
+
"""
|
|
971
|
+
|
|
972
|
+
# remove 1/10 of a pixel so we don't get a tiles we only touch
|
|
973
|
+
delta = self.grid.resolutions[level] / 10.0
|
|
974
|
+
x0, y0, _ = self.grid.tile(bbox[0]+delta, bbox[1]+delta, level)
|
|
975
|
+
x1, y1, _ = self.grid.tile(bbox[2]-delta, bbox[3]-delta, level)
|
|
976
|
+
|
|
977
|
+
meta_size = self._meta_size(level)
|
|
978
|
+
|
|
979
|
+
x0 = x0//meta_size[0] * meta_size[0]
|
|
980
|
+
x1 = x1//meta_size[0] * meta_size[0]
|
|
981
|
+
y0 = y0//meta_size[1] * meta_size[1]
|
|
982
|
+
y1 = y1//meta_size[1] * meta_size[1]
|
|
983
|
+
|
|
984
|
+
try:
|
|
985
|
+
return self._tile_iter(x0, y0, x1, y1, level)
|
|
986
|
+
except IndexError:
|
|
987
|
+
raise GridError('Invalid BBOX')
|
|
988
|
+
|
|
989
|
+
def _tile_iter(self, x0, y0, x1, y1, level):
|
|
990
|
+
meta_size = self._meta_size(level)
|
|
991
|
+
|
|
992
|
+
xs = list(range(x0, x1+1, meta_size[0]))
|
|
993
|
+
if self.grid.flipped_y_axis:
|
|
994
|
+
y0, y1 = y1, y0
|
|
995
|
+
ys = list(range(y0, y1+1, meta_size[1]))
|
|
996
|
+
else:
|
|
997
|
+
ys = list(range(y1, y0-1, -meta_size[1]))
|
|
998
|
+
|
|
999
|
+
ll = (xs[0], ys[-1], level)
|
|
1000
|
+
ur = (xs[-1], ys[0], level)
|
|
1001
|
+
# add meta_size to get full affected bbox
|
|
1002
|
+
ur = ur[0]+meta_size[0]-1, ur[1]+meta_size[1]-1, ur[2]
|
|
1003
|
+
abbox = self.grid._tiles_bbox([ll, ur])
|
|
1004
|
+
return (abbox, (len(xs), len(ys)),
|
|
1005
|
+
_create_tile_list(xs, ys, level, self.grid.grid_sizes[level]))
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
class MetaTile(object):
|
|
1009
|
+
def __init__(self, bbox, size, tile_patterns, grid_size):
|
|
1010
|
+
self.bbox = bbox
|
|
1011
|
+
self.size = size
|
|
1012
|
+
self.tile_patterns = list(tile_patterns)
|
|
1013
|
+
self.grid_size = grid_size
|
|
1014
|
+
|
|
1015
|
+
@property
|
|
1016
|
+
def tiles(self):
|
|
1017
|
+
return [t[0] for t in self.tile_patterns]
|
|
1018
|
+
|
|
1019
|
+
@property
|
|
1020
|
+
def main_tile_coord(self):
|
|
1021
|
+
"""
|
|
1022
|
+
Returns the "main" tile of the meta tile. This tile(coord) can be used
|
|
1023
|
+
for locking.
|
|
1024
|
+
|
|
1025
|
+
>>> t = MetaTile(None, None, [((0, 0, 0), (0, 0)), ((1, 0, 0), (100, 0))], (2, 1))
|
|
1026
|
+
>>> t.main_tile_coord
|
|
1027
|
+
(0, 0, 0)
|
|
1028
|
+
>>> t = MetaTile(None, None, [(None, None), ((1, 0, 0), (100, 0))], (2, 1))
|
|
1029
|
+
>>> t.main_tile_coord
|
|
1030
|
+
(1, 0, 0)
|
|
1031
|
+
"""
|
|
1032
|
+
for t in self.tiles:
|
|
1033
|
+
if t is not None:
|
|
1034
|
+
return t
|
|
1035
|
+
|
|
1036
|
+
def __repr__(self):
|
|
1037
|
+
return "MetaTile(%r, %r, %r, %r)" % (self.bbox, self.size, self.grid_size,
|
|
1038
|
+
self.tile_patterns)
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def bbox_intersects(one, two):
|
|
1042
|
+
a_x0, a_y0, a_x1, a_y1 = one
|
|
1043
|
+
b_x0, b_y0, b_x1, b_y1 = two
|
|
1044
|
+
|
|
1045
|
+
if (
|
|
1046
|
+
a_x0 < b_x1 and
|
|
1047
|
+
a_x1 > b_x0 and
|
|
1048
|
+
a_y0 < b_y1 and
|
|
1049
|
+
a_y1 > b_y0
|
|
1050
|
+
):
|
|
1051
|
+
return True
|
|
1052
|
+
|
|
1053
|
+
return False
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
def bbox_contains(one, two):
|
|
1057
|
+
"""
|
|
1058
|
+
Returns ``True`` if `one` contains `two`.
|
|
1059
|
+
|
|
1060
|
+
>>> bbox_contains([0, 0, 10, 10], [2, 2, 4, 4])
|
|
1061
|
+
True
|
|
1062
|
+
>>> bbox_contains([0, 0, 10, 10], [0, 0, 11, 10])
|
|
1063
|
+
False
|
|
1064
|
+
|
|
1065
|
+
Allow tiny rounding errors:
|
|
1066
|
+
|
|
1067
|
+
>>> bbox_contains([0, 0, 10, 10], [0.000001, 0.0000001, 10.000001, 10.000001])
|
|
1068
|
+
False
|
|
1069
|
+
>>> bbox_contains([0, 0, 10, 10], [0.0000000000001, 0.0000000000001, 10.0000000000001, 10.0000000000001])
|
|
1070
|
+
True
|
|
1071
|
+
"""
|
|
1072
|
+
a_x0, a_y0, a_x1, a_y1 = one
|
|
1073
|
+
b_x0, b_y0, b_x1, b_y1 = two
|
|
1074
|
+
|
|
1075
|
+
x_delta = abs(a_x1 - a_x0) / 10e12
|
|
1076
|
+
y_delta = abs(a_y1 - a_y0) / 10e12
|
|
1077
|
+
|
|
1078
|
+
if (
|
|
1079
|
+
a_x0 <= b_x0 + x_delta and
|
|
1080
|
+
a_x1 >= b_x1 - x_delta and
|
|
1081
|
+
a_y0 <= b_y0 + y_delta and
|
|
1082
|
+
a_y1 >= b_y1 - y_delta
|
|
1083
|
+
):
|
|
1084
|
+
return True
|
|
1085
|
+
|
|
1086
|
+
return False
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def deg_to_m(deg):
|
|
1090
|
+
return deg * (6378137 * 2 * math.pi) / 360
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
OGC_PIXEL_SIZE = 0.00028 # m/px
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def ogc_scale_to_res(scale):
|
|
1097
|
+
return scale * OGC_PIXEL_SIZE
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def res_to_ogc_scale(res):
|
|
1101
|
+
return res / OGC_PIXEL_SIZE
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def resolution_range(min_res=None, max_res=None, max_scale=None, min_scale=None):
|
|
1105
|
+
if min_scale == max_scale == min_res == max_res is None:
|
|
1106
|
+
return None
|
|
1107
|
+
if min_res or max_res:
|
|
1108
|
+
if not max_scale and not min_scale:
|
|
1109
|
+
return ResolutionRange(min_res, max_res)
|
|
1110
|
+
elif max_scale or min_scale:
|
|
1111
|
+
if not min_res and not max_res:
|
|
1112
|
+
min_res = ogc_scale_to_res(max_scale)
|
|
1113
|
+
max_res = ogc_scale_to_res(min_scale)
|
|
1114
|
+
return ResolutionRange(min_res, max_res)
|
|
1115
|
+
|
|
1116
|
+
raise ValueError('requires either min_res/max_res or max_scale/min_scale')
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
class ResolutionRange(object):
|
|
1120
|
+
def __init__(self, min_res, max_res):
|
|
1121
|
+
self.min_res = min_res
|
|
1122
|
+
self.max_res = max_res
|
|
1123
|
+
|
|
1124
|
+
if min_res and max_res:
|
|
1125
|
+
assert min_res > max_res
|
|
1126
|
+
|
|
1127
|
+
def scale_denominator(self):
|
|
1128
|
+
min_scale = res_to_ogc_scale(self.max_res) if self.max_res else None
|
|
1129
|
+
max_scale = res_to_ogc_scale(self.min_res) if self.min_res else None
|
|
1130
|
+
return min_scale, max_scale
|
|
1131
|
+
|
|
1132
|
+
def scale_hint(self):
|
|
1133
|
+
"""
|
|
1134
|
+
Returns the min and max diagonal resolution.
|
|
1135
|
+
"""
|
|
1136
|
+
min_res = self.min_res
|
|
1137
|
+
max_res = self.max_res
|
|
1138
|
+
if min_res:
|
|
1139
|
+
min_res = math.sqrt(2*min_res**2)
|
|
1140
|
+
if max_res:
|
|
1141
|
+
max_res = math.sqrt(2*max_res**2)
|
|
1142
|
+
return min_res, max_res
|
|
1143
|
+
|
|
1144
|
+
def contains(self, bbox, size, srs):
|
|
1145
|
+
width, height = bbox_size(bbox)
|
|
1146
|
+
if srs.is_latlong:
|
|
1147
|
+
width = deg_to_m(width)
|
|
1148
|
+
height = deg_to_m(height)
|
|
1149
|
+
|
|
1150
|
+
x_res = width/size[0]
|
|
1151
|
+
y_res = height/size[1]
|
|
1152
|
+
|
|
1153
|
+
if self.min_res:
|
|
1154
|
+
min_res = self.min_res + 1e-6
|
|
1155
|
+
if min_res <= x_res or min_res <= y_res:
|
|
1156
|
+
return False
|
|
1157
|
+
if self.max_res:
|
|
1158
|
+
max_res = self.max_res
|
|
1159
|
+
if max_res > x_res or max_res > y_res:
|
|
1160
|
+
return False
|
|
1161
|
+
|
|
1162
|
+
return True
|
|
1163
|
+
|
|
1164
|
+
def __eq__(self, other):
|
|
1165
|
+
if not isinstance(other, ResolutionRange):
|
|
1166
|
+
return NotImplemented
|
|
1167
|
+
|
|
1168
|
+
return (self.min_res == other.min_res
|
|
1169
|
+
and self.max_res == other.max_res)
|
|
1170
|
+
|
|
1171
|
+
def __ne__(self, other):
|
|
1172
|
+
if not isinstance(other, ResolutionRange):
|
|
1173
|
+
return NotImplemented
|
|
1174
|
+
return not self == other
|
|
1175
|
+
|
|
1176
|
+
def __repr__(self):
|
|
1177
|
+
return '<ResolutionRange(min_res=%.3f, max_res=%.3f)>' % (
|
|
1178
|
+
self.min_res or 9e99, self.max_res or 0)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def max_with_none(a, b):
|
|
1182
|
+
if a is None or b is None:
|
|
1183
|
+
return None
|
|
1184
|
+
else:
|
|
1185
|
+
return max(a, b)
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
def min_with_none(a, b):
|
|
1189
|
+
if a is None or b is None:
|
|
1190
|
+
return None
|
|
1191
|
+
else:
|
|
1192
|
+
return min(a, b)
|
|
1193
|
+
|
|
1194
|
+
|
|
1195
|
+
def merge_resolution_range(a, b):
|
|
1196
|
+
if a and b:
|
|
1197
|
+
return resolution_range(min_res=max_with_none(a.min_res, b.min_res),
|
|
1198
|
+
max_res=min_with_none(a.max_res, b.max_res))
|
|
1199
|
+
return None
|