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
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2016-2017 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
|
+
import contextlib
|
|
17
|
+
import errno
|
|
18
|
+
import hashlib
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import struct
|
|
22
|
+
from io import BytesIO
|
|
23
|
+
|
|
24
|
+
from mapproxy.image import ImageSource
|
|
25
|
+
from mapproxy.cache.base import TileCacheBase, tile_buffer
|
|
26
|
+
from mapproxy.util.fs import ensure_directory, write_atomic
|
|
27
|
+
from mapproxy.util.lock import FileLock
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
log = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CompactCacheBase(TileCacheBase):
|
|
34
|
+
supports_timestamp = False
|
|
35
|
+
bundle_class = None
|
|
36
|
+
|
|
37
|
+
def __init__(self, cache_dir, coverage=None):
|
|
38
|
+
super(CompactCacheBase, self).__init__(coverage)
|
|
39
|
+
md5 = hashlib.new('md5', cache_dir.encode('utf-8'), usedforsecurity=False)
|
|
40
|
+
self.lock_cache_id = 'compactcache-' + md5.hexdigest()
|
|
41
|
+
self.cache_dir = cache_dir
|
|
42
|
+
|
|
43
|
+
def _get_bundle_fname_and_offset(self, tile_coord):
|
|
44
|
+
x, y, z = tile_coord
|
|
45
|
+
|
|
46
|
+
level_dir = os.path.join(self.cache_dir, 'L%02d' % z)
|
|
47
|
+
|
|
48
|
+
c = x // BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_WIDTH
|
|
49
|
+
r = y // BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_HEIGHT
|
|
50
|
+
|
|
51
|
+
basename = 'R%04xC%04x' % (r, c)
|
|
52
|
+
return os.path.join(level_dir, basename), (c, r)
|
|
53
|
+
|
|
54
|
+
def _get_bundle(self, tile_coord):
|
|
55
|
+
bundle_fname, offset = self._get_bundle_fname_and_offset(tile_coord)
|
|
56
|
+
return self.bundle_class(bundle_fname, offset=offset)
|
|
57
|
+
|
|
58
|
+
def is_cached(self, tile, dimensions=None):
|
|
59
|
+
if tile.coord is None:
|
|
60
|
+
return True
|
|
61
|
+
if tile.source:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
return self._get_bundle(tile.coord).is_cached(tile, dimensions=dimensions)
|
|
65
|
+
|
|
66
|
+
def store_tile(self, tile, dimensions=None):
|
|
67
|
+
if tile.stored:
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
return self._get_bundle(tile.coord).store_tile(tile, dimensions=dimensions)
|
|
71
|
+
|
|
72
|
+
def store_tiles(self, tiles, dimensions=None):
|
|
73
|
+
if len(tiles) > 1:
|
|
74
|
+
# Check if all tiles are from a single bundle.
|
|
75
|
+
bundle_files = set()
|
|
76
|
+
tile_coord = None
|
|
77
|
+
for t in tiles:
|
|
78
|
+
if t.stored:
|
|
79
|
+
continue
|
|
80
|
+
bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
|
|
81
|
+
tile_coord = t.coord
|
|
82
|
+
if len(bundle_files) == 1:
|
|
83
|
+
return self._get_bundle(tile_coord).store_tiles(tiles, dimensions=dimensions)
|
|
84
|
+
|
|
85
|
+
# Tiles are across multiple bundles
|
|
86
|
+
failed = False
|
|
87
|
+
for tile in tiles:
|
|
88
|
+
if not self.store_tile(tile, dimensions=dimensions):
|
|
89
|
+
failed = True
|
|
90
|
+
return not failed
|
|
91
|
+
|
|
92
|
+
def load_tile(self, tile, with_metadata=False, dimensions=None):
|
|
93
|
+
if tile.source or tile.coord is None:
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
return self._get_bundle(tile.coord).load_tile(tile, dimensions=dimensions)
|
|
97
|
+
|
|
98
|
+
def load_tiles(self, tiles, with_metadata=False, dimensions=None):
|
|
99
|
+
if len(tiles) > 1:
|
|
100
|
+
# Check if all tiles are from a single bundle.
|
|
101
|
+
bundle_files = set()
|
|
102
|
+
tile_coord = None
|
|
103
|
+
for t in tiles:
|
|
104
|
+
if t.source or t.coord is None:
|
|
105
|
+
continue
|
|
106
|
+
bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
|
|
107
|
+
tile_coord = t.coord
|
|
108
|
+
if len(bundle_files) == 1:
|
|
109
|
+
return self._get_bundle(tile_coord).load_tiles(tiles, dimensions=dimensions)
|
|
110
|
+
|
|
111
|
+
# No support_bulk_load or tiles are across multiple bundles
|
|
112
|
+
missing = False
|
|
113
|
+
for tile in tiles:
|
|
114
|
+
if not self.load_tile(tile, with_metadata=with_metadata, dimensions=dimensions):
|
|
115
|
+
missing = True
|
|
116
|
+
return not missing
|
|
117
|
+
|
|
118
|
+
def remove_tile(self, tile, dimensions=None):
|
|
119
|
+
if tile.coord is None:
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
return self._get_bundle(tile.coord).remove_tile(tile, dimensions=dimensions)
|
|
123
|
+
|
|
124
|
+
def load_tile_metadata(self, tile, dimensions=None):
|
|
125
|
+
if self.load_tile(tile, dimensions=dimensions):
|
|
126
|
+
tile.timestamp = -1
|
|
127
|
+
|
|
128
|
+
def remove_level_tiles_before(self, level, timestamp):
|
|
129
|
+
if timestamp == 0:
|
|
130
|
+
level_dir = os.path.join(self.cache_dir, 'L%02d' % level)
|
|
131
|
+
shutil.rmtree(level_dir, ignore_errors=True)
|
|
132
|
+
return True
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
BUNDLE_EXT = '.bundle'
|
|
137
|
+
BUNDLEX_V1_EXT = '.bundlx'
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class BundleV1(object):
|
|
141
|
+
def __init__(self, base_filename, offset):
|
|
142
|
+
self.base_filename = base_filename
|
|
143
|
+
self.lock_filename = base_filename + '.lck'
|
|
144
|
+
self.offset = offset
|
|
145
|
+
|
|
146
|
+
def _rel_tile_coord(self, tile_coord):
|
|
147
|
+
return (
|
|
148
|
+
tile_coord[0] % BUNDLEX_V1_GRID_WIDTH,
|
|
149
|
+
tile_coord[1] % BUNDLEX_V1_GRID_HEIGHT,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def data(self):
|
|
153
|
+
return BundleDataV1(self.base_filename + BUNDLE_EXT, self.offset)
|
|
154
|
+
|
|
155
|
+
def index(self):
|
|
156
|
+
return BundleIndexV1(self.base_filename + BUNDLEX_V1_EXT)
|
|
157
|
+
|
|
158
|
+
def is_cached(self, tile, dimensions=None):
|
|
159
|
+
if tile.source or tile.coord is None:
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
with self.index().readonly() as idx:
|
|
163
|
+
if not idx:
|
|
164
|
+
return False
|
|
165
|
+
x, y = self._rel_tile_coord(tile.coord)
|
|
166
|
+
offset = idx.tile_offset(x, y)
|
|
167
|
+
if offset == 0:
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
with self.data().readonly() as bundle:
|
|
171
|
+
size = bundle.read_size(offset)
|
|
172
|
+
return size != 0
|
|
173
|
+
|
|
174
|
+
def store_tile(self, tile, dimensions=None):
|
|
175
|
+
if tile.stored:
|
|
176
|
+
return True
|
|
177
|
+
return self.store_tiles([tile], dimensions=dimensions)
|
|
178
|
+
|
|
179
|
+
def store_tiles(self, tiles, dimensions=None):
|
|
180
|
+
tiles_data = []
|
|
181
|
+
for t in tiles:
|
|
182
|
+
if t.stored:
|
|
183
|
+
continue
|
|
184
|
+
with tile_buffer(t) as buf:
|
|
185
|
+
data = buf.read()
|
|
186
|
+
tiles_data.append((t.coord, data))
|
|
187
|
+
|
|
188
|
+
with FileLock(self.lock_filename):
|
|
189
|
+
with self.data().readwrite() as bundle:
|
|
190
|
+
with self.index().readwrite() as idx:
|
|
191
|
+
for tile_coord, data in tiles_data:
|
|
192
|
+
x, y = self._rel_tile_coord(tile_coord)
|
|
193
|
+
offset = idx.tile_offset(x, y)
|
|
194
|
+
offset, size = bundle.append_tile(data, prev_offset=offset)
|
|
195
|
+
idx.update_tile_offset(x, y, offset=offset, size=size)
|
|
196
|
+
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def load_tile(self, tile, with_metadata=False, dimensions=None):
|
|
200
|
+
if tile.source or tile.coord is None:
|
|
201
|
+
return True
|
|
202
|
+
return self.load_tiles([tile], with_metadata, dimensions=dimensions)
|
|
203
|
+
|
|
204
|
+
def load_tiles(self, tiles, with_metadata=False, dimensions=None):
|
|
205
|
+
missing = False
|
|
206
|
+
|
|
207
|
+
with self.index().readonly() as idx:
|
|
208
|
+
if not idx:
|
|
209
|
+
return False
|
|
210
|
+
with self.data().readonly() as bundle:
|
|
211
|
+
for t in tiles:
|
|
212
|
+
if t.source or t.coord is None:
|
|
213
|
+
continue
|
|
214
|
+
x, y = self._rel_tile_coord(t.coord)
|
|
215
|
+
offset = idx.tile_offset(x, y)
|
|
216
|
+
if offset == 0:
|
|
217
|
+
missing = True
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
data = bundle.read_tile(offset)
|
|
221
|
+
if not data:
|
|
222
|
+
missing = True
|
|
223
|
+
continue
|
|
224
|
+
t.source = ImageSource(BytesIO(data))
|
|
225
|
+
|
|
226
|
+
return not missing
|
|
227
|
+
|
|
228
|
+
def remove_tile(self, tile, dimensions=None):
|
|
229
|
+
if tile.coord is None:
|
|
230
|
+
return True
|
|
231
|
+
|
|
232
|
+
with FileLock(self.lock_filename):
|
|
233
|
+
with self.index().readwrite() as idx:
|
|
234
|
+
x, y = self._rel_tile_coord(tile.coord)
|
|
235
|
+
idx.remove_tile_offset(x, y)
|
|
236
|
+
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
def size(self):
|
|
240
|
+
total_size = 0
|
|
241
|
+
|
|
242
|
+
with self.index().readonly() as idx:
|
|
243
|
+
if not idx:
|
|
244
|
+
return 0, 0
|
|
245
|
+
|
|
246
|
+
with self.data().readonly() as bundle:
|
|
247
|
+
for y in range(BUNDLEX_V1_GRID_HEIGHT):
|
|
248
|
+
for x in range(BUNDLEX_V1_GRID_WIDTH):
|
|
249
|
+
offset = idx.tile_offset(x, y)
|
|
250
|
+
if not offset:
|
|
251
|
+
continue
|
|
252
|
+
size = bundle.read_size(offset)
|
|
253
|
+
if not size:
|
|
254
|
+
continue
|
|
255
|
+
total_size += size + 4
|
|
256
|
+
|
|
257
|
+
actual_size = os.path.getsize(bundle.filename)
|
|
258
|
+
return total_size + BUNDLE_V1_HEADER_SIZE + (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4), actual_size
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
BUNDLEX_V1_GRID_WIDTH = 128
|
|
262
|
+
BUNDLEX_V1_GRID_HEIGHT = 128
|
|
263
|
+
BUNDLEX_V1_HEADER_SIZE = 16
|
|
264
|
+
BUNDLEX_V1_HEADER = b'\x03\x00\x00\x00\x10\x00\x00\x00\x00\x40\x00\x00\x05\x00\x00\x00'
|
|
265
|
+
BUNDLEX_V1_FOOTER_SIZE = 16
|
|
266
|
+
BUNDLEX_V1_FOOTER = b'\x00\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00'
|
|
267
|
+
|
|
268
|
+
INT64LE = struct.Struct('<Q')
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class BundleIndexV1(object):
|
|
272
|
+
def __init__(self, filename):
|
|
273
|
+
self.filename = filename
|
|
274
|
+
self._fh = None
|
|
275
|
+
# defer initialization to update/remove calls to avoid
|
|
276
|
+
# index creation on is_cached (prevents new files in read-only caches)
|
|
277
|
+
self._initialized = False
|
|
278
|
+
|
|
279
|
+
def _init_index(self):
|
|
280
|
+
self._initialized = True
|
|
281
|
+
if os.path.exists(self.filename):
|
|
282
|
+
return
|
|
283
|
+
ensure_directory(self.filename)
|
|
284
|
+
buf = BytesIO()
|
|
285
|
+
buf.write(BUNDLEX_V1_HEADER)
|
|
286
|
+
|
|
287
|
+
for i in range(BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_HEIGHT):
|
|
288
|
+
buf.write(INT64LE.pack((i*4)+BUNDLE_V1_HEADER_SIZE)[:5])
|
|
289
|
+
buf.write(BUNDLEX_V1_FOOTER)
|
|
290
|
+
write_atomic(self.filename, buf.getvalue())
|
|
291
|
+
|
|
292
|
+
def _tile_index_offset(self, x, y):
|
|
293
|
+
return BUNDLEX_V1_HEADER_SIZE + (x * BUNDLEX_V1_GRID_HEIGHT + y) * 5
|
|
294
|
+
|
|
295
|
+
def tile_offset(self, x, y):
|
|
296
|
+
if self._fh is None:
|
|
297
|
+
raise RuntimeError('not called within readonly/readwrite context')
|
|
298
|
+
idx_offset = self._tile_index_offset(x, y)
|
|
299
|
+
self._fh.seek(idx_offset)
|
|
300
|
+
offset = INT64LE.unpack(self._fh.read(5) + b'\x00\x00\x00')[0]
|
|
301
|
+
return offset
|
|
302
|
+
|
|
303
|
+
def update_tile_offset(self, x, y, offset, size):
|
|
304
|
+
if self._fh is None:
|
|
305
|
+
raise RuntimeError('not called within readwrite context')
|
|
306
|
+
idx_offset = self._tile_index_offset(x, y)
|
|
307
|
+
offset = INT64LE.pack(offset)[:5]
|
|
308
|
+
self._fh.seek(idx_offset, os.SEEK_SET)
|
|
309
|
+
self._fh.write(offset)
|
|
310
|
+
|
|
311
|
+
def remove_tile_offset(self, x, y):
|
|
312
|
+
if self._fh is None:
|
|
313
|
+
raise RuntimeError('not called within readwrite context')
|
|
314
|
+
idx_offset = self._tile_index_offset(x, y)
|
|
315
|
+
self._fh.seek(idx_offset)
|
|
316
|
+
self._fh.write(b'\x00' * 5)
|
|
317
|
+
|
|
318
|
+
@contextlib.contextmanager
|
|
319
|
+
def readonly(self):
|
|
320
|
+
try:
|
|
321
|
+
with open(self.filename, 'rb') as fh:
|
|
322
|
+
b = BundleIndexV1(self.filename)
|
|
323
|
+
b._fh = fh
|
|
324
|
+
yield b
|
|
325
|
+
except IOError as ex:
|
|
326
|
+
if ex.errno == errno.ENOENT:
|
|
327
|
+
# missing bundle file -> missing tile
|
|
328
|
+
yield None
|
|
329
|
+
else:
|
|
330
|
+
raise ex
|
|
331
|
+
|
|
332
|
+
@contextlib.contextmanager
|
|
333
|
+
def readwrite(self):
|
|
334
|
+
self._init_index()
|
|
335
|
+
with open(self.filename, 'r+b') as fh:
|
|
336
|
+
b = BundleIndexV1(self.filename)
|
|
337
|
+
b._fh = fh
|
|
338
|
+
yield b
|
|
339
|
+
|
|
340
|
+
# The bundle file has a header with 15 little-endian long values (60 bytes).
|
|
341
|
+
# NOTE: the fixed values might be some flags for image options (format, aliasing)
|
|
342
|
+
# all files available for testing had the same values however.
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
BUNDLE_V1_HEADER_SIZE = 60
|
|
346
|
+
BUNDLE_V1_HEADER = [
|
|
347
|
+
3, # 0, fixed
|
|
348
|
+
16384, # 1, max. num of tiles 128*128 = 16384
|
|
349
|
+
16, # 2, size of largest tile
|
|
350
|
+
5, # 3, fixed
|
|
351
|
+
0, # 4, num of tiles in bundle (*4)
|
|
352
|
+
60+65536, # 5, bundle size
|
|
353
|
+
40, # 6 fixed
|
|
354
|
+
16, # 7, fixed
|
|
355
|
+
0, # 8, y0
|
|
356
|
+
127, # 9, y1
|
|
357
|
+
0, # 10, x0
|
|
358
|
+
127, # 11, x1
|
|
359
|
+
]
|
|
360
|
+
BUNDLE_V1_HEADER_STRUCT_FORMAT = '<4I3Q5I'
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class BundleDataV1(object):
|
|
364
|
+
def __init__(self, filename, tile_offsets):
|
|
365
|
+
self.filename = filename
|
|
366
|
+
self.tile_offsets = tile_offsets
|
|
367
|
+
self._fh = None
|
|
368
|
+
if not os.path.exists(self.filename):
|
|
369
|
+
self._init_bundle()
|
|
370
|
+
|
|
371
|
+
def _init_bundle(self):
|
|
372
|
+
ensure_directory(self.filename)
|
|
373
|
+
header = list(BUNDLE_V1_HEADER)
|
|
374
|
+
header[10], header[8] = self.tile_offsets
|
|
375
|
+
header[11], header[9] = header[10]+127, header[8]+127
|
|
376
|
+
write_atomic(self.filename,
|
|
377
|
+
struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header) +
|
|
378
|
+
# zero-size entry for each tile
|
|
379
|
+
(b'\x00' * (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4)))
|
|
380
|
+
|
|
381
|
+
@contextlib.contextmanager
|
|
382
|
+
def readonly(self):
|
|
383
|
+
with open(self.filename, 'rb') as fh:
|
|
384
|
+
b = BundleDataV1(self.filename, self.tile_offsets)
|
|
385
|
+
b._fh = fh
|
|
386
|
+
yield b
|
|
387
|
+
|
|
388
|
+
@contextlib.contextmanager
|
|
389
|
+
def readwrite(self):
|
|
390
|
+
with open(self.filename, 'r+b') as fh:
|
|
391
|
+
b = BundleDataV1(self.filename, self.tile_offsets)
|
|
392
|
+
b._fh = fh
|
|
393
|
+
yield b
|
|
394
|
+
|
|
395
|
+
def read_size(self, offset):
|
|
396
|
+
if self._fh is None:
|
|
397
|
+
raise RuntimeError('not called within readonly/readwrite context')
|
|
398
|
+
self._fh.seek(offset)
|
|
399
|
+
return struct.unpack('<L', self._fh.read(4))[0]
|
|
400
|
+
|
|
401
|
+
def read_tile(self, offset):
|
|
402
|
+
if self._fh is None:
|
|
403
|
+
raise RuntimeError('not called within readonly/readwrite context')
|
|
404
|
+
self._fh.seek(offset)
|
|
405
|
+
size = struct.unpack('<L', self._fh.read(4))[0]
|
|
406
|
+
if size <= 0:
|
|
407
|
+
return False
|
|
408
|
+
return self._fh.read(size)
|
|
409
|
+
|
|
410
|
+
def append_tile(self, data, prev_offset):
|
|
411
|
+
if self._fh is None:
|
|
412
|
+
raise RuntimeError('not called within readwrite context')
|
|
413
|
+
size = len(data)
|
|
414
|
+
is_new_tile = True
|
|
415
|
+
if prev_offset:
|
|
416
|
+
self._fh.seek(prev_offset, os.SEEK_SET)
|
|
417
|
+
if self._fh.tell() == prev_offset:
|
|
418
|
+
if struct.unpack('<L', self._fh.read(4))[0] > 0:
|
|
419
|
+
is_new_tile = False
|
|
420
|
+
|
|
421
|
+
self._fh.seek(0, os.SEEK_END)
|
|
422
|
+
offset = self._fh.tell()
|
|
423
|
+
if offset == 0:
|
|
424
|
+
self._fh.write(b'\x00' * 16) # header
|
|
425
|
+
offset = 16
|
|
426
|
+
self._fh.write(struct.pack('<L', size))
|
|
427
|
+
self._fh.write(data)
|
|
428
|
+
|
|
429
|
+
# update header
|
|
430
|
+
self._fh.seek(0, os.SEEK_SET)
|
|
431
|
+
header = list(struct.unpack(BUNDLE_V1_HEADER_STRUCT_FORMAT, self._fh.read(60)))
|
|
432
|
+
header[2] = max(header[2], size)
|
|
433
|
+
header[5] += size + 4
|
|
434
|
+
if is_new_tile:
|
|
435
|
+
header[4] += 4
|
|
436
|
+
self._fh.seek(0, os.SEEK_SET)
|
|
437
|
+
self._fh.write(struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header))
|
|
438
|
+
|
|
439
|
+
return offset, size
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
BUNDLE_V2_GRID_WIDTH = 128
|
|
443
|
+
BUNDLE_V2_GRID_HEIGHT = 128
|
|
444
|
+
BUNDLE_V2_TILES = BUNDLE_V2_GRID_WIDTH * BUNDLE_V2_GRID_HEIGHT
|
|
445
|
+
BUNDLE_V2_INDEX_SIZE = BUNDLE_V2_TILES * 8
|
|
446
|
+
|
|
447
|
+
BUNDLE_V2_HEADER = (
|
|
448
|
+
3, # Version
|
|
449
|
+
BUNDLE_V2_TILES, # numRecords
|
|
450
|
+
0, # maxRecord Size
|
|
451
|
+
5, # Offset Size
|
|
452
|
+
0, # Slack Space
|
|
453
|
+
64 + BUNDLE_V2_INDEX_SIZE, # File Size
|
|
454
|
+
40, # User Header Offset
|
|
455
|
+
20 + BUNDLE_V2_INDEX_SIZE, # User Header Size
|
|
456
|
+
3, # Legacy 1
|
|
457
|
+
16, # Legacy 2 0?
|
|
458
|
+
BUNDLE_V2_TILES, # Legacy 3
|
|
459
|
+
5, # Legacy 4
|
|
460
|
+
BUNDLE_V2_INDEX_SIZE # Index Size
|
|
461
|
+
)
|
|
462
|
+
BUNDLE_V2_HEADER_STRUCT_FORMAT = '<4I3Q6I'
|
|
463
|
+
BUNDLE_V2_HEADER_SIZE = 64
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
class BundleV2(object):
|
|
467
|
+
def __init__(self, base_filename, offset=None):
|
|
468
|
+
# offset not used by V2
|
|
469
|
+
self.filename = base_filename + '.bundle'
|
|
470
|
+
self.lock_filename = base_filename + '.lck'
|
|
471
|
+
|
|
472
|
+
# defer initialization to update/remove calls to avoid
|
|
473
|
+
# index creation on is_cached (prevents new files in read-only caches)
|
|
474
|
+
self._initialized = False
|
|
475
|
+
|
|
476
|
+
def _init_index(self):
|
|
477
|
+
self._initialized = True
|
|
478
|
+
if os.path.exists(self.filename):
|
|
479
|
+
return
|
|
480
|
+
ensure_directory(self.filename)
|
|
481
|
+
buf = BytesIO()
|
|
482
|
+
buf.write(struct.pack(BUNDLE_V2_HEADER_STRUCT_FORMAT, *BUNDLE_V2_HEADER))
|
|
483
|
+
# Empty index (ArcGIS stores an offset of 4 and size of 0 for missing tiles)
|
|
484
|
+
buf.write(struct.pack('<%dQ' % BUNDLE_V2_TILES, *(4, ) * BUNDLE_V2_TILES))
|
|
485
|
+
write_atomic(self.filename, buf.getvalue())
|
|
486
|
+
|
|
487
|
+
def _tile_idx_offset(self, x, y):
|
|
488
|
+
return BUNDLE_V2_HEADER_SIZE + (x + BUNDLE_V2_GRID_HEIGHT * y) * 8
|
|
489
|
+
|
|
490
|
+
def _rel_tile_coord(self, tile_coord):
|
|
491
|
+
return (tile_coord[0] % BUNDLE_V2_GRID_WIDTH,
|
|
492
|
+
tile_coord[1] % BUNDLE_V2_GRID_HEIGHT, )
|
|
493
|
+
|
|
494
|
+
def _tile_offset_size(self, fh, x, y):
|
|
495
|
+
idx_offset = self._tile_idx_offset(x, y)
|
|
496
|
+
fh.seek(idx_offset)
|
|
497
|
+
val = INT64LE.unpack(fh.read(8))[0]
|
|
498
|
+
# Index contains 8 bytes per tile.
|
|
499
|
+
# Size is stored in 24 most significant bits.
|
|
500
|
+
# Offset in the least significant 40 bits.
|
|
501
|
+
size = val >> 40
|
|
502
|
+
if size == 0:
|
|
503
|
+
return 0, 0
|
|
504
|
+
offset = val - (size << 40)
|
|
505
|
+
return offset, size
|
|
506
|
+
|
|
507
|
+
def _load_tile(self, fh, tile, dimensions=None):
|
|
508
|
+
if tile.source or tile.coord is None:
|
|
509
|
+
return True
|
|
510
|
+
|
|
511
|
+
x, y = self._rel_tile_coord(tile.coord)
|
|
512
|
+
offset, size = self._tile_offset_size(fh, x, y)
|
|
513
|
+
if not size:
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
fh.seek(offset)
|
|
517
|
+
data = fh.read(size)
|
|
518
|
+
|
|
519
|
+
tile.source = ImageSource(BytesIO(data))
|
|
520
|
+
return True
|
|
521
|
+
|
|
522
|
+
def load_tile(self, tile, with_metadata=False, dimensions=None):
|
|
523
|
+
if tile.source or tile.coord is None:
|
|
524
|
+
return True
|
|
525
|
+
|
|
526
|
+
return self.load_tiles([tile], with_metadata, dimensions=dimensions)
|
|
527
|
+
|
|
528
|
+
def load_tiles(self, tiles, with_metadata=False, dimensions=None):
|
|
529
|
+
missing = False
|
|
530
|
+
|
|
531
|
+
with self._readonly() as fh:
|
|
532
|
+
if not fh:
|
|
533
|
+
return False
|
|
534
|
+
|
|
535
|
+
for t in tiles:
|
|
536
|
+
if t.source or t.coord is None:
|
|
537
|
+
continue
|
|
538
|
+
if not self._load_tile(fh, t):
|
|
539
|
+
missing = True
|
|
540
|
+
|
|
541
|
+
return not missing
|
|
542
|
+
|
|
543
|
+
def is_cached(self, tile, dimensions=None):
|
|
544
|
+
with self._readonly() as fh:
|
|
545
|
+
if not fh:
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
x, y = self._rel_tile_coord(tile.coord)
|
|
549
|
+
_, size = self._tile_offset_size(fh, x, y)
|
|
550
|
+
if not size:
|
|
551
|
+
return False
|
|
552
|
+
return True
|
|
553
|
+
|
|
554
|
+
def _update_tile_offset(self, fh, x, y, offset, size):
|
|
555
|
+
idx_offset = self._tile_idx_offset(x, y)
|
|
556
|
+
val = offset + (size << 40)
|
|
557
|
+
|
|
558
|
+
fh.seek(idx_offset, os.SEEK_SET)
|
|
559
|
+
fh.write(INT64LE.pack(val))
|
|
560
|
+
|
|
561
|
+
def _append_tile(self, fh, data):
|
|
562
|
+
# Write tile size first, then tile data.
|
|
563
|
+
# Offset points to actual tile data.
|
|
564
|
+
fh.seek(0, os.SEEK_END)
|
|
565
|
+
fh.write(struct.pack('<L', len(data)))
|
|
566
|
+
offset = fh.tell()
|
|
567
|
+
fh.write(data)
|
|
568
|
+
return offset
|
|
569
|
+
|
|
570
|
+
def _update_metadata(self, fh, filesize, tilesize):
|
|
571
|
+
# Max record/tile size
|
|
572
|
+
fh.seek(8)
|
|
573
|
+
old_tilesize = struct.unpack('<I', fh.read(4))[0]
|
|
574
|
+
if tilesize > old_tilesize:
|
|
575
|
+
fh.seek(8)
|
|
576
|
+
fh.write(struct.pack('<I', tilesize))
|
|
577
|
+
|
|
578
|
+
# Complete file size
|
|
579
|
+
fh.seek(24)
|
|
580
|
+
fh.write(struct.pack("<Q", filesize))
|
|
581
|
+
|
|
582
|
+
def _store_tile(self, fh, tile_coord, data, dimensions=None):
|
|
583
|
+
size = len(data)
|
|
584
|
+
x, y = self._rel_tile_coord(tile_coord)
|
|
585
|
+
offset = self._append_tile(fh, data)
|
|
586
|
+
self._update_tile_offset(fh, x, y, offset, size)
|
|
587
|
+
|
|
588
|
+
filesize = offset + size
|
|
589
|
+
self._update_metadata(fh, filesize, size)
|
|
590
|
+
|
|
591
|
+
def store_tile(self, tile, dimensions=None):
|
|
592
|
+
if tile.stored:
|
|
593
|
+
return True
|
|
594
|
+
|
|
595
|
+
return self.store_tiles([tile], dimensions=dimensions)
|
|
596
|
+
|
|
597
|
+
def store_tiles(self, tiles, dimensions=None):
|
|
598
|
+
self._init_index()
|
|
599
|
+
|
|
600
|
+
tiles_data = []
|
|
601
|
+
for t in tiles:
|
|
602
|
+
if t.stored:
|
|
603
|
+
continue
|
|
604
|
+
with tile_buffer(t) as buf:
|
|
605
|
+
data = buf.read()
|
|
606
|
+
tiles_data.append((t.coord, data))
|
|
607
|
+
|
|
608
|
+
with FileLock(self.lock_filename):
|
|
609
|
+
with self._readwrite() as fh:
|
|
610
|
+
for tile_coord, data in tiles_data:
|
|
611
|
+
self._store_tile(fh, tile_coord, data, dimensions=dimensions)
|
|
612
|
+
|
|
613
|
+
return True
|
|
614
|
+
|
|
615
|
+
def remove_tile(self, tile, dimensions=None):
|
|
616
|
+
if tile.coord is None:
|
|
617
|
+
return True
|
|
618
|
+
|
|
619
|
+
self._init_index()
|
|
620
|
+
with FileLock(self.lock_filename):
|
|
621
|
+
with self._readwrite() as fh:
|
|
622
|
+
x, y = self._rel_tile_coord(tile.coord)
|
|
623
|
+
self._update_tile_offset(fh, x, y, 0, 0)
|
|
624
|
+
|
|
625
|
+
return True
|
|
626
|
+
|
|
627
|
+
def size(self):
|
|
628
|
+
total_size = 0
|
|
629
|
+
with self._readonly() as fh:
|
|
630
|
+
if not fh:
|
|
631
|
+
return 0, 0
|
|
632
|
+
for y in range(BUNDLE_V2_GRID_HEIGHT):
|
|
633
|
+
for x in range(BUNDLE_V2_GRID_WIDTH):
|
|
634
|
+
_, size = self._tile_offset_size(fh, x, y)
|
|
635
|
+
if size:
|
|
636
|
+
total_size += size + 4
|
|
637
|
+
fh.seek(0, os.SEEK_END)
|
|
638
|
+
actual_size = fh.tell()
|
|
639
|
+
return total_size + 64 + BUNDLE_V2_INDEX_SIZE, actual_size
|
|
640
|
+
|
|
641
|
+
@contextlib.contextmanager
|
|
642
|
+
def _readonly(self):
|
|
643
|
+
try:
|
|
644
|
+
with open(self.filename, 'rb') as fh:
|
|
645
|
+
yield fh
|
|
646
|
+
except IOError as ex:
|
|
647
|
+
if ex.errno == errno.ENOENT:
|
|
648
|
+
# missing bundle file -> missing tile
|
|
649
|
+
yield None
|
|
650
|
+
else:
|
|
651
|
+
raise ex
|
|
652
|
+
|
|
653
|
+
@contextlib.contextmanager
|
|
654
|
+
def _readwrite(self):
|
|
655
|
+
self._init_index()
|
|
656
|
+
with open(self.filename, 'r+b') as fh:
|
|
657
|
+
yield fh
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class CompactCacheV1(CompactCacheBase):
|
|
661
|
+
bundle_class = BundleV1
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
class CompactCacheV2(CompactCacheBase):
|
|
665
|
+
bundle_class = BundleV2
|