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,2399 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2010-2016 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
|
+
Configuration loading and system initializing.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import division
|
|
20
|
+
from mapproxy.util.fs import find_exec
|
|
21
|
+
from mapproxy.util.yaml import load_yaml_file, YAMLError
|
|
22
|
+
from mapproxy.util.ext.odict import odict
|
|
23
|
+
from mapproxy.util.py import memoize
|
|
24
|
+
from mapproxy.config.spec import validate_options, add_source_to_mapproxy_yaml_spec, add_service_to_mapproxy_yaml_spec
|
|
25
|
+
from mapproxy.config.validator import validate
|
|
26
|
+
from mapproxy.config import load_default_config, finish_base_config, defaults
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import hashlib
|
|
31
|
+
import warnings
|
|
32
|
+
from copy import deepcopy, copy
|
|
33
|
+
from functools import partial
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
from urllib.parse import urlparse
|
|
37
|
+
|
|
38
|
+
log = logging.getLogger('mapproxy.config')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ConfigurationError(Exception):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ProxyConfiguration(object):
|
|
46
|
+
def __init__(self, conf, conf_base_dir=None, seed=False, renderd=False):
|
|
47
|
+
self.configuration = conf
|
|
48
|
+
self.seed = seed
|
|
49
|
+
self.renderd = renderd
|
|
50
|
+
|
|
51
|
+
if conf_base_dir is None:
|
|
52
|
+
conf_base_dir = os.getcwd()
|
|
53
|
+
|
|
54
|
+
self.load_globals(conf_base_dir=conf_base_dir)
|
|
55
|
+
self.load_grids()
|
|
56
|
+
self.load_caches()
|
|
57
|
+
self.load_sources()
|
|
58
|
+
self.load_wms_root_layer()
|
|
59
|
+
self.load_tile_layers()
|
|
60
|
+
self.load_services()
|
|
61
|
+
|
|
62
|
+
def load_globals(self, conf_base_dir):
|
|
63
|
+
self.globals = GlobalConfiguration(conf_base_dir=conf_base_dir,
|
|
64
|
+
conf=self.configuration.get('globals') or {},
|
|
65
|
+
context=self)
|
|
66
|
+
|
|
67
|
+
def load_grids(self):
|
|
68
|
+
self.grids = {}
|
|
69
|
+
grid_configs = dict(defaults.grids)
|
|
70
|
+
grid_configs.update(self.configuration.get('grids') or {})
|
|
71
|
+
for grid_name, grid_conf in grid_configs.items():
|
|
72
|
+
grid_conf.setdefault('name', grid_name)
|
|
73
|
+
self.grids[grid_name] = GridConfiguration(grid_conf, context=self)
|
|
74
|
+
|
|
75
|
+
def load_caches(self):
|
|
76
|
+
self.caches = odict()
|
|
77
|
+
caches_conf = self.configuration.get('caches')
|
|
78
|
+
if not caches_conf:
|
|
79
|
+
return
|
|
80
|
+
if isinstance(caches_conf, list):
|
|
81
|
+
caches_conf = list_of_dicts_to_ordered_dict(caches_conf)
|
|
82
|
+
for cache_name, cache_conf in caches_conf.items():
|
|
83
|
+
cache_conf['name'] = cache_name
|
|
84
|
+
self.caches[cache_name] = CacheConfiguration(conf=cache_conf, context=self)
|
|
85
|
+
|
|
86
|
+
def load_sources(self):
|
|
87
|
+
self.sources = SourcesCollection()
|
|
88
|
+
for source_name, source_conf in (self.configuration.get('sources') or {}).items():
|
|
89
|
+
source_conf['name'] = source_name
|
|
90
|
+
self.sources[source_name] = SourceConfiguration.load(conf=source_conf, context=self)
|
|
91
|
+
|
|
92
|
+
def load_tile_layers(self):
|
|
93
|
+
self.layers = odict()
|
|
94
|
+
layers_conf = deepcopy(self._layers_conf_dict())
|
|
95
|
+
if layers_conf is None:
|
|
96
|
+
return
|
|
97
|
+
layers = self._flatten_layers_conf_dict(layers_conf)
|
|
98
|
+
for layer_name, layer_conf in layers.items():
|
|
99
|
+
layer_conf['name'] = layer_name
|
|
100
|
+
self.layers[layer_name] = LayerConfiguration(conf=layer_conf, context=self)
|
|
101
|
+
|
|
102
|
+
def _legacy_layers_conf_dict(self):
|
|
103
|
+
"""
|
|
104
|
+
Read old style layer configuration with a dictionary where
|
|
105
|
+
the key is the layer name. Optionally: a list an each layer
|
|
106
|
+
is wrapped in such dictionary.
|
|
107
|
+
|
|
108
|
+
::
|
|
109
|
+
layers:
|
|
110
|
+
foo:
|
|
111
|
+
title: xxx
|
|
112
|
+
sources: []
|
|
113
|
+
bar:
|
|
114
|
+
title: xxx
|
|
115
|
+
sources: []
|
|
116
|
+
|
|
117
|
+
or
|
|
118
|
+
|
|
119
|
+
::
|
|
120
|
+
|
|
121
|
+
layers:
|
|
122
|
+
- foo:
|
|
123
|
+
title: xxx
|
|
124
|
+
sources: []
|
|
125
|
+
- bar:
|
|
126
|
+
title: xxx
|
|
127
|
+
sources: []
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
warnings.warn('old layer configuration syntax is deprecated since 1.4.0. '
|
|
131
|
+
'use list of dictionaries as documented', RuntimeWarning)
|
|
132
|
+
layers = []
|
|
133
|
+
layers_conf = self.configuration.get('layers')
|
|
134
|
+
if not layers_conf:
|
|
135
|
+
return None # TODO config error
|
|
136
|
+
if isinstance(layers_conf, list):
|
|
137
|
+
layers_conf = list_of_dicts_to_ordered_dict(layers_conf)
|
|
138
|
+
for layer_name, layer_conf in layers_conf.items():
|
|
139
|
+
layer_conf['name'] = layer_name
|
|
140
|
+
layers.append(layer_conf)
|
|
141
|
+
return dict(title=None, layers=layers)
|
|
142
|
+
|
|
143
|
+
def _layers_conf_dict(self):
|
|
144
|
+
"""
|
|
145
|
+
Returns (recursive) layer configuration as a dictionary
|
|
146
|
+
in unified structure:
|
|
147
|
+
|
|
148
|
+
::
|
|
149
|
+
{
|
|
150
|
+
title: 'xxx', # required, might be None
|
|
151
|
+
name: 'xxx', # optional
|
|
152
|
+
# sources or layers or both are required
|
|
153
|
+
sources: [],
|
|
154
|
+
layers: [
|
|
155
|
+
{..., ...} # more layers like this
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Multiple layers will be wrapped in an unnamed root layer, if the
|
|
160
|
+
first level starts with multiple layers.
|
|
161
|
+
"""
|
|
162
|
+
layers_conf = self.configuration.get('layers')
|
|
163
|
+
if layers_conf is None:
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
if isinstance(layers_conf, list):
|
|
167
|
+
if isinstance(layers_conf[0], dict) and len(layers_conf[0].keys()) == 1:
|
|
168
|
+
# looks like ordered legacy config
|
|
169
|
+
layers_conf = self._legacy_layers_conf_dict()
|
|
170
|
+
elif len(layers_conf) == 1 and (
|
|
171
|
+
'layers' in layers_conf[0]
|
|
172
|
+
or 'sources' in layers_conf[0]
|
|
173
|
+
or 'tile_sources' in layers_conf[0]):
|
|
174
|
+
# single root layer in list -> remove list
|
|
175
|
+
layers_conf = layers_conf[0]
|
|
176
|
+
else:
|
|
177
|
+
# layer list without root -> wrap in root layer
|
|
178
|
+
layers_conf = dict(title=None, layers=layers_conf)
|
|
179
|
+
|
|
180
|
+
if len(set(layers_conf.keys()) &
|
|
181
|
+
set('layers name title sources'.split())) < 2:
|
|
182
|
+
# looks like unordered legacy config
|
|
183
|
+
layers_conf = self._legacy_layers_conf_dict()
|
|
184
|
+
|
|
185
|
+
return layers_conf
|
|
186
|
+
|
|
187
|
+
def _flatten_layers_conf_dict(self, layers_conf, _layers=None):
|
|
188
|
+
"""
|
|
189
|
+
Returns a dictionary with all layers that have a name and sources.
|
|
190
|
+
Flattens the layer tree.
|
|
191
|
+
"""
|
|
192
|
+
layers = _layers if _layers is not None else odict()
|
|
193
|
+
|
|
194
|
+
if 'layers' in layers_conf:
|
|
195
|
+
for layer in layers_conf.pop('layers'):
|
|
196
|
+
self._flatten_layers_conf_dict(layer, layers)
|
|
197
|
+
|
|
198
|
+
if 'name' in layers_conf and ('sources' in layers_conf or 'tile_sources' in layers_conf):
|
|
199
|
+
layers[layers_conf['name']] = layers_conf
|
|
200
|
+
|
|
201
|
+
return layers
|
|
202
|
+
|
|
203
|
+
def load_wms_root_layer(self):
|
|
204
|
+
self.wms_root_layer = None
|
|
205
|
+
|
|
206
|
+
layers_conf = self._layers_conf_dict()
|
|
207
|
+
if layers_conf is None:
|
|
208
|
+
return
|
|
209
|
+
self.wms_root_layer = WMSLayerConfiguration(layers_conf, context=self)
|
|
210
|
+
|
|
211
|
+
def load_services(self):
|
|
212
|
+
self.services = ServiceConfiguration(self.configuration.get('services', {}), context=self)
|
|
213
|
+
|
|
214
|
+
def configured_services(self):
|
|
215
|
+
with self:
|
|
216
|
+
return self.services.services()
|
|
217
|
+
|
|
218
|
+
def __enter__(self):
|
|
219
|
+
# push local base_config onto config stack
|
|
220
|
+
import mapproxy.config.config
|
|
221
|
+
mapproxy.config.config._config.push(self.base_config)
|
|
222
|
+
|
|
223
|
+
def __exit__(self, type, value, traceback):
|
|
224
|
+
# pop local base_config from config stack
|
|
225
|
+
import mapproxy.config.config
|
|
226
|
+
mapproxy.config.config._config.pop()
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def base_config(self):
|
|
230
|
+
return self.globals.base_config
|
|
231
|
+
|
|
232
|
+
def config_files(self):
|
|
233
|
+
"""
|
|
234
|
+
Returns a dictionary with all configuration filenames and there timestamps.
|
|
235
|
+
Contains any included files as well (see `base` option).
|
|
236
|
+
"""
|
|
237
|
+
return self.configuration.get('__config_files__', {})
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def list_of_dicts_to_ordered_dict(dictlist):
|
|
241
|
+
"""
|
|
242
|
+
>>> d = list_of_dicts_to_ordered_dict([{'a': 1}, {'b': 2}, {'c': 3}])
|
|
243
|
+
>>> list(d.items())
|
|
244
|
+
[('a', 1), ('b', 2), ('c', 3)]
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
result = odict()
|
|
248
|
+
for d in dictlist:
|
|
249
|
+
for k, v in d.items():
|
|
250
|
+
result[k] = v
|
|
251
|
+
return result
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class ConfigurationBase(object):
|
|
255
|
+
"""
|
|
256
|
+
Base class for all configurations.
|
|
257
|
+
"""
|
|
258
|
+
defaults = {}
|
|
259
|
+
|
|
260
|
+
def __init__(self, conf, context):
|
|
261
|
+
"""
|
|
262
|
+
:param conf: the configuration part for this configurator
|
|
263
|
+
:param context: the complete proxy configuration
|
|
264
|
+
:type context: ProxyConfiguration
|
|
265
|
+
"""
|
|
266
|
+
self.conf = conf
|
|
267
|
+
self.context = context
|
|
268
|
+
for k, v in self.defaults.items():
|
|
269
|
+
if k not in self.conf:
|
|
270
|
+
self.conf[k] = v
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class GridConfiguration(ConfigurationBase):
|
|
274
|
+
@memoize
|
|
275
|
+
def tile_grid(self):
|
|
276
|
+
from mapproxy.grid import tile_grid
|
|
277
|
+
|
|
278
|
+
if 'base' in self.conf:
|
|
279
|
+
base_grid_name = self.conf['base']
|
|
280
|
+
if base_grid_name not in self.context.grids:
|
|
281
|
+
raise ConfigurationError('unknown base %s for grid %s' % (base_grid_name, self.conf['name']))
|
|
282
|
+
conf = self.context.grids[base_grid_name].conf.copy()
|
|
283
|
+
conf.update(self.conf)
|
|
284
|
+
conf.pop('base')
|
|
285
|
+
self.conf = conf
|
|
286
|
+
else:
|
|
287
|
+
conf = self.conf
|
|
288
|
+
align_with = None
|
|
289
|
+
if 'align_resolutions_with' in self.conf:
|
|
290
|
+
align_with_grid_name = self.conf['align_resolutions_with']
|
|
291
|
+
align_with = self.context.grids[align_with_grid_name].tile_grid()
|
|
292
|
+
|
|
293
|
+
tile_size = self.context.globals.get_value('tile_size', conf,
|
|
294
|
+
global_key='grid.tile_size')
|
|
295
|
+
conf['tile_size'] = tuple(tile_size)
|
|
296
|
+
tile_size = tuple(tile_size)
|
|
297
|
+
|
|
298
|
+
stretch_factor = self.context.globals.get_value('stretch_factor', conf,
|
|
299
|
+
global_key='image.stretch_factor')
|
|
300
|
+
max_shrink_factor = self.context.globals.get_value('max_shrink_factor', conf,
|
|
301
|
+
global_key='image.max_shrink_factor')
|
|
302
|
+
|
|
303
|
+
if conf.get('origin') is None:
|
|
304
|
+
log.warning(
|
|
305
|
+
'grid %s does not have an origin. default origin will change from sw (south/west) to nw (north-west)'
|
|
306
|
+
' with MapProxy 2.0', conf['name'])
|
|
307
|
+
|
|
308
|
+
grid = tile_grid(
|
|
309
|
+
name=conf['name'],
|
|
310
|
+
srs=conf.get('srs'),
|
|
311
|
+
tile_size=tile_size,
|
|
312
|
+
min_res=conf.get('min_res'),
|
|
313
|
+
max_res=conf.get('max_res'),
|
|
314
|
+
res=conf.get('res'),
|
|
315
|
+
res_factor=conf.get('res_factor', 2.0),
|
|
316
|
+
threshold_res=conf.get('threshold_res'),
|
|
317
|
+
bbox=conf.get('bbox'),
|
|
318
|
+
bbox_srs=conf.get('bbox_srs'),
|
|
319
|
+
num_levels=conf.get('num_levels'),
|
|
320
|
+
stretch_factor=stretch_factor,
|
|
321
|
+
max_shrink_factor=max_shrink_factor,
|
|
322
|
+
align_with=align_with,
|
|
323
|
+
origin=conf.get('origin')
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
return grid
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def preferred_srs(conf):
|
|
330
|
+
from mapproxy.srs import SRS, PreferredSrcSRS
|
|
331
|
+
|
|
332
|
+
preferred_conf = conf.get('preferred_src_proj', {})
|
|
333
|
+
|
|
334
|
+
if not preferred_conf:
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
preferred = PreferredSrcSRS()
|
|
338
|
+
|
|
339
|
+
for target, preferred_srcs in preferred_conf.items():
|
|
340
|
+
preferred.add(SRS(target), [SRS(s) for s in preferred_srcs])
|
|
341
|
+
|
|
342
|
+
return preferred
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class GlobalConfiguration(ConfigurationBase):
|
|
346
|
+
def __init__(self, conf_base_dir, conf, context):
|
|
347
|
+
ConfigurationBase.__init__(self, conf, context)
|
|
348
|
+
self.base_config = load_default_config()
|
|
349
|
+
self._copy_conf_values(self.conf, self.base_config)
|
|
350
|
+
self.base_config.conf_base_dir = conf_base_dir
|
|
351
|
+
finish_base_config(self.base_config)
|
|
352
|
+
|
|
353
|
+
self.image_options = ImageOptionsConfiguration(self.conf.get('image', {}), context)
|
|
354
|
+
self.preferred_srs = preferred_srs(self.conf.get('srs', {}))
|
|
355
|
+
self.renderd_address = self.get_value('renderd.address')
|
|
356
|
+
|
|
357
|
+
def _copy_conf_values(self, d, target):
|
|
358
|
+
for k, v in d.items():
|
|
359
|
+
if v is None:
|
|
360
|
+
continue
|
|
361
|
+
if (hasattr(v, 'iteritems') or hasattr(v, 'items')) and k in target:
|
|
362
|
+
self._copy_conf_values(v, target[k])
|
|
363
|
+
else:
|
|
364
|
+
target[k] = v
|
|
365
|
+
|
|
366
|
+
def get_value(self, key, local={}, global_key=None, default_key=None):
|
|
367
|
+
result = dotted_dict_get(key, local)
|
|
368
|
+
if result is None:
|
|
369
|
+
result = dotted_dict_get(global_key or key, self.conf)
|
|
370
|
+
|
|
371
|
+
if result is None:
|
|
372
|
+
result = dotted_dict_get(default_key or global_key or key, self.base_config)
|
|
373
|
+
|
|
374
|
+
return result
|
|
375
|
+
|
|
376
|
+
def get_path(self, key, local, global_key=None, default_key=None):
|
|
377
|
+
value = self.get_value(key, local, global_key, default_key)
|
|
378
|
+
if value is not None:
|
|
379
|
+
value = self.abspath(value)
|
|
380
|
+
return value
|
|
381
|
+
|
|
382
|
+
def abspath(self, path):
|
|
383
|
+
return os.path.join(self.base_config.conf_base_dir, path)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
default_image_options = {
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
class ImageOptionsConfiguration(ConfigurationBase):
|
|
391
|
+
def __init__(self, conf, context):
|
|
392
|
+
ConfigurationBase.__init__(self, conf, context)
|
|
393
|
+
self._init_formats()
|
|
394
|
+
|
|
395
|
+
def _init_formats(self):
|
|
396
|
+
self.formats = {}
|
|
397
|
+
|
|
398
|
+
formats_config = default_image_options.copy()
|
|
399
|
+
for format, conf in self.conf.get('formats', {}).items():
|
|
400
|
+
if format in formats_config:
|
|
401
|
+
tmp = formats_config[format].copy()
|
|
402
|
+
tmp.update(conf)
|
|
403
|
+
conf = tmp
|
|
404
|
+
if 'resampling_method' in conf:
|
|
405
|
+
conf['resampling'] = conf.pop('resampling_method')
|
|
406
|
+
if 'encoding_options' in conf:
|
|
407
|
+
self._check_encoding_options(conf['encoding_options'])
|
|
408
|
+
if 'merge_method' in conf:
|
|
409
|
+
warnings.warn('merge_method now defaults to composite. option no longer required',
|
|
410
|
+
DeprecationWarning)
|
|
411
|
+
formats_config[format] = conf
|
|
412
|
+
for format, conf in formats_config.items():
|
|
413
|
+
if 'format' not in conf and format.startswith('image/'):
|
|
414
|
+
conf['format'] = format
|
|
415
|
+
self.formats[format] = conf
|
|
416
|
+
|
|
417
|
+
def _check_encoding_options(self, options):
|
|
418
|
+
if not options:
|
|
419
|
+
return
|
|
420
|
+
options = options.copy()
|
|
421
|
+
jpeg_quality = options.pop('jpeg_quality', None)
|
|
422
|
+
if jpeg_quality and not isinstance(jpeg_quality, int):
|
|
423
|
+
raise ConfigurationError('jpeg_quality is not an integer')
|
|
424
|
+
|
|
425
|
+
tiff_compression = options.pop('tiff_compression', None)
|
|
426
|
+
if tiff_compression and tiff_compression not in ('raw', 'tiff_lzw', 'jpeg'):
|
|
427
|
+
raise ConfigurationError('unknown tiff_compression')
|
|
428
|
+
|
|
429
|
+
quantizer = options.pop('quantizer', None)
|
|
430
|
+
if quantizer and quantizer not in ('fastoctree', 'mediancut'):
|
|
431
|
+
raise ConfigurationError('unknown quantizer')
|
|
432
|
+
|
|
433
|
+
if options:
|
|
434
|
+
raise ConfigurationError('unknown encoding_options: %r' % options)
|
|
435
|
+
|
|
436
|
+
def image_opts(self, image_conf, format):
|
|
437
|
+
from mapproxy.image.opts import ImageOptions
|
|
438
|
+
if not image_conf:
|
|
439
|
+
image_conf = {}
|
|
440
|
+
|
|
441
|
+
conf = {}
|
|
442
|
+
if format in self.formats:
|
|
443
|
+
conf = self.formats[format].copy()
|
|
444
|
+
|
|
445
|
+
resampling = image_conf.get('resampling_method') or conf.get('resampling')
|
|
446
|
+
if resampling is None:
|
|
447
|
+
resampling = self.context.globals.get_value('image.resampling_method', {})
|
|
448
|
+
transparent = image_conf.get('transparent')
|
|
449
|
+
opacity = image_conf.get('opacity')
|
|
450
|
+
img_format = image_conf.get('format')
|
|
451
|
+
colors = image_conf.get('colors')
|
|
452
|
+
mode = image_conf.get('mode')
|
|
453
|
+
encoding_options = image_conf.get('encoding_options')
|
|
454
|
+
if 'merge_method' in image_conf:
|
|
455
|
+
warnings.warn('merge_method now defaults to composite. option no longer required',
|
|
456
|
+
DeprecationWarning)
|
|
457
|
+
|
|
458
|
+
self._check_encoding_options(encoding_options)
|
|
459
|
+
|
|
460
|
+
# only overwrite default if it is not None
|
|
461
|
+
for k, v in dict(
|
|
462
|
+
transparent=transparent, opacity=opacity, resampling=resampling,
|
|
463
|
+
format=img_format, colors=colors, mode=mode, encoding_options=encoding_options,
|
|
464
|
+
).items():
|
|
465
|
+
if v is not None:
|
|
466
|
+
conf[k] = v
|
|
467
|
+
|
|
468
|
+
if 'format' not in conf and format and format.startswith('image/'):
|
|
469
|
+
conf['format'] = format
|
|
470
|
+
|
|
471
|
+
# caches shall be able to store png and jpeg tiles with mixed format
|
|
472
|
+
if format == 'mixed':
|
|
473
|
+
conf['format'] = format
|
|
474
|
+
conf['transparent'] = True
|
|
475
|
+
|
|
476
|
+
# force 256 colors for image.paletted for backwards compat
|
|
477
|
+
paletted = self.context.globals.get_value('image.paletted', self.conf)
|
|
478
|
+
if conf.get('colors') is None and 'png' in conf.get('format', '') and paletted:
|
|
479
|
+
conf['colors'] = 256
|
|
480
|
+
|
|
481
|
+
opts = ImageOptions(**conf)
|
|
482
|
+
return opts
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def dotted_dict_get(key, d):
|
|
486
|
+
"""
|
|
487
|
+
>>> dotted_dict_get('foo', {'foo': {'bar': 1}})
|
|
488
|
+
{'bar': 1}
|
|
489
|
+
>>> dotted_dict_get('foo.bar', {'foo': {'bar': 1}})
|
|
490
|
+
1
|
|
491
|
+
>>> dotted_dict_get('bar', {'foo': {'bar': 1}})
|
|
492
|
+
"""
|
|
493
|
+
parts = key.split('.')
|
|
494
|
+
try:
|
|
495
|
+
while parts and d:
|
|
496
|
+
d = d[parts.pop(0)]
|
|
497
|
+
except KeyError:
|
|
498
|
+
return None
|
|
499
|
+
if parts: # not completely resolved
|
|
500
|
+
return None
|
|
501
|
+
return d
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class SourcesCollection(dict):
|
|
505
|
+
"""
|
|
506
|
+
Collection of SourceConfigurations.
|
|
507
|
+
Allows access to tagged WMS sources, e.g.
|
|
508
|
+
``sc['source_name:lyr,lyr2']`` will return the source with ``source_name``
|
|
509
|
+
and set ``req.layers`` to ``lyr1,lyr2``.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
def __getitem__(self, key):
|
|
513
|
+
layers = None
|
|
514
|
+
source_name = key
|
|
515
|
+
if ':' in source_name:
|
|
516
|
+
source_name, layers = source_name.split(':', 1)
|
|
517
|
+
source = dict.__getitem__(self, source_name)
|
|
518
|
+
if not layers:
|
|
519
|
+
return source
|
|
520
|
+
|
|
521
|
+
if source.conf.get('type') not in ('wms', 'mapserver', 'mapnik'):
|
|
522
|
+
raise ConfigurationError("found ':' in: '%s'."
|
|
523
|
+
" tagged sources only supported for WMS/Mapserver/Mapnik" % key)
|
|
524
|
+
|
|
525
|
+
uses_req = source.conf.get('type') != 'mapnik'
|
|
526
|
+
|
|
527
|
+
source = copy(source)
|
|
528
|
+
source.conf = deepcopy(source.conf)
|
|
529
|
+
|
|
530
|
+
if uses_req:
|
|
531
|
+
supported_layers = source.conf['req'].get('layers', [])
|
|
532
|
+
else:
|
|
533
|
+
supported_layers = source.conf.get('layers', [])
|
|
534
|
+
supported_layer_set = SourcesCollection.layer_set(supported_layers)
|
|
535
|
+
layer_set = SourcesCollection.layer_set(layers)
|
|
536
|
+
|
|
537
|
+
if supported_layer_set and not layer_set.issubset(supported_layer_set):
|
|
538
|
+
raise ConfigurationError('layers (%s) not supported by source (%s)' % (
|
|
539
|
+
layers, ','.join(supported_layer_set)))
|
|
540
|
+
|
|
541
|
+
if uses_req:
|
|
542
|
+
source.conf['req']['layers'] = layers
|
|
543
|
+
else:
|
|
544
|
+
source.conf['layers'] = layers
|
|
545
|
+
|
|
546
|
+
return source
|
|
547
|
+
|
|
548
|
+
def __contains__(self, key):
|
|
549
|
+
source_name = key
|
|
550
|
+
if ':' in source_name:
|
|
551
|
+
source_name, _ = source_name.split(':', 1)
|
|
552
|
+
return dict.__contains__(self, source_name)
|
|
553
|
+
|
|
554
|
+
@staticmethod
|
|
555
|
+
def layer_set(layers):
|
|
556
|
+
if isinstance(layers, (list, tuple)):
|
|
557
|
+
return set(layers)
|
|
558
|
+
return set(layers.split(','))
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
class SourceConfiguration(ConfigurationBase):
|
|
562
|
+
|
|
563
|
+
supports_meta_tiles = True
|
|
564
|
+
|
|
565
|
+
@classmethod
|
|
566
|
+
def load(cls, conf, context):
|
|
567
|
+
source_type = conf['type']
|
|
568
|
+
|
|
569
|
+
subclass = source_configuration_types.get(source_type)
|
|
570
|
+
if not subclass:
|
|
571
|
+
raise ConfigurationError("unknown source type '%s'" % source_type)
|
|
572
|
+
|
|
573
|
+
return subclass(conf, context)
|
|
574
|
+
|
|
575
|
+
@memoize
|
|
576
|
+
def coverage(self):
|
|
577
|
+
if 'coverage' not in self.conf:
|
|
578
|
+
return None
|
|
579
|
+
from mapproxy.config.coverage import load_coverage
|
|
580
|
+
return load_coverage(self.conf['coverage'])
|
|
581
|
+
|
|
582
|
+
def image_opts(self, format=None):
|
|
583
|
+
if 'transparent' in self.conf:
|
|
584
|
+
self.conf.setdefault('image', {})['transparent'] = self.conf['transparent']
|
|
585
|
+
return self.context.globals.image_options.image_opts(self.conf.get('image', {}), format)
|
|
586
|
+
|
|
587
|
+
def supported_srs(self):
|
|
588
|
+
from mapproxy.srs import SRS, SupportedSRS
|
|
589
|
+
|
|
590
|
+
supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])]
|
|
591
|
+
if not supported_srs:
|
|
592
|
+
return None
|
|
593
|
+
return SupportedSRS(supported_srs, self.context.globals.preferred_srs)
|
|
594
|
+
|
|
595
|
+
def http_client(self, url):
|
|
596
|
+
from mapproxy.client.http import auth_data_from_url, HTTPClient
|
|
597
|
+
|
|
598
|
+
http_client = None
|
|
599
|
+
url, (username, password) = auth_data_from_url(url)
|
|
600
|
+
insecure = ssl_ca_certs = None
|
|
601
|
+
if 'https' in url:
|
|
602
|
+
insecure = self.context.globals.get_value('http.ssl_no_cert_checks', self.conf)
|
|
603
|
+
ssl_ca_certs = self.context.globals.get_path('http.ssl_ca_certs', self.conf)
|
|
604
|
+
|
|
605
|
+
timeout = self.context.globals.get_value('http.client_timeout', self.conf)
|
|
606
|
+
headers = self.context.globals.get_value('http.headers', self.conf)
|
|
607
|
+
hide_error_details = self.context.globals.get_value('http.hide_error_details', self.conf)
|
|
608
|
+
manage_cookies = self.context.globals.get_value('http.manage_cookies', self.conf)
|
|
609
|
+
|
|
610
|
+
http_client = HTTPClient(url, username, password, insecure=insecure,
|
|
611
|
+
ssl_ca_certs=ssl_ca_certs, timeout=timeout,
|
|
612
|
+
headers=headers, hide_error_details=hide_error_details,
|
|
613
|
+
manage_cookies=manage_cookies)
|
|
614
|
+
return http_client, url
|
|
615
|
+
|
|
616
|
+
@memoize
|
|
617
|
+
def on_error_handler(self):
|
|
618
|
+
if 'on_error' not in self.conf:
|
|
619
|
+
return None
|
|
620
|
+
from mapproxy.source.error import HTTPSourceErrorHandler
|
|
621
|
+
|
|
622
|
+
error_handler = HTTPSourceErrorHandler()
|
|
623
|
+
for status_code, response_conf in self.conf['on_error'].items():
|
|
624
|
+
if not isinstance(status_code, int) and status_code != 'other':
|
|
625
|
+
raise ConfigurationError("invalid error code %r in on_error", status_code)
|
|
626
|
+
cacheable = response_conf.get('cache', False)
|
|
627
|
+
color = response_conf.get('response', 'transparent')
|
|
628
|
+
authorize_stale = response_conf.get('authorize_stale', False)
|
|
629
|
+
if color == 'transparent':
|
|
630
|
+
color = (255, 255, 255, 0)
|
|
631
|
+
else:
|
|
632
|
+
color = parse_color(color)
|
|
633
|
+
error_handler.add_handler(status_code, color, cacheable, authorize_stale)
|
|
634
|
+
|
|
635
|
+
return error_handler
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def resolution_range(conf):
|
|
639
|
+
from mapproxy.grid import resolution_range as _resolution_range
|
|
640
|
+
if 'min_res' in conf or 'max_res' in conf:
|
|
641
|
+
return _resolution_range(min_res=conf.get('min_res'),
|
|
642
|
+
max_res=conf.get('max_res'))
|
|
643
|
+
if 'min_scale' in conf or 'max_scale' in conf:
|
|
644
|
+
return _resolution_range(min_scale=conf.get('min_scale'),
|
|
645
|
+
max_scale=conf.get('max_scale'))
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class ArcGISSourceConfiguration(SourceConfiguration):
|
|
649
|
+
source_type = ('arcgis',)
|
|
650
|
+
|
|
651
|
+
def __init__(self, conf, context):
|
|
652
|
+
SourceConfiguration.__init__(self, conf, context)
|
|
653
|
+
|
|
654
|
+
def source(self, params=None):
|
|
655
|
+
from mapproxy.client.arcgis import ArcGISClient
|
|
656
|
+
from mapproxy.source.arcgis import ArcGISSource
|
|
657
|
+
from mapproxy.request.arcgis import create_request
|
|
658
|
+
|
|
659
|
+
if not self.conf.get('opts', {}).get('map', True):
|
|
660
|
+
return None
|
|
661
|
+
|
|
662
|
+
if not self.context.seed and self.conf.get('seed_only'):
|
|
663
|
+
from mapproxy.source import DummySource
|
|
664
|
+
return DummySource(coverage=self.coverage())
|
|
665
|
+
|
|
666
|
+
supported_formats = [file_ext(f) for f in self.conf.get("supported_formats", [])]
|
|
667
|
+
|
|
668
|
+
# Construct the parameters
|
|
669
|
+
if params is None:
|
|
670
|
+
params = {}
|
|
671
|
+
|
|
672
|
+
request_format = self.conf['req'].get('format')
|
|
673
|
+
if request_format:
|
|
674
|
+
params['format'] = request_format
|
|
675
|
+
|
|
676
|
+
request = create_request(self.conf["req"], params)
|
|
677
|
+
http_client, request.url = self.http_client(request.url)
|
|
678
|
+
coverage = self.coverage()
|
|
679
|
+
res_range = resolution_range(self.conf)
|
|
680
|
+
|
|
681
|
+
client = ArcGISClient(request, http_client)
|
|
682
|
+
image_opts = self.image_opts(format=params.get('format'))
|
|
683
|
+
return ArcGISSource(client, image_opts=image_opts, coverage=coverage,
|
|
684
|
+
res_range=res_range,
|
|
685
|
+
supported_srs=self.supported_srs(),
|
|
686
|
+
supported_formats=supported_formats or None,
|
|
687
|
+
error_handler=self.on_error_handler())
|
|
688
|
+
|
|
689
|
+
@memoize
|
|
690
|
+
def fi_source(self, params=None):
|
|
691
|
+
from mapproxy.client.arcgis import ArcGISInfoClient
|
|
692
|
+
from mapproxy.request.arcgis import create_identify_request
|
|
693
|
+
from mapproxy.source.arcgis import ArcGISInfoSource
|
|
694
|
+
|
|
695
|
+
if params is None:
|
|
696
|
+
params = {}
|
|
697
|
+
request_format = self.conf['req'].get('format')
|
|
698
|
+
if request_format:
|
|
699
|
+
params['format'] = request_format
|
|
700
|
+
fi_source = None
|
|
701
|
+
if self.conf.get('opts', {}).get('featureinfo', False):
|
|
702
|
+
opts = self.conf['opts']
|
|
703
|
+
tolerance = opts.get('featureinfo_tolerance', 5)
|
|
704
|
+
return_geometries = opts.get('featureinfo_return_geometries', False)
|
|
705
|
+
|
|
706
|
+
fi_request = create_identify_request(self.conf['req'], params)
|
|
707
|
+
|
|
708
|
+
http_client, fi_request.url = self.http_client(fi_request.url)
|
|
709
|
+
fi_client = ArcGISInfoClient(fi_request,
|
|
710
|
+
supported_srs=self.supported_srs(),
|
|
711
|
+
http_client=http_client,
|
|
712
|
+
tolerance=tolerance,
|
|
713
|
+
return_geometries=return_geometries,
|
|
714
|
+
)
|
|
715
|
+
fi_source = ArcGISInfoSource(fi_client)
|
|
716
|
+
return fi_source
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
class WMSSourceConfiguration(SourceConfiguration):
|
|
720
|
+
source_type = ('wms',)
|
|
721
|
+
|
|
722
|
+
@staticmethod
|
|
723
|
+
def static_legend_source(url, context):
|
|
724
|
+
from mapproxy.cache.legend import LegendCache
|
|
725
|
+
from mapproxy.client.wms import WMSLegendURLClient
|
|
726
|
+
from mapproxy.source.wms import WMSLegendSource
|
|
727
|
+
|
|
728
|
+
cache_dir = os.path.join(context.globals.get_path('cache.base_dir', {}),
|
|
729
|
+
'legends')
|
|
730
|
+
if url.startswith('file://') and not url.startswith('file:///'):
|
|
731
|
+
prefix = 'file://'
|
|
732
|
+
url = prefix + context.globals.abspath(url[7:])
|
|
733
|
+
lg_client = WMSLegendURLClient(url)
|
|
734
|
+
legend_cache = LegendCache(cache_dir=cache_dir)
|
|
735
|
+
return WMSLegendSource([lg_client], legend_cache, static=True)
|
|
736
|
+
|
|
737
|
+
def fi_xslt_transformer(self, conf, context):
|
|
738
|
+
from mapproxy.featureinfo import XSLTransformer, has_xslt_support
|
|
739
|
+
fi_transformer = None
|
|
740
|
+
fi_xslt = conf.get('featureinfo_xslt')
|
|
741
|
+
if fi_xslt:
|
|
742
|
+
if not has_xslt_support:
|
|
743
|
+
raise ValueError('featureinfo_xslt requires lxml. Please install.')
|
|
744
|
+
fi_xslt = context.globals.abspath(fi_xslt)
|
|
745
|
+
fi_format = conf.get('featureinfo_out_format')
|
|
746
|
+
if not fi_format:
|
|
747
|
+
fi_format = conf.get('featureinfo_format')
|
|
748
|
+
fi_transformer = XSLTransformer(fi_xslt, fi_format)
|
|
749
|
+
return fi_transformer
|
|
750
|
+
|
|
751
|
+
def image_opts(self, format=None):
|
|
752
|
+
if 'transparent' not in (self.conf.get('image') or {}):
|
|
753
|
+
transparent = self.conf['req'].get('transparent')
|
|
754
|
+
if transparent is not None:
|
|
755
|
+
transparent = bool(str(transparent).lower() == 'true')
|
|
756
|
+
self.conf.setdefault('image', {})['transparent'] = transparent
|
|
757
|
+
return SourceConfiguration.image_opts(self, format=format)
|
|
758
|
+
|
|
759
|
+
def source(self, params=None):
|
|
760
|
+
from mapproxy.client.wms import WMSClient
|
|
761
|
+
from mapproxy.request.wms import create_request
|
|
762
|
+
from mapproxy.source.wms import WMSSource
|
|
763
|
+
|
|
764
|
+
if not self.conf.get('wms_opts', {}).get('map', True):
|
|
765
|
+
return None
|
|
766
|
+
|
|
767
|
+
if not self.context.seed and self.conf.get('seed_only'):
|
|
768
|
+
from mapproxy.source import DummySource
|
|
769
|
+
return DummySource(coverage=self.coverage())
|
|
770
|
+
|
|
771
|
+
if params is None:
|
|
772
|
+
params = {}
|
|
773
|
+
|
|
774
|
+
request_format = self.conf['req'].get('format')
|
|
775
|
+
if request_format:
|
|
776
|
+
params['format'] = request_format
|
|
777
|
+
|
|
778
|
+
image_opts = self.image_opts(format=params.get('format'))
|
|
779
|
+
|
|
780
|
+
supported_formats = [file_ext(f) for f in self.conf.get('supported_formats', [])]
|
|
781
|
+
version = self.conf.get('wms_opts', {}).get('version', '1.1.1')
|
|
782
|
+
|
|
783
|
+
lock = None
|
|
784
|
+
concurrent_requests = self.context.globals.get_value('concurrent_requests', self.conf,
|
|
785
|
+
global_key='http.concurrent_requests')
|
|
786
|
+
if concurrent_requests:
|
|
787
|
+
from mapproxy.util.lock import SemLock
|
|
788
|
+
lock_dir = self.context.globals.get_path('cache.lock_dir', self.conf)
|
|
789
|
+
lock_timeout = self.context.globals.get_value('http.client_timeout', self.conf)
|
|
790
|
+
url = urlparse(self.conf['req']['url'])
|
|
791
|
+
md5 = hashlib.new('md5', url.netloc.encode('ascii'), usedforsecurity=False)
|
|
792
|
+
lock_file = os.path.join(lock_dir, md5.hexdigest() + '.lck')
|
|
793
|
+
lock = lambda: SemLock(lock_file, concurrent_requests, timeout=lock_timeout) # noqa
|
|
794
|
+
|
|
795
|
+
coverage = self.coverage()
|
|
796
|
+
res_range = resolution_range(self.conf)
|
|
797
|
+
|
|
798
|
+
transparent_color = (self.conf.get('image') or {}).get('transparent_color')
|
|
799
|
+
transparent_color_tolerance = self.context.globals.get_value(
|
|
800
|
+
'image.transparent_color_tolerance', self.conf)
|
|
801
|
+
if transparent_color:
|
|
802
|
+
transparent_color = parse_color(transparent_color)
|
|
803
|
+
|
|
804
|
+
http_method = self.context.globals.get_value('http.method', self.conf)
|
|
805
|
+
|
|
806
|
+
fwd_req_params = set(self.conf.get('forward_req_params', []))
|
|
807
|
+
|
|
808
|
+
request = create_request(self.conf['req'], params, version=version,
|
|
809
|
+
abspath=self.context.globals.abspath)
|
|
810
|
+
http_client, request.url = self.http_client(request.url)
|
|
811
|
+
client = WMSClient(request, http_client=http_client,
|
|
812
|
+
http_method=http_method, lock=lock,
|
|
813
|
+
fwd_req_params=fwd_req_params)
|
|
814
|
+
return WMSSource(client, image_opts=image_opts, coverage=coverage,
|
|
815
|
+
res_range=res_range, transparent_color=transparent_color,
|
|
816
|
+
transparent_color_tolerance=transparent_color_tolerance,
|
|
817
|
+
supported_srs=self.supported_srs(),
|
|
818
|
+
supported_formats=supported_formats or None,
|
|
819
|
+
fwd_req_params=fwd_req_params,
|
|
820
|
+
error_handler=self.on_error_handler())
|
|
821
|
+
|
|
822
|
+
def fi_source(self, params=None):
|
|
823
|
+
from mapproxy.client.wms import WMSInfoClient
|
|
824
|
+
from mapproxy.request.wms import create_request
|
|
825
|
+
from mapproxy.source.wms import WMSInfoSource
|
|
826
|
+
|
|
827
|
+
if params is None:
|
|
828
|
+
params = {}
|
|
829
|
+
request_format = self.conf['req'].get('format')
|
|
830
|
+
if request_format:
|
|
831
|
+
params['format'] = request_format
|
|
832
|
+
fi_source = None
|
|
833
|
+
if self.conf.get('wms_opts', {}).get('featureinfo', False):
|
|
834
|
+
wms_opts = self.conf['wms_opts']
|
|
835
|
+
version = wms_opts.get('version', '1.1.1')
|
|
836
|
+
if 'featureinfo_format' in wms_opts:
|
|
837
|
+
params['info_format'] = wms_opts['featureinfo_format']
|
|
838
|
+
fi_request = create_request(self.conf['req'], params,
|
|
839
|
+
req_type='featureinfo', version=version,
|
|
840
|
+
abspath=self.context.globals.abspath)
|
|
841
|
+
|
|
842
|
+
fi_transformer = self.fi_xslt_transformer(self.conf.get('wms_opts', {}),
|
|
843
|
+
self.context)
|
|
844
|
+
|
|
845
|
+
http_client, fi_request.url = self.http_client(fi_request.url)
|
|
846
|
+
fi_client = WMSInfoClient(fi_request, supported_srs=self.supported_srs(),
|
|
847
|
+
http_client=http_client)
|
|
848
|
+
coverage = self.coverage()
|
|
849
|
+
fi_source = WMSInfoSource(fi_client, fi_transformer=fi_transformer,
|
|
850
|
+
coverage=coverage)
|
|
851
|
+
return fi_source
|
|
852
|
+
|
|
853
|
+
def lg_source(self, params=None):
|
|
854
|
+
from mapproxy.cache.legend import LegendCache
|
|
855
|
+
from mapproxy.client.wms import WMSLegendClient
|
|
856
|
+
from mapproxy.request.wms import create_request
|
|
857
|
+
from mapproxy.source.wms import WMSLegendSource
|
|
858
|
+
|
|
859
|
+
if params is None:
|
|
860
|
+
params = {}
|
|
861
|
+
request_format = self.conf['req'].get('format')
|
|
862
|
+
if request_format:
|
|
863
|
+
params['format'] = request_format
|
|
864
|
+
lg_source = None
|
|
865
|
+
cache_dir = os.path.join(self.context.globals.get_path('cache.base_dir', {}),
|
|
866
|
+
'legends')
|
|
867
|
+
|
|
868
|
+
if self.conf.get('wms_opts', {}).get('legendurl', False):
|
|
869
|
+
lg_url = self.conf.get('wms_opts', {}).get('legendurl')
|
|
870
|
+
lg_source = WMSSourceConfiguration.static_legend_source(lg_url, self.context)
|
|
871
|
+
elif self.conf.get('wms_opts', {}).get('legendgraphic', False):
|
|
872
|
+
version = self.conf.get('wms_opts', {}).get('version', '1.1.1')
|
|
873
|
+
lg_req = self.conf['req'].copy()
|
|
874
|
+
lg_clients = []
|
|
875
|
+
lg_layers = str(lg_req['layers']).split(',')
|
|
876
|
+
del lg_req['layers']
|
|
877
|
+
for lg_layer in lg_layers:
|
|
878
|
+
lg_req['layer'] = lg_layer
|
|
879
|
+
lg_request = create_request(lg_req, params,
|
|
880
|
+
req_type='legendgraphic', version=version,
|
|
881
|
+
abspath=self.context.globals.abspath)
|
|
882
|
+
http_client, lg_request.url = self.http_client(lg_request.url)
|
|
883
|
+
lg_client = WMSLegendClient(lg_request, http_client=http_client)
|
|
884
|
+
lg_clients.append(lg_client)
|
|
885
|
+
legend_cache = LegendCache(cache_dir=cache_dir)
|
|
886
|
+
lg_source = WMSLegendSource(lg_clients, legend_cache)
|
|
887
|
+
return lg_source
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
class MapServerSourceConfiguration(WMSSourceConfiguration):
|
|
891
|
+
source_type = ('mapserver',)
|
|
892
|
+
|
|
893
|
+
def __init__(self, conf, context):
|
|
894
|
+
WMSSourceConfiguration.__init__(self, conf, context)
|
|
895
|
+
self.script = self.context.globals.get_path('mapserver.binary',
|
|
896
|
+
self.conf)
|
|
897
|
+
if not self.script:
|
|
898
|
+
self.script = find_exec('mapserv')
|
|
899
|
+
|
|
900
|
+
if not self.script or not os.path.isfile(self.script):
|
|
901
|
+
raise ConfigurationError('could not find mapserver binary (%r)' %
|
|
902
|
+
(self.script, ))
|
|
903
|
+
|
|
904
|
+
# set url to dummy script name, required as identifier
|
|
905
|
+
# for concurrent_request
|
|
906
|
+
self.conf['req']['url'] = 'mapserver://' + self.script
|
|
907
|
+
|
|
908
|
+
mapfile = self.context.globals.abspath(self.conf['req']['map'])
|
|
909
|
+
self.conf['req']['map'] = mapfile
|
|
910
|
+
|
|
911
|
+
def http_client(self, url):
|
|
912
|
+
working_dir = self.context.globals.get_path('mapserver.working_dir', self.conf)
|
|
913
|
+
if working_dir and not os.path.isdir(working_dir):
|
|
914
|
+
raise ConfigurationError('could not find mapserver working_dir (%r)' % (working_dir, ))
|
|
915
|
+
|
|
916
|
+
from mapproxy.client.cgi import CGIClient
|
|
917
|
+
client = CGIClient(script=self.script, working_directory=working_dir)
|
|
918
|
+
return client, url
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
class MapnikSourceConfiguration(SourceConfiguration):
|
|
922
|
+
source_type = ('mapnik',)
|
|
923
|
+
|
|
924
|
+
def source(self, params=None):
|
|
925
|
+
if not self.context.seed and self.conf.get('seed_only'):
|
|
926
|
+
from mapproxy.source import DummySource
|
|
927
|
+
return DummySource(coverage=self.coverage())
|
|
928
|
+
|
|
929
|
+
image_opts = self.image_opts()
|
|
930
|
+
|
|
931
|
+
lock = None
|
|
932
|
+
concurrent_requests = self.context.globals.get_value('concurrent_requests', self.conf,
|
|
933
|
+
global_key='http.concurrent_requests')
|
|
934
|
+
if concurrent_requests:
|
|
935
|
+
from mapproxy.util.lock import SemLock
|
|
936
|
+
lock_dir = self.context.globals.get_path('cache.lock_dir', self.conf)
|
|
937
|
+
mapfile = self.conf['mapfile']
|
|
938
|
+
md5 = hashlib.new('md5', mapfile.encode('utf-8'), usedforsecurity=False)
|
|
939
|
+
lock_file = os.path.join(lock_dir, md5.hexdigest() + '.lck')
|
|
940
|
+
lock = lambda: SemLock(lock_file, concurrent_requests) # noqa
|
|
941
|
+
|
|
942
|
+
coverage = self.coverage()
|
|
943
|
+
res_range = resolution_range(self.conf)
|
|
944
|
+
|
|
945
|
+
scale_factor = self.conf.get('scale_factor', None)
|
|
946
|
+
multithreaded = self.conf.get('multithreaded', False)
|
|
947
|
+
|
|
948
|
+
layers = self.conf.get('layers', None)
|
|
949
|
+
if isinstance(layers, str):
|
|
950
|
+
layers = layers.split(',')
|
|
951
|
+
|
|
952
|
+
mapfile = self.context.globals.abspath(self.conf['mapfile'])
|
|
953
|
+
|
|
954
|
+
if self.conf.get('use_mapnik2', False):
|
|
955
|
+
warnings.warn('use_mapnik2 option is no longer needed for Mapnik 2 support',
|
|
956
|
+
DeprecationWarning)
|
|
957
|
+
|
|
958
|
+
from mapproxy.source.mapnik import MapnikSource, mapnik as mapnik_api
|
|
959
|
+
if mapnik_api is None:
|
|
960
|
+
raise ConfigurationError('Could not import Mapnik, please verify it is installed!')
|
|
961
|
+
|
|
962
|
+
if self.context.renderd:
|
|
963
|
+
# only renderd guarantees that we have a single proc/thread
|
|
964
|
+
# that accesses the same mapnik map object
|
|
965
|
+
reuse_map_objects = True
|
|
966
|
+
else:
|
|
967
|
+
reuse_map_objects = False
|
|
968
|
+
|
|
969
|
+
return MapnikSource(mapfile, layers=layers, image_opts=image_opts,
|
|
970
|
+
coverage=coverage, res_range=res_range, lock=lock,
|
|
971
|
+
reuse_map_objects=reuse_map_objects, scale_factor=scale_factor,
|
|
972
|
+
multithreaded=multithreaded)
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
class TileSourceConfiguration(SourceConfiguration):
|
|
976
|
+
supports_meta_tiles = False
|
|
977
|
+
source_type = ('tile',)
|
|
978
|
+
defaults = {}
|
|
979
|
+
|
|
980
|
+
def source(self, params=None):
|
|
981
|
+
from mapproxy.client.tile import TileClient, TileURLTemplate
|
|
982
|
+
from mapproxy.source.tile import TiledSource
|
|
983
|
+
|
|
984
|
+
if not self.context.seed and self.conf.get('seed_only'):
|
|
985
|
+
from mapproxy.source import DummySource
|
|
986
|
+
return DummySource(coverage=self.coverage())
|
|
987
|
+
|
|
988
|
+
if params is None:
|
|
989
|
+
params = {}
|
|
990
|
+
|
|
991
|
+
url = self.conf['url']
|
|
992
|
+
|
|
993
|
+
if self.conf.get('origin'):
|
|
994
|
+
warnings.warn('origin for tile sources is deprecated since 1.3.0 '
|
|
995
|
+
'and will be ignored. use grid with correct origin.', RuntimeWarning)
|
|
996
|
+
|
|
997
|
+
http_client, url = self.http_client(url)
|
|
998
|
+
|
|
999
|
+
grid_name = self.conf.get('grid')
|
|
1000
|
+
if grid_name is None:
|
|
1001
|
+
log.warning(
|
|
1002
|
+
"tile source for %s does not have a grid configured and defaults to GLOBAL_MERCATOR. default will"
|
|
1003
|
+
" change with MapProxy 2.0", url)
|
|
1004
|
+
grid_name = "GLOBAL_MERCATOR"
|
|
1005
|
+
|
|
1006
|
+
grid = self.context.grids[grid_name].tile_grid()
|
|
1007
|
+
coverage = self.coverage()
|
|
1008
|
+
res_range = resolution_range(self.conf)
|
|
1009
|
+
|
|
1010
|
+
image_opts = self.image_opts()
|
|
1011
|
+
error_handler = self.on_error_handler()
|
|
1012
|
+
|
|
1013
|
+
format = file_ext(params['format'])
|
|
1014
|
+
client = TileClient(TileURLTemplate(url, format=format), http_client=http_client, grid=grid)
|
|
1015
|
+
return TiledSource(grid, client, coverage=coverage, image_opts=image_opts,
|
|
1016
|
+
error_handler=error_handler, res_range=res_range)
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def file_ext(mimetype):
|
|
1020
|
+
from mapproxy.request.base import split_mime_type
|
|
1021
|
+
_mime_class, format, _options = split_mime_type(mimetype)
|
|
1022
|
+
return format
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
class DebugSourceConfiguration(SourceConfiguration):
|
|
1026
|
+
source_type = ('debug',)
|
|
1027
|
+
required_keys = set('type'.split())
|
|
1028
|
+
|
|
1029
|
+
def source(self, params=None):
|
|
1030
|
+
from mapproxy.source import DebugSource
|
|
1031
|
+
return DebugSource()
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
source_configuration_types = {
|
|
1035
|
+
'wms': WMSSourceConfiguration,
|
|
1036
|
+
'arcgis': ArcGISSourceConfiguration,
|
|
1037
|
+
'tile': TileSourceConfiguration,
|
|
1038
|
+
'debug': DebugSourceConfiguration,
|
|
1039
|
+
'mapserver': MapServerSourceConfiguration,
|
|
1040
|
+
'mapnik': MapnikSourceConfiguration,
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def register_source_configuration(config_name, config_class,
|
|
1045
|
+
yaml_spec_source_name=None, yaml_spec_source_def=None):
|
|
1046
|
+
""" Method used by plugins to register a new source configuration.
|
|
1047
|
+
|
|
1048
|
+
:param config_name: Name of the source configuration
|
|
1049
|
+
:type config_name: str
|
|
1050
|
+
:param config_class: Class of the source configuration
|
|
1051
|
+
:type config_name: SourceConfiguration
|
|
1052
|
+
:param yaml_spec_source_name: Name of the source in the YAML configuration file
|
|
1053
|
+
:type yaml_spec_source_name: str
|
|
1054
|
+
:param yaml_spec_source_def: Definition of the source in the YAML configuration file
|
|
1055
|
+
:type yaml_spec_source_def: dict
|
|
1056
|
+
|
|
1057
|
+
Example:
|
|
1058
|
+
register_source_configuration('hips', HIPSSourceConfiguration,
|
|
1059
|
+
'hips', { required('url'): str(),
|
|
1060
|
+
'resampling_method': str(),
|
|
1061
|
+
'image': image_opts,
|
|
1062
|
+
})
|
|
1063
|
+
"""
|
|
1064
|
+
log.info('Registering configuration for plugin source %s' % config_name)
|
|
1065
|
+
source_configuration_types[config_name] = config_class
|
|
1066
|
+
if yaml_spec_source_name is not None and yaml_spec_source_def is not None:
|
|
1067
|
+
add_source_to_mapproxy_yaml_spec(yaml_spec_source_name, yaml_spec_source_def)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
class CacheConfiguration(ConfigurationBase):
|
|
1071
|
+
defaults = {'format': 'image/png'}
|
|
1072
|
+
|
|
1073
|
+
@memoize
|
|
1074
|
+
def coverage(self):
|
|
1075
|
+
if 'cache' not in self.conf or 'coverage' not in self.conf['cache']:
|
|
1076
|
+
return None
|
|
1077
|
+
from mapproxy.config.coverage import load_coverage
|
|
1078
|
+
return load_coverage(self.conf['cache']['coverage'])
|
|
1079
|
+
|
|
1080
|
+
@memoize
|
|
1081
|
+
def cache_dir(self):
|
|
1082
|
+
cache_dir = self.conf.get('cache', {}).get('directory')
|
|
1083
|
+
if cache_dir:
|
|
1084
|
+
if self.conf.get('cache_dir'):
|
|
1085
|
+
log.warning('found cache.directory and cache_dir option for %s, ignoring cache_dir',
|
|
1086
|
+
self.conf['name'])
|
|
1087
|
+
return self.context.globals.abspath(cache_dir)
|
|
1088
|
+
|
|
1089
|
+
return self.context.globals.get_path('cache_dir', self.conf,
|
|
1090
|
+
global_key='cache.base_dir')
|
|
1091
|
+
|
|
1092
|
+
@memoize
|
|
1093
|
+
def has_multiple_grids(self):
|
|
1094
|
+
return len(self.grid_confs()) > 1
|
|
1095
|
+
|
|
1096
|
+
def lock_dir(self):
|
|
1097
|
+
lock_dir = self.context.globals.get_path('cache.tile_lock_dir', self.conf)
|
|
1098
|
+
if not lock_dir:
|
|
1099
|
+
lock_dir = os.path.join(self.cache_dir(), 'tile_locks')
|
|
1100
|
+
return lock_dir
|
|
1101
|
+
|
|
1102
|
+
def _file_cache(self, grid_conf, image_opts):
|
|
1103
|
+
from mapproxy.cache.file import FileCache
|
|
1104
|
+
|
|
1105
|
+
cache_dir = self.cache_dir()
|
|
1106
|
+
directory_layout = self.conf.get('cache', {}).get('directory_layout', 'tc')
|
|
1107
|
+
coverage = self.coverage()
|
|
1108
|
+
|
|
1109
|
+
if self.conf.get('cache', {}).get('directory'):
|
|
1110
|
+
if self.has_multiple_grids():
|
|
1111
|
+
raise ConfigurationError(
|
|
1112
|
+
"using single directory for cache with multiple grids in %s" %
|
|
1113
|
+
(self.conf['name']),
|
|
1114
|
+
)
|
|
1115
|
+
pass
|
|
1116
|
+
elif self.conf.get('cache', {}).get('use_grid_names'):
|
|
1117
|
+
cache_dir = os.path.join(cache_dir, self.conf['name'], grid_conf.tile_grid().name)
|
|
1118
|
+
else:
|
|
1119
|
+
suffix = grid_conf.conf['srs'].replace(':', '')
|
|
1120
|
+
cache_dir = os.path.join(cache_dir, self.conf['name'] + '_' + suffix)
|
|
1121
|
+
link_single_color_images = self.context.globals.get_value('link_single_color_images', self.conf,
|
|
1122
|
+
global_key='cache.link_single_color_images')
|
|
1123
|
+
|
|
1124
|
+
if link_single_color_images and sys.platform == 'win32':
|
|
1125
|
+
log.warning('link_single_color_images not supported on windows')
|
|
1126
|
+
link_single_color_images = False
|
|
1127
|
+
|
|
1128
|
+
return FileCache(
|
|
1129
|
+
cache_dir,
|
|
1130
|
+
file_ext=image_opts.format.ext,
|
|
1131
|
+
image_opts=image_opts,
|
|
1132
|
+
directory_layout=directory_layout,
|
|
1133
|
+
link_single_color_images=link_single_color_images,
|
|
1134
|
+
coverage=coverage
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
def _mbtiles_cache(self, grid_conf, image_opts):
|
|
1138
|
+
from mapproxy.cache.mbtiles import MBTilesCache
|
|
1139
|
+
|
|
1140
|
+
filename = self.conf['cache'].get('filename')
|
|
1141
|
+
if not filename:
|
|
1142
|
+
filename = self.conf['name'] + '.mbtiles'
|
|
1143
|
+
|
|
1144
|
+
if filename.startswith('.' + os.sep):
|
|
1145
|
+
mbfile_path = self.context.globals.abspath(filename)
|
|
1146
|
+
else:
|
|
1147
|
+
mbfile_path = os.path.join(self.cache_dir(), filename)
|
|
1148
|
+
|
|
1149
|
+
sqlite_timeout = self.context.globals.get_value('cache.sqlite_timeout', self.conf)
|
|
1150
|
+
wal = self.context.globals.get_value('cache.sqlite_wal', self.conf)
|
|
1151
|
+
coverage = self.coverage()
|
|
1152
|
+
|
|
1153
|
+
return MBTilesCache(
|
|
1154
|
+
mbfile_path,
|
|
1155
|
+
timeout=sqlite_timeout,
|
|
1156
|
+
wal=wal,
|
|
1157
|
+
coverage=coverage
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
def _geopackage_cache(self, grid_conf, image_opts):
|
|
1161
|
+
from mapproxy.cache.geopackage import GeopackageCache, GeopackageLevelCache
|
|
1162
|
+
|
|
1163
|
+
filename = self.conf['cache'].get('filename')
|
|
1164
|
+
table_name = self.conf['cache'].get('table_name') or \
|
|
1165
|
+
"{}_{}".format(self.conf['name'], grid_conf.tile_grid().name)
|
|
1166
|
+
levels = self.conf['cache'].get('levels')
|
|
1167
|
+
coverage = self.coverage()
|
|
1168
|
+
|
|
1169
|
+
if not filename:
|
|
1170
|
+
filename = self.conf['name'] + '.gpkg'
|
|
1171
|
+
if filename.startswith('.' + os.sep):
|
|
1172
|
+
gpkg_file_path = self.context.globals.abspath(filename)
|
|
1173
|
+
else:
|
|
1174
|
+
gpkg_file_path = os.path.join(self.cache_dir(), filename)
|
|
1175
|
+
|
|
1176
|
+
cache_dir = self.conf['cache'].get('directory')
|
|
1177
|
+
if cache_dir:
|
|
1178
|
+
cache_dir = os.path.join(
|
|
1179
|
+
self.context.globals.abspath(cache_dir),
|
|
1180
|
+
grid_conf.tile_grid().name
|
|
1181
|
+
)
|
|
1182
|
+
else:
|
|
1183
|
+
cache_dir = self.cache_dir()
|
|
1184
|
+
cache_dir = os.path.join(
|
|
1185
|
+
cache_dir,
|
|
1186
|
+
self.conf['name'],
|
|
1187
|
+
grid_conf.tile_grid().name
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
if levels:
|
|
1191
|
+
return GeopackageLevelCache(
|
|
1192
|
+
cache_dir, grid_conf.tile_grid(), table_name, coverage=coverage
|
|
1193
|
+
)
|
|
1194
|
+
else:
|
|
1195
|
+
return GeopackageCache(
|
|
1196
|
+
gpkg_file_path, grid_conf.tile_grid(), table_name, coverage=coverage
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
def _azureblob_cache(self, grid_conf, image_opts):
|
|
1200
|
+
from mapproxy.cache.azureblob import AzureBlobCache
|
|
1201
|
+
|
|
1202
|
+
container_name = self.context.globals.get_value('cache.container_name', self.conf,
|
|
1203
|
+
global_key='cache.azureblob.container_name')
|
|
1204
|
+
coverage = self.coverage()
|
|
1205
|
+
|
|
1206
|
+
if not container_name:
|
|
1207
|
+
raise ConfigurationError("no container_name configured for Azure Blob cache %s" % self.conf['name'])
|
|
1208
|
+
|
|
1209
|
+
connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING", self.context.globals.get_value(
|
|
1210
|
+
'cache.connection_string', self.conf, global_key='cache.azureblob.connection_string'))
|
|
1211
|
+
|
|
1212
|
+
if not connection_string:
|
|
1213
|
+
raise ConfigurationError("no connection_string configured for Azure Blob cache %s" % self.conf['name'])
|
|
1214
|
+
|
|
1215
|
+
directory_layout = self.conf['cache'].get('directory_layout', 'tms')
|
|
1216
|
+
|
|
1217
|
+
base_path = self.conf['cache'].get('directory', None)
|
|
1218
|
+
if base_path is None:
|
|
1219
|
+
base_path = os.path.join(self.conf['name'], grid_conf.tile_grid().name)
|
|
1220
|
+
|
|
1221
|
+
return AzureBlobCache(
|
|
1222
|
+
base_path=base_path,
|
|
1223
|
+
file_ext=image_opts.format.ext,
|
|
1224
|
+
directory_layout=directory_layout,
|
|
1225
|
+
container_name=container_name,
|
|
1226
|
+
connection_string=connection_string,
|
|
1227
|
+
coverage=coverage
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1230
|
+
def _s3_cache(self, grid_conf, image_opts):
|
|
1231
|
+
from mapproxy.cache.s3 import S3Cache
|
|
1232
|
+
|
|
1233
|
+
bucket_name = self.context.globals.get_value('cache.bucket_name', self.conf,
|
|
1234
|
+
global_key='cache.s3.bucket_name')
|
|
1235
|
+
coverage = self.coverage()
|
|
1236
|
+
|
|
1237
|
+
if not bucket_name:
|
|
1238
|
+
raise ConfigurationError("no bucket_name configured for s3 cache %s" % self.conf['name'])
|
|
1239
|
+
|
|
1240
|
+
profile_name = self.context.globals.get_value('cache.profile_name', self.conf,
|
|
1241
|
+
global_key='cache.s3.profile_name')
|
|
1242
|
+
|
|
1243
|
+
region_name = self.context.globals.get_value('cache.region_name', self.conf,
|
|
1244
|
+
global_key='cache.s3.region_name')
|
|
1245
|
+
|
|
1246
|
+
endpoint_url = self.context.globals.get_value('cache.endpoint_url', self.conf,
|
|
1247
|
+
global_key='cache.s3.endpoint_url')
|
|
1248
|
+
|
|
1249
|
+
access_control_list = self.context.globals.get_value('cache.access_control_list', self.conf,
|
|
1250
|
+
global_key='cache.s3.access_control_list')
|
|
1251
|
+
|
|
1252
|
+
use_http_get = self.context.globals.get_value('cache.use_http_get', self.conf,
|
|
1253
|
+
global_key='cache.s3.use_http_get'
|
|
1254
|
+
)
|
|
1255
|
+
|
|
1256
|
+
directory_layout = self.conf['cache'].get('directory_layout', 'tms')
|
|
1257
|
+
|
|
1258
|
+
base_path = self.conf['cache'].get('directory', None)
|
|
1259
|
+
if base_path is None:
|
|
1260
|
+
base_path = os.path.join(self.conf['name'], grid_conf.tile_grid().name)
|
|
1261
|
+
|
|
1262
|
+
return S3Cache(
|
|
1263
|
+
base_path=base_path,
|
|
1264
|
+
file_ext=image_opts.format.ext,
|
|
1265
|
+
directory_layout=directory_layout,
|
|
1266
|
+
bucket_name=bucket_name,
|
|
1267
|
+
profile_name=profile_name,
|
|
1268
|
+
region_name=region_name,
|
|
1269
|
+
endpoint_url=endpoint_url,
|
|
1270
|
+
access_control_list=access_control_list,
|
|
1271
|
+
coverage=coverage,
|
|
1272
|
+
use_http_get=use_http_get
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
def _sqlite_cache(self, grid_conf, image_opts):
|
|
1276
|
+
from mapproxy.cache.mbtiles import MBTilesLevelCache
|
|
1277
|
+
|
|
1278
|
+
cache_dir = self.conf.get('cache', {}).get('directory')
|
|
1279
|
+
if cache_dir:
|
|
1280
|
+
cache_dir = os.path.join(
|
|
1281
|
+
self.context.globals.abspath(cache_dir),
|
|
1282
|
+
grid_conf.tile_grid().name
|
|
1283
|
+
)
|
|
1284
|
+
else:
|
|
1285
|
+
cache_dir = self.cache_dir()
|
|
1286
|
+
cache_dir = os.path.join(
|
|
1287
|
+
cache_dir,
|
|
1288
|
+
self.conf['name'],
|
|
1289
|
+
grid_conf.tile_grid().name
|
|
1290
|
+
)
|
|
1291
|
+
|
|
1292
|
+
sqlite_timeout = self.context.globals.get_value('cache.sqlite_timeout', self.conf)
|
|
1293
|
+
wal = self.context.globals.get_value('cache.sqlite_wal', self.conf)
|
|
1294
|
+
coverage = self.coverage()
|
|
1295
|
+
|
|
1296
|
+
return MBTilesLevelCache(
|
|
1297
|
+
cache_dir,
|
|
1298
|
+
timeout=sqlite_timeout,
|
|
1299
|
+
wal=wal,
|
|
1300
|
+
ttl=self.conf.get('cache', {}).get('ttl', 0),
|
|
1301
|
+
coverage=coverage
|
|
1302
|
+
)
|
|
1303
|
+
|
|
1304
|
+
def _couchdb_cache(self, grid_conf, image_opts):
|
|
1305
|
+
from mapproxy.cache.couchdb import CouchDBCache, CouchDBMDTemplate
|
|
1306
|
+
|
|
1307
|
+
db_name = self.conf['cache'].get('db_name')
|
|
1308
|
+
if not db_name:
|
|
1309
|
+
suffix = grid_conf.conf['srs'].replace(':', '')
|
|
1310
|
+
db_name = self.conf['name'] + '_' + suffix
|
|
1311
|
+
|
|
1312
|
+
url = self.conf['cache'].get('url')
|
|
1313
|
+
if not url:
|
|
1314
|
+
url = 'http://127.0.0.1:5984'
|
|
1315
|
+
|
|
1316
|
+
md_template = CouchDBMDTemplate(self.conf['cache'].get('tile_metadata', {}))
|
|
1317
|
+
tile_id = self.conf['cache'].get('tile_id')
|
|
1318
|
+
coverage = self.coverage()
|
|
1319
|
+
|
|
1320
|
+
return CouchDBCache(url=url, db_name=db_name,
|
|
1321
|
+
file_ext=image_opts.format.ext, tile_grid=grid_conf.tile_grid(),
|
|
1322
|
+
md_template=md_template, tile_id_template=tile_id, coverage=coverage)
|
|
1323
|
+
|
|
1324
|
+
def _riak_cache(self, grid_conf, image_opts):
|
|
1325
|
+
from mapproxy.cache.riak import RiakCache
|
|
1326
|
+
|
|
1327
|
+
default_ports = self.conf['cache'].get('default_ports', {})
|
|
1328
|
+
default_pb_port = default_ports.get('pb', 8087)
|
|
1329
|
+
default_http_port = default_ports.get('http', 8098)
|
|
1330
|
+
coverage = self.coverage()
|
|
1331
|
+
|
|
1332
|
+
nodes = self.conf['cache'].get('nodes')
|
|
1333
|
+
if not nodes:
|
|
1334
|
+
nodes = [{'host': '127.0.0.1'}]
|
|
1335
|
+
|
|
1336
|
+
for n in nodes:
|
|
1337
|
+
if 'pb_port' not in n:
|
|
1338
|
+
n['pb_port'] = default_pb_port
|
|
1339
|
+
if 'http_port' not in n:
|
|
1340
|
+
n['http_port'] = default_http_port
|
|
1341
|
+
|
|
1342
|
+
protocol = self.conf['cache'].get('protocol', 'pbc')
|
|
1343
|
+
bucket = self.conf['cache'].get('bucket')
|
|
1344
|
+
if not bucket:
|
|
1345
|
+
suffix = grid_conf.tile_grid().name
|
|
1346
|
+
bucket = self.conf['name'] + '_' + suffix
|
|
1347
|
+
|
|
1348
|
+
use_secondary_index = self.conf['cache'].get('secondary_index', False)
|
|
1349
|
+
timeout = self.context.globals.get_value('http.client_timeout', self.conf)
|
|
1350
|
+
|
|
1351
|
+
return RiakCache(nodes=nodes, protocol=protocol, bucket=bucket,
|
|
1352
|
+
tile_grid=grid_conf.tile_grid(),
|
|
1353
|
+
use_secondary_index=use_secondary_index,
|
|
1354
|
+
timeout=timeout,
|
|
1355
|
+
coverage=coverage
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
def _redis_cache(self, grid_conf, image_opts):
|
|
1359
|
+
from mapproxy.cache.redis import RedisCache
|
|
1360
|
+
|
|
1361
|
+
host = self.conf['cache'].get('host', '127.0.0.1')
|
|
1362
|
+
port = self.conf['cache'].get('port', 6379)
|
|
1363
|
+
db = self.conf['cache'].get('db', 0)
|
|
1364
|
+
ttl = self.conf['cache'].get('default_ttl', 3600)
|
|
1365
|
+
username = self.conf['cache'].get('username', None)
|
|
1366
|
+
password = self.conf['cache'].get('password', None)
|
|
1367
|
+
coverage = self.coverage()
|
|
1368
|
+
ssl_certfile = self.conf['cache'].get('ssl_certfile', None)
|
|
1369
|
+
ssl_keyfile = self.conf['cache'].get('ssl_keyfile', None)
|
|
1370
|
+
ssl_ca_certs = self.conf['cache'].get('ssl_ca_certs', None)
|
|
1371
|
+
prefix = self.conf['cache'].get('prefix')
|
|
1372
|
+
if not prefix:
|
|
1373
|
+
prefix = self.conf['name'] + '_' + grid_conf.tile_grid().name
|
|
1374
|
+
|
|
1375
|
+
return RedisCache(
|
|
1376
|
+
host=host,
|
|
1377
|
+
port=port,
|
|
1378
|
+
db=db,
|
|
1379
|
+
username=username,
|
|
1380
|
+
password=password,
|
|
1381
|
+
prefix=prefix,
|
|
1382
|
+
ttl=ttl,
|
|
1383
|
+
coverage=coverage,
|
|
1384
|
+
ssl_certfile=ssl_certfile,
|
|
1385
|
+
ssl_keyfile=ssl_keyfile,
|
|
1386
|
+
ssl_ca_certs=ssl_ca_certs
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
def _compact_cache(self, grid_conf, image_opts):
|
|
1390
|
+
from mapproxy.cache.compact import CompactCacheV1, CompactCacheV2
|
|
1391
|
+
|
|
1392
|
+
coverage = self.coverage()
|
|
1393
|
+
cache_dir = self.cache_dir()
|
|
1394
|
+
if self.conf.get('cache', {}).get('directory'):
|
|
1395
|
+
if self.has_multiple_grids():
|
|
1396
|
+
raise ConfigurationError(
|
|
1397
|
+
"using single directory for cache with multiple grids in %s" %
|
|
1398
|
+
(self.conf['name']),
|
|
1399
|
+
)
|
|
1400
|
+
pass
|
|
1401
|
+
else:
|
|
1402
|
+
cache_dir = os.path.join(cache_dir, self.conf['name'], grid_conf.tile_grid().name)
|
|
1403
|
+
|
|
1404
|
+
version = self.conf['cache']['version']
|
|
1405
|
+
if version == 1:
|
|
1406
|
+
return CompactCacheV1(cache_dir=cache_dir, coverage=coverage)
|
|
1407
|
+
elif version == 2:
|
|
1408
|
+
return CompactCacheV2(cache_dir=cache_dir, coverage=coverage)
|
|
1409
|
+
|
|
1410
|
+
raise ConfigurationError("compact cache only supports version 1 or 2")
|
|
1411
|
+
|
|
1412
|
+
def _tile_cache(self, grid_conf, image_opts):
|
|
1413
|
+
if self.conf.get('disable_storage', False):
|
|
1414
|
+
from mapproxy.cache.dummy import DummyCache
|
|
1415
|
+
return DummyCache()
|
|
1416
|
+
|
|
1417
|
+
grid_conf.tile_grid() # create to resolve `base` in grid_conf.conf
|
|
1418
|
+
cache_type = self.conf.get('cache', {}).get('type', 'file')
|
|
1419
|
+
return getattr(self, '_%s_cache' % cache_type)(grid_conf, image_opts)
|
|
1420
|
+
|
|
1421
|
+
def _tile_filter(self):
|
|
1422
|
+
filters = []
|
|
1423
|
+
if 'watermark' in self.conf:
|
|
1424
|
+
from mapproxy.tilefilter import create_watermark_filter
|
|
1425
|
+
if self.conf['watermark'].get('color'):
|
|
1426
|
+
self.conf['watermark']['color'] = parse_color(self.conf['watermark']['color'])
|
|
1427
|
+
f = create_watermark_filter(self.conf, self.context)
|
|
1428
|
+
if f:
|
|
1429
|
+
filters.append(f)
|
|
1430
|
+
return filters
|
|
1431
|
+
|
|
1432
|
+
@memoize
|
|
1433
|
+
def image_opts(self):
|
|
1434
|
+
from mapproxy.image.opts import ImageFormat
|
|
1435
|
+
|
|
1436
|
+
format = None
|
|
1437
|
+
if 'format' not in self.conf.get('image', {}):
|
|
1438
|
+
format = self.conf.get('format') or self.conf.get('request_format')
|
|
1439
|
+
image_opts = self.context.globals.image_options.image_opts(self.conf.get('image', {}), format)
|
|
1440
|
+
if image_opts.format is None:
|
|
1441
|
+
if format is not None and format.startswith('image/'):
|
|
1442
|
+
image_opts.format = ImageFormat(format)
|
|
1443
|
+
else:
|
|
1444
|
+
image_opts.format = ImageFormat('image/png')
|
|
1445
|
+
return image_opts
|
|
1446
|
+
|
|
1447
|
+
def supports_tiled_only_access(self, params=None, tile_grid=None):
|
|
1448
|
+
caches = self.caches()
|
|
1449
|
+
if len(caches) > 1:
|
|
1450
|
+
return False
|
|
1451
|
+
|
|
1452
|
+
cache_grid, extent, tile_manager = caches[0]
|
|
1453
|
+
image_opts = self.image_opts()
|
|
1454
|
+
|
|
1455
|
+
if (tile_grid.is_subset_of(cache_grid)
|
|
1456
|
+
and params.get('format') == image_opts.format):
|
|
1457
|
+
return True
|
|
1458
|
+
|
|
1459
|
+
return False
|
|
1460
|
+
|
|
1461
|
+
def source(self, params=None, tile_grid=None, tiled_only=False):
|
|
1462
|
+
from mapproxy.source.tile import CacheSource
|
|
1463
|
+
from mapproxy.layer import map_extent_from_grid
|
|
1464
|
+
|
|
1465
|
+
caches = self.caches()
|
|
1466
|
+
if len(caches) > 1:
|
|
1467
|
+
# cache with multiple grids/sources
|
|
1468
|
+
source = self.map_layer()
|
|
1469
|
+
source.supports_meta_tiles = True
|
|
1470
|
+
return source
|
|
1471
|
+
|
|
1472
|
+
cache_grid, extent, tile_manager = caches[0]
|
|
1473
|
+
image_opts = self.image_opts()
|
|
1474
|
+
|
|
1475
|
+
cache_extent = map_extent_from_grid(tile_grid)
|
|
1476
|
+
cache_extent = extent.intersection(cache_extent)
|
|
1477
|
+
|
|
1478
|
+
source = CacheSource(tile_manager, extent=cache_extent,
|
|
1479
|
+
image_opts=image_opts, tiled_only=tiled_only)
|
|
1480
|
+
return source
|
|
1481
|
+
|
|
1482
|
+
def _sources_for_grid(self, source_names, grid_conf, request_format):
|
|
1483
|
+
sources = []
|
|
1484
|
+
source_image_opts = []
|
|
1485
|
+
|
|
1486
|
+
# a cache can directly access source tiles when _all_ sources are caches too
|
|
1487
|
+
# and when they have compatible grids by using tiled_only on the CacheSource
|
|
1488
|
+
# check if all sources support tiled_only
|
|
1489
|
+
tiled_only = True
|
|
1490
|
+
for source_name in source_names:
|
|
1491
|
+
if source_name in self.context.sources:
|
|
1492
|
+
tiled_only = False
|
|
1493
|
+
break
|
|
1494
|
+
elif source_name in self.context.caches:
|
|
1495
|
+
cache_conf = self.context.caches[source_name]
|
|
1496
|
+
tiled_only = cache_conf.supports_tiled_only_access(
|
|
1497
|
+
params={'format': request_format},
|
|
1498
|
+
tile_grid=grid_conf.tile_grid(),
|
|
1499
|
+
)
|
|
1500
|
+
if not tiled_only:
|
|
1501
|
+
break
|
|
1502
|
+
|
|
1503
|
+
for source_name in source_names:
|
|
1504
|
+
if source_name in self.context.sources:
|
|
1505
|
+
source_conf = self.context.sources[source_name]
|
|
1506
|
+
source = source_conf.source({'format': request_format})
|
|
1507
|
+
elif source_name in self.context.caches:
|
|
1508
|
+
cache_conf = self.context.caches[source_name]
|
|
1509
|
+
source = cache_conf.source(
|
|
1510
|
+
params={'format': request_format},
|
|
1511
|
+
tile_grid=grid_conf.tile_grid(),
|
|
1512
|
+
tiled_only=tiled_only,
|
|
1513
|
+
)
|
|
1514
|
+
else:
|
|
1515
|
+
raise ConfigurationError('unknown source %s' % source_name)
|
|
1516
|
+
if source:
|
|
1517
|
+
sources.append(source)
|
|
1518
|
+
source_image_opts.append(source.image_opts)
|
|
1519
|
+
|
|
1520
|
+
return sources, source_image_opts
|
|
1521
|
+
|
|
1522
|
+
def _sources_for_band_merge(self, sources_conf, grid_conf, request_format):
|
|
1523
|
+
from mapproxy.image.merge import BandMerger
|
|
1524
|
+
|
|
1525
|
+
source_names = []
|
|
1526
|
+
|
|
1527
|
+
for band, band_sources in sources_conf.items():
|
|
1528
|
+
for source in band_sources:
|
|
1529
|
+
name = source['source']
|
|
1530
|
+
if name in source_names:
|
|
1531
|
+
idx = source_names.index(name)
|
|
1532
|
+
else:
|
|
1533
|
+
source_names.append(name)
|
|
1534
|
+
idx = len(source_names) - 1
|
|
1535
|
+
|
|
1536
|
+
source["src_idx"] = idx
|
|
1537
|
+
|
|
1538
|
+
sources, source_image_opts = self._sources_for_grid(
|
|
1539
|
+
source_names=source_names,
|
|
1540
|
+
grid_conf=grid_conf,
|
|
1541
|
+
request_format=request_format,
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
if 'l' in sources_conf:
|
|
1545
|
+
mode = 'L'
|
|
1546
|
+
elif 'a' in sources_conf:
|
|
1547
|
+
mode = 'RGBA'
|
|
1548
|
+
else:
|
|
1549
|
+
mode = 'RGB'
|
|
1550
|
+
|
|
1551
|
+
band_merger = BandMerger(mode=mode)
|
|
1552
|
+
available_bands = {'r': 0, 'g': 1, 'b': 2, 'a': 3, 'l': 0}
|
|
1553
|
+
for band, band_sources in sources_conf.items():
|
|
1554
|
+
band_idx = available_bands.get(band)
|
|
1555
|
+
if band_idx is None:
|
|
1556
|
+
raise ConfigurationError("unsupported band '%s' for cache %s"
|
|
1557
|
+
% (band, self.conf['name']))
|
|
1558
|
+
for source in band_sources:
|
|
1559
|
+
band_merger.add_ops(
|
|
1560
|
+
dst_band=band_idx,
|
|
1561
|
+
src_img=source['src_idx'],
|
|
1562
|
+
src_band=source['band'],
|
|
1563
|
+
factor=source.get('factor', 1.0),
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
return band_merger, sources, source_image_opts
|
|
1567
|
+
|
|
1568
|
+
@memoize
|
|
1569
|
+
def caches(self):
|
|
1570
|
+
from mapproxy.cache.dummy import DummyCache, DummyLocker
|
|
1571
|
+
from mapproxy.cache.tile import TileManager
|
|
1572
|
+
from mapproxy.cache.base import TileLocker
|
|
1573
|
+
from mapproxy.image.opts import compatible_image_options
|
|
1574
|
+
from mapproxy.layer import map_extent_from_grid, merge_layer_extents
|
|
1575
|
+
|
|
1576
|
+
base_image_opts = self.image_opts()
|
|
1577
|
+
if (self.conf.get('format') == 'mixed' and
|
|
1578
|
+
self.conf.get('request_format') not in ['image/png', 'image/vnd.jpeg-png']):
|
|
1579
|
+
raise ConfigurationError(
|
|
1580
|
+
'request_format must be set to image/png or image/vnd.jpeg-png if mixed mode is enabled')
|
|
1581
|
+
request_format = self.conf.get('request_format') or self.conf.get('format')
|
|
1582
|
+
if '/' in request_format:
|
|
1583
|
+
request_format_ext = request_format.split('/', 1)[1]
|
|
1584
|
+
else:
|
|
1585
|
+
request_format_ext = request_format
|
|
1586
|
+
|
|
1587
|
+
caches = []
|
|
1588
|
+
|
|
1589
|
+
meta_buffer = self.context.globals.get_value('meta_buffer', self.conf,
|
|
1590
|
+
global_key='cache.meta_buffer')
|
|
1591
|
+
meta_size = self.context.globals.get_value('meta_size', self.conf,
|
|
1592
|
+
global_key='cache.meta_size')
|
|
1593
|
+
bulk_meta_tiles = self.context.globals.get_value('bulk_meta_tiles', self.conf,
|
|
1594
|
+
global_key='cache.bulk_meta_tiles')
|
|
1595
|
+
minimize_meta_requests = self.context.globals.get_value('minimize_meta_requests', self.conf,
|
|
1596
|
+
global_key='cache.minimize_meta_requests')
|
|
1597
|
+
concurrent_tile_creators = self.context.globals.get_value('concurrent_tile_creators', self.conf,
|
|
1598
|
+
global_key='cache.concurrent_tile_creators')
|
|
1599
|
+
|
|
1600
|
+
cache_rescaled_tiles = self.conf.get('cache_rescaled_tiles')
|
|
1601
|
+
upscale_tiles = self.conf.get('upscale_tiles', 0)
|
|
1602
|
+
if upscale_tiles < 0:
|
|
1603
|
+
raise ConfigurationError("upscale_tiles must be positive")
|
|
1604
|
+
downscale_tiles = self.conf.get('downscale_tiles', 0)
|
|
1605
|
+
if downscale_tiles < 0:
|
|
1606
|
+
raise ConfigurationError("downscale_tiles must be positive")
|
|
1607
|
+
if upscale_tiles and downscale_tiles:
|
|
1608
|
+
raise ConfigurationError("cannot use both upscale_tiles and downscale_tiles")
|
|
1609
|
+
|
|
1610
|
+
rescale_tiles = 0
|
|
1611
|
+
if upscale_tiles:
|
|
1612
|
+
rescale_tiles = -upscale_tiles
|
|
1613
|
+
if downscale_tiles:
|
|
1614
|
+
rescale_tiles = downscale_tiles
|
|
1615
|
+
|
|
1616
|
+
renderd_address = self.context.globals.get_value('renderd.address', self.conf)
|
|
1617
|
+
|
|
1618
|
+
band_merger = None
|
|
1619
|
+
for grid_name, grid_conf in self.grid_confs():
|
|
1620
|
+
if isinstance(self.conf['sources'], dict):
|
|
1621
|
+
band_merger, sources, source_image_opts = self._sources_for_band_merge(
|
|
1622
|
+
self.conf['sources'],
|
|
1623
|
+
grid_conf=grid_conf,
|
|
1624
|
+
request_format=request_format,
|
|
1625
|
+
)
|
|
1626
|
+
else:
|
|
1627
|
+
sources, source_image_opts = self._sources_for_grid(
|
|
1628
|
+
self.conf['sources'],
|
|
1629
|
+
grid_conf=grid_conf,
|
|
1630
|
+
request_format=request_format,
|
|
1631
|
+
)
|
|
1632
|
+
|
|
1633
|
+
if not sources:
|
|
1634
|
+
from mapproxy.source import DummySource
|
|
1635
|
+
sources = [DummySource()]
|
|
1636
|
+
source_image_opts.append(sources[0].image_opts)
|
|
1637
|
+
tile_grid = grid_conf.tile_grid()
|
|
1638
|
+
tile_filter = self._tile_filter()
|
|
1639
|
+
image_opts = compatible_image_options(source_image_opts, base_opts=base_image_opts)
|
|
1640
|
+
cache = self._tile_cache(grid_conf, image_opts)
|
|
1641
|
+
identifier = self.conf['name'] + '_' + tile_grid.name
|
|
1642
|
+
|
|
1643
|
+
tile_creator_class = None
|
|
1644
|
+
|
|
1645
|
+
use_renderd = bool(renderd_address)
|
|
1646
|
+
if self.context.renderd:
|
|
1647
|
+
# we _are_ renderd
|
|
1648
|
+
use_renderd = False
|
|
1649
|
+
if self.conf.get('disable_storage', False):
|
|
1650
|
+
# can't ask renderd to create tiles that shouldn't be cached
|
|
1651
|
+
use_renderd = False
|
|
1652
|
+
|
|
1653
|
+
if use_renderd:
|
|
1654
|
+
from mapproxy.cache.renderd import RenderdTileCreator, has_renderd_support
|
|
1655
|
+
if not has_renderd_support():
|
|
1656
|
+
raise ConfigurationError("renderd requires requests library")
|
|
1657
|
+
if self.context.seed:
|
|
1658
|
+
priority = 10
|
|
1659
|
+
else:
|
|
1660
|
+
priority = 100
|
|
1661
|
+
|
|
1662
|
+
cache_dir = self.cache_dir()
|
|
1663
|
+
|
|
1664
|
+
lock_dir = self.context.globals.get_value('cache.tile_lock_dir')
|
|
1665
|
+
if not lock_dir:
|
|
1666
|
+
lock_dir = os.path.join(cache_dir, 'tile_locks')
|
|
1667
|
+
|
|
1668
|
+
lock_timeout = self.context.globals.get_value('http.client_timeout', {})
|
|
1669
|
+
locker = TileLocker(lock_dir, lock_timeout, identifier + '_renderd')
|
|
1670
|
+
# TODO band_merger
|
|
1671
|
+
tile_creator_class = partial(RenderdTileCreator, renderd_address,
|
|
1672
|
+
priority=priority, tile_locker=locker)
|
|
1673
|
+
|
|
1674
|
+
else:
|
|
1675
|
+
from mapproxy.cache.tile import TileCreator
|
|
1676
|
+
tile_creator_class = partial(TileCreator, image_merger=band_merger)
|
|
1677
|
+
|
|
1678
|
+
if isinstance(cache, DummyCache):
|
|
1679
|
+
locker = DummyLocker()
|
|
1680
|
+
else:
|
|
1681
|
+
locker = TileLocker(
|
|
1682
|
+
lock_dir=self.lock_dir(),
|
|
1683
|
+
lock_timeout=self.context.globals.get_value('http.client_timeout', {}),
|
|
1684
|
+
lock_cache_id=cache.lock_cache_id,
|
|
1685
|
+
)
|
|
1686
|
+
|
|
1687
|
+
mgr = TileManager(tile_grid, cache, sources, image_opts.format.ext,
|
|
1688
|
+
locker=locker,
|
|
1689
|
+
image_opts=image_opts, identifier=identifier,
|
|
1690
|
+
request_format=request_format_ext,
|
|
1691
|
+
meta_size=meta_size, meta_buffer=meta_buffer,
|
|
1692
|
+
minimize_meta_requests=minimize_meta_requests,
|
|
1693
|
+
concurrent_tile_creators=concurrent_tile_creators,
|
|
1694
|
+
pre_store_filter=tile_filter,
|
|
1695
|
+
tile_creator_class=tile_creator_class,
|
|
1696
|
+
bulk_meta_tiles=bulk_meta_tiles,
|
|
1697
|
+
cache_rescaled_tiles=cache_rescaled_tiles,
|
|
1698
|
+
rescale_tiles=rescale_tiles,
|
|
1699
|
+
)
|
|
1700
|
+
if self.conf['name'] in self.context.caches:
|
|
1701
|
+
mgr._refresh_before = self.context.caches[self.conf['name']].conf.get('refresh_before', {})
|
|
1702
|
+
extent = merge_layer_extents(sources)
|
|
1703
|
+
# If the cache has a defined coverage prefer it's extent over source extent
|
|
1704
|
+
if cache.coverage:
|
|
1705
|
+
extent = cache.coverage.extent
|
|
1706
|
+
elif extent.is_default:
|
|
1707
|
+
extent = map_extent_from_grid(tile_grid)
|
|
1708
|
+
caches.append((tile_grid, extent, mgr))
|
|
1709
|
+
return caches
|
|
1710
|
+
|
|
1711
|
+
@memoize
|
|
1712
|
+
def grid_confs(self):
|
|
1713
|
+
grid_names = self.conf.get('grids')
|
|
1714
|
+
if grid_names is None:
|
|
1715
|
+
log.warning(
|
|
1716
|
+
'cache %s does not have any grids. default will change from [GLOBAL_MERCATOR] to [GLOBAL_WEBMERCATOR]'
|
|
1717
|
+
' with MapProxy 2.0', self.conf['name'])
|
|
1718
|
+
grid_names = ['GLOBAL_MERCATOR']
|
|
1719
|
+
return [(g, self.context.grids[g]) for g in grid_names]
|
|
1720
|
+
|
|
1721
|
+
@memoize
|
|
1722
|
+
def map_layer(self):
|
|
1723
|
+
from mapproxy.layer import CacheMapLayer, SRSConditional, ResolutionConditional
|
|
1724
|
+
|
|
1725
|
+
image_opts = self.image_opts()
|
|
1726
|
+
max_tile_limit = self.context.globals.get_value('max_tile_limit', self.conf,
|
|
1727
|
+
global_key='cache.max_tile_limit')
|
|
1728
|
+
caches = []
|
|
1729
|
+
main_grid = None
|
|
1730
|
+
for grid, extent, tile_manager in self.caches():
|
|
1731
|
+
if main_grid is None:
|
|
1732
|
+
main_grid = grid
|
|
1733
|
+
caches.append((CacheMapLayer(tile_manager, extent=extent, image_opts=image_opts,
|
|
1734
|
+
max_tile_limit=max_tile_limit),
|
|
1735
|
+
grid.srs))
|
|
1736
|
+
|
|
1737
|
+
if len(caches) == 1:
|
|
1738
|
+
layer = caches[0][0]
|
|
1739
|
+
else:
|
|
1740
|
+
layer = SRSConditional(caches, caches[0][0].extent, opacity=image_opts.opacity,
|
|
1741
|
+
preferred_srs=self.context.globals.preferred_srs)
|
|
1742
|
+
|
|
1743
|
+
if 'use_direct_from_level' in self.conf:
|
|
1744
|
+
self.conf['use_direct_from_res'] = main_grid.resolution(self.conf['use_direct_from_level'])
|
|
1745
|
+
if 'use_direct_from_res' in self.conf:
|
|
1746
|
+
if len(self.conf['sources']) != 1:
|
|
1747
|
+
raise ValueError('use_direct_from_level/res only supports single sources')
|
|
1748
|
+
source_conf = self.context.sources[self.conf['sources'][0]]
|
|
1749
|
+
layer = ResolutionConditional(layer, source_conf.source(), self.conf['use_direct_from_res'],
|
|
1750
|
+
main_grid.srs, layer.extent, opacity=image_opts.opacity)
|
|
1751
|
+
return layer
|
|
1752
|
+
|
|
1753
|
+
|
|
1754
|
+
class WMSLayerConfiguration(ConfigurationBase):
|
|
1755
|
+
@memoize
|
|
1756
|
+
def wms_layer(self):
|
|
1757
|
+
from mapproxy.service.wms import WMSGroupLayer
|
|
1758
|
+
|
|
1759
|
+
layers = []
|
|
1760
|
+
this_layer = None
|
|
1761
|
+
|
|
1762
|
+
if 'layers' in self.conf:
|
|
1763
|
+
layers_conf = self.conf['layers']
|
|
1764
|
+
for layer_conf in layers_conf:
|
|
1765
|
+
lyr = WMSLayerConfiguration(layer_conf, self.context).wms_layer()
|
|
1766
|
+
if lyr:
|
|
1767
|
+
layers.append(lyr)
|
|
1768
|
+
|
|
1769
|
+
if 'sources' in self.conf or 'legendurl' in self.conf:
|
|
1770
|
+
this_layer = LayerConfiguration(self.conf, self.context).wms_layer()
|
|
1771
|
+
|
|
1772
|
+
if not layers and not this_layer:
|
|
1773
|
+
return None
|
|
1774
|
+
|
|
1775
|
+
if not layers:
|
|
1776
|
+
layer = this_layer
|
|
1777
|
+
else:
|
|
1778
|
+
layer = WMSGroupLayer(name=self.conf.get('name'), title=self.conf.get('title'),
|
|
1779
|
+
this=this_layer, layers=layers, md=self.conf.get('md'))
|
|
1780
|
+
return layer
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
def cache_source_names(context, cache):
|
|
1784
|
+
"""
|
|
1785
|
+
Return all sources for a cache, even if a caches uses another cache.
|
|
1786
|
+
"""
|
|
1787
|
+
source_names = []
|
|
1788
|
+
for src in context.caches[cache].conf['sources']:
|
|
1789
|
+
if src in context.caches and src not in context.sources:
|
|
1790
|
+
source_names.extend(cache_source_names(context, src))
|
|
1791
|
+
else:
|
|
1792
|
+
source_names.append(src)
|
|
1793
|
+
|
|
1794
|
+
return source_names
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
class LayerConfiguration(ConfigurationBase):
|
|
1798
|
+
@memoize
|
|
1799
|
+
def wms_layer(self):
|
|
1800
|
+
from mapproxy.service.wms import WMSLayer
|
|
1801
|
+
|
|
1802
|
+
sources = []
|
|
1803
|
+
fi_sources = []
|
|
1804
|
+
lg_sources = []
|
|
1805
|
+
|
|
1806
|
+
lg_sources_configured = False
|
|
1807
|
+
if self.conf.get('legendurl'):
|
|
1808
|
+
legend_url = self.conf['legendurl']
|
|
1809
|
+
lg_sources.append(WMSSourceConfiguration.static_legend_source(legend_url, self.context))
|
|
1810
|
+
lg_sources_configured = True
|
|
1811
|
+
|
|
1812
|
+
for source_name in self.conf.get('sources', []):
|
|
1813
|
+
fi_source_names = []
|
|
1814
|
+
lg_source_names = []
|
|
1815
|
+
if source_name in self.context.caches:
|
|
1816
|
+
map_layer = self.context.caches[source_name].map_layer()
|
|
1817
|
+
fi_source_names = cache_source_names(self.context, source_name)
|
|
1818
|
+
lg_source_names = cache_source_names(self.context, source_name)
|
|
1819
|
+
elif source_name in self.context.sources:
|
|
1820
|
+
source_conf = self.context.sources[source_name]
|
|
1821
|
+
if not source_conf.supports_meta_tiles:
|
|
1822
|
+
raise ConfigurationError('source "%s" of layer "%s" does not support un-tiled access'
|
|
1823
|
+
% (source_name, self.conf.get('name')))
|
|
1824
|
+
map_layer = source_conf.source()
|
|
1825
|
+
fi_source_names = [source_name]
|
|
1826
|
+
lg_source_names = [source_name]
|
|
1827
|
+
else:
|
|
1828
|
+
raise ConfigurationError('source/cache "%s" not found' % source_name)
|
|
1829
|
+
|
|
1830
|
+
if map_layer:
|
|
1831
|
+
sources.append(map_layer)
|
|
1832
|
+
|
|
1833
|
+
for fi_source_name in fi_source_names:
|
|
1834
|
+
if fi_source_name not in self.context.sources:
|
|
1835
|
+
continue
|
|
1836
|
+
if not hasattr(self.context.sources[fi_source_name], 'fi_source'):
|
|
1837
|
+
continue
|
|
1838
|
+
fi_source = self.context.sources[fi_source_name].fi_source()
|
|
1839
|
+
if fi_source:
|
|
1840
|
+
fi_sources.append(fi_source)
|
|
1841
|
+
if not lg_sources_configured:
|
|
1842
|
+
for lg_source_name in lg_source_names:
|
|
1843
|
+
if lg_source_name not in self.context.sources:
|
|
1844
|
+
continue
|
|
1845
|
+
if not hasattr(self.context.sources[lg_source_name], 'lg_source'):
|
|
1846
|
+
continue
|
|
1847
|
+
lg_source = self.context.sources[lg_source_name].lg_source()
|
|
1848
|
+
if lg_source:
|
|
1849
|
+
lg_sources.append(lg_source)
|
|
1850
|
+
|
|
1851
|
+
res_range = resolution_range(self.conf)
|
|
1852
|
+
dimensions = None
|
|
1853
|
+
if 'dimensions' in self.conf.keys():
|
|
1854
|
+
dimensions = self.dimensions()
|
|
1855
|
+
|
|
1856
|
+
layer = WMSLayer(
|
|
1857
|
+
self.conf.get('name'), self.conf.get('title'), sources, fi_sources, lg_sources, res_range=res_range,
|
|
1858
|
+
md=self.conf.get('md'), dimensions=dimensions)
|
|
1859
|
+
return layer
|
|
1860
|
+
|
|
1861
|
+
@memoize
|
|
1862
|
+
def dimensions(self):
|
|
1863
|
+
from mapproxy.layer import Dimension
|
|
1864
|
+
from mapproxy.util.ext.wmsparse.util import parse_datetime_range
|
|
1865
|
+
dimensions = {}
|
|
1866
|
+
for dimension, conf in self.conf.get('dimensions', {}).items():
|
|
1867
|
+
raw_values = conf.get('values')
|
|
1868
|
+
if len(raw_values) == 1:
|
|
1869
|
+
# look for time or dim_reference_time
|
|
1870
|
+
if 'time' in dimension.lower():
|
|
1871
|
+
log.debug('Determining values as datetime strings')
|
|
1872
|
+
values = parse_datetime_range(raw_values[0])
|
|
1873
|
+
else:
|
|
1874
|
+
log.debug('Determining values as plain strings')
|
|
1875
|
+
values = raw_values[0].strip().split('/')
|
|
1876
|
+
else:
|
|
1877
|
+
values = [str(val) for val in conf.get('values', ['default'])]
|
|
1878
|
+
|
|
1879
|
+
default = conf.get('default', values[-1])
|
|
1880
|
+
dimensions[dimension.lower()] = Dimension(dimension, values, default=default)
|
|
1881
|
+
return dimensions
|
|
1882
|
+
|
|
1883
|
+
@memoize
|
|
1884
|
+
def tile_layers(self, grid_name_as_path=False):
|
|
1885
|
+
from mapproxy.service.tile import TileLayer
|
|
1886
|
+
from mapproxy.cache.dummy import DummyCache
|
|
1887
|
+
sources = []
|
|
1888
|
+
fi_only_sources = []
|
|
1889
|
+
if 'tile_sources' in self.conf:
|
|
1890
|
+
sources = self.conf['tile_sources']
|
|
1891
|
+
else:
|
|
1892
|
+
for source_name in self.conf.get('sources', []):
|
|
1893
|
+
# we only support caches for tiled access...
|
|
1894
|
+
if source_name not in self.context.caches:
|
|
1895
|
+
if source_name in self.context.sources:
|
|
1896
|
+
src_conf = self.context.sources[source_name].conf
|
|
1897
|
+
# but we ignore debug layers for convenience
|
|
1898
|
+
if src_conf['type'] == 'debug':
|
|
1899
|
+
continue
|
|
1900
|
+
# and WMS layers with map: False (i.e. FeatureInfo only sources)
|
|
1901
|
+
if src_conf['type'] == 'wms' and src_conf.get('wms_opts', {}).get('map', True) is False:
|
|
1902
|
+
fi_only_sources.append(source_name)
|
|
1903
|
+
continue
|
|
1904
|
+
|
|
1905
|
+
return []
|
|
1906
|
+
sources.append(source_name)
|
|
1907
|
+
|
|
1908
|
+
if len(sources) > 1:
|
|
1909
|
+
# skip layers with more then one source
|
|
1910
|
+
return []
|
|
1911
|
+
|
|
1912
|
+
dimensions = self.dimensions()
|
|
1913
|
+
|
|
1914
|
+
tile_layers = []
|
|
1915
|
+
for cache_name in sources:
|
|
1916
|
+
fi_sources = []
|
|
1917
|
+
fi_source_names = cache_source_names(self.context, cache_name)
|
|
1918
|
+
|
|
1919
|
+
for fi_source_name in fi_source_names + fi_only_sources:
|
|
1920
|
+
if fi_source_name not in self.context.sources:
|
|
1921
|
+
continue
|
|
1922
|
+
if not hasattr(self.context.sources[fi_source_name], 'fi_source'):
|
|
1923
|
+
continue
|
|
1924
|
+
fi_source = self.context.sources[fi_source_name].fi_source()
|
|
1925
|
+
if fi_source:
|
|
1926
|
+
fi_sources.append(fi_source)
|
|
1927
|
+
|
|
1928
|
+
for grid, extent, cache_source in self.context.caches[cache_name].caches():
|
|
1929
|
+
disable_storage = self.context.configuration['caches'][cache_name].get('disable_storage', False)
|
|
1930
|
+
if disable_storage:
|
|
1931
|
+
supports_dimensions = isinstance(cache_source.cache, DummyCache)
|
|
1932
|
+
else:
|
|
1933
|
+
supports_dimensions = cache_source.cache.supports_dimensions
|
|
1934
|
+
if dimensions and not supports_dimensions:
|
|
1935
|
+
# caching of dimension layers is not supported yet
|
|
1936
|
+
raise ConfigurationError(
|
|
1937
|
+
"caching of dimension layer (%s) is not supported yet."
|
|
1938
|
+
" need to `disable_storage: true` on %s cache" % (self.conf['name'], cache_name)
|
|
1939
|
+
)
|
|
1940
|
+
|
|
1941
|
+
md = {}
|
|
1942
|
+
md['title'] = self.conf['title']
|
|
1943
|
+
md['name'] = self.conf['name']
|
|
1944
|
+
md['grid_name'] = grid.name
|
|
1945
|
+
if grid_name_as_path:
|
|
1946
|
+
md['name_path'] = (md['name'], md['grid_name'])
|
|
1947
|
+
else:
|
|
1948
|
+
md['name_path'] = (md['name'], grid.srs.srs_code.replace(':', '').upper())
|
|
1949
|
+
md['name_internal'] = md['name_path'][0] + '_' + md['name_path'][1]
|
|
1950
|
+
md['format'] = self.context.caches[cache_name].image_opts().format
|
|
1951
|
+
md['cache_name'] = cache_name
|
|
1952
|
+
md['extent'] = extent
|
|
1953
|
+
tile_layers.append(
|
|
1954
|
+
TileLayer(
|
|
1955
|
+
self.conf['name'], self.conf['title'],
|
|
1956
|
+
info_sources=fi_sources,
|
|
1957
|
+
md=md,
|
|
1958
|
+
tile_manager=cache_source,
|
|
1959
|
+
dimensions=dimensions,
|
|
1960
|
+
)
|
|
1961
|
+
)
|
|
1962
|
+
|
|
1963
|
+
return tile_layers
|
|
1964
|
+
|
|
1965
|
+
|
|
1966
|
+
def fi_xslt_transformers(conf, context):
|
|
1967
|
+
from mapproxy.featureinfo import XSLTransformer, has_xslt_support
|
|
1968
|
+
fi_transformers = {}
|
|
1969
|
+
fi_xslt = conf.get('featureinfo_xslt')
|
|
1970
|
+
if fi_xslt:
|
|
1971
|
+
if not has_xslt_support:
|
|
1972
|
+
raise ValueError('featureinfo_xslt requires lxml. Please install.')
|
|
1973
|
+
for info_type, fi_xslt in fi_xslt.items():
|
|
1974
|
+
fi_xslt = context.globals.abspath(fi_xslt)
|
|
1975
|
+
fi_transformers[info_type] = XSLTransformer(fi_xslt)
|
|
1976
|
+
return fi_transformers
|
|
1977
|
+
|
|
1978
|
+
|
|
1979
|
+
def extents_for_srs(bbox_srs):
|
|
1980
|
+
from mapproxy.layer import DefaultMapExtent, MapExtent
|
|
1981
|
+
from mapproxy.srs import SRS
|
|
1982
|
+
extents = {}
|
|
1983
|
+
for srs in bbox_srs:
|
|
1984
|
+
if isinstance(srs, str):
|
|
1985
|
+
bbox = DefaultMapExtent()
|
|
1986
|
+
else:
|
|
1987
|
+
srs, bbox = srs['srs'], srs['bbox']
|
|
1988
|
+
bbox = MapExtent(bbox, SRS(srs))
|
|
1989
|
+
|
|
1990
|
+
extents[srs] = bbox
|
|
1991
|
+
|
|
1992
|
+
return extents
|
|
1993
|
+
|
|
1994
|
+
|
|
1995
|
+
plugin_services = {}
|
|
1996
|
+
|
|
1997
|
+
|
|
1998
|
+
def register_service_configuration(service_name, service_creator,
|
|
1999
|
+
yaml_spec_service_name=None, yaml_spec_service_def=None):
|
|
2000
|
+
""" Method used by plugins to register a new service.
|
|
2001
|
+
|
|
2002
|
+
:param service_name: Name of the service
|
|
2003
|
+
:type service_name: str
|
|
2004
|
+
:param service_creator: Creator method of the service
|
|
2005
|
+
:type service_creator: method of type (serviceConfiguration: ServiceConfiguration, conf: dict) -> Server
|
|
2006
|
+
:param yaml_spec_service_name: Name of the service in the YAML configuration file
|
|
2007
|
+
:type yaml_spec_service_name: str
|
|
2008
|
+
:param yaml_spec_service_def: Definition of the service in the YAML configuration file
|
|
2009
|
+
:type yaml_spec_service_def: dict
|
|
2010
|
+
"""
|
|
2011
|
+
|
|
2012
|
+
log.info('Registering configuration for plugin service %s' % service_name)
|
|
2013
|
+
plugin_services[service_name] = service_creator
|
|
2014
|
+
if yaml_spec_service_name is not None and yaml_spec_service_def is not None:
|
|
2015
|
+
add_service_to_mapproxy_yaml_spec(yaml_spec_service_name, yaml_spec_service_def)
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
class ServiceConfiguration(ConfigurationBase):
|
|
2019
|
+
def __init__(self, conf, context):
|
|
2020
|
+
if 'wms' in conf:
|
|
2021
|
+
if conf['wms'] is None:
|
|
2022
|
+
conf['wms'] = {}
|
|
2023
|
+
if 'md' not in conf['wms']:
|
|
2024
|
+
conf['wms']['md'] = {'title': 'MapProxy WMS'}
|
|
2025
|
+
|
|
2026
|
+
ConfigurationBase.__init__(self, conf, context)
|
|
2027
|
+
|
|
2028
|
+
def services(self):
|
|
2029
|
+
services = []
|
|
2030
|
+
ows_services = []
|
|
2031
|
+
for service_name, service_conf in self.conf.items():
|
|
2032
|
+
creator = getattr(self, service_name + '_service', None)
|
|
2033
|
+
if not creator:
|
|
2034
|
+
# If not a known service, try to use the plugin mechanism
|
|
2035
|
+
global plugin_services
|
|
2036
|
+
creator = plugin_services.get(service_name, None)
|
|
2037
|
+
if not creator:
|
|
2038
|
+
raise ValueError('unknown service: %s' % service_name)
|
|
2039
|
+
new_services = creator(self, service_conf or {})
|
|
2040
|
+
else:
|
|
2041
|
+
new_services = creator(service_conf or {})
|
|
2042
|
+
|
|
2043
|
+
# a creator can return a list of services...
|
|
2044
|
+
if not isinstance(new_services, (list, tuple)):
|
|
2045
|
+
new_services = [new_services]
|
|
2046
|
+
|
|
2047
|
+
for new_service in new_services:
|
|
2048
|
+
if getattr(new_service, 'service', None):
|
|
2049
|
+
ows_services.append(new_service)
|
|
2050
|
+
else:
|
|
2051
|
+
services.append(new_service)
|
|
2052
|
+
|
|
2053
|
+
if ows_services:
|
|
2054
|
+
from mapproxy.service.ows import OWSServer
|
|
2055
|
+
services.append(OWSServer(ows_services))
|
|
2056
|
+
return services
|
|
2057
|
+
|
|
2058
|
+
def tile_layers(self, conf, use_grid_names=False):
|
|
2059
|
+
layers = odict()
|
|
2060
|
+
for layer_name, layer_conf in self.context.layers.items():
|
|
2061
|
+
for tile_layer in layer_conf.tile_layers(grid_name_as_path=use_grid_names):
|
|
2062
|
+
if not tile_layer:
|
|
2063
|
+
continue
|
|
2064
|
+
if use_grid_names:
|
|
2065
|
+
layers[tile_layer.md['name_path']] = tile_layer
|
|
2066
|
+
else:
|
|
2067
|
+
layers[tile_layer.md['name_internal']] = tile_layer
|
|
2068
|
+
return layers
|
|
2069
|
+
|
|
2070
|
+
def kml_service(self, conf):
|
|
2071
|
+
from mapproxy.service.kml import KMLServer
|
|
2072
|
+
|
|
2073
|
+
md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
|
|
2074
|
+
md.update(conf.get('md', {}))
|
|
2075
|
+
max_tile_age = self.context.globals.get_value('tiles.expires_hours')
|
|
2076
|
+
max_tile_age *= 60 * 60 # seconds
|
|
2077
|
+
use_grid_names = conf.get('use_grid_names', False)
|
|
2078
|
+
layers = self.tile_layers(conf, use_grid_names=use_grid_names)
|
|
2079
|
+
return KMLServer(layers, md, max_tile_age=max_tile_age, use_dimension_layers=use_grid_names)
|
|
2080
|
+
|
|
2081
|
+
def tms_service(self, conf):
|
|
2082
|
+
from mapproxy.service.tile import TileServer
|
|
2083
|
+
|
|
2084
|
+
md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
|
|
2085
|
+
md.update(conf.get('md', {}))
|
|
2086
|
+
max_tile_age = self.context.globals.get_value('tiles.expires_hours')
|
|
2087
|
+
max_tile_age *= 60 * 60 # seconds
|
|
2088
|
+
|
|
2089
|
+
origin = conf.get('origin')
|
|
2090
|
+
use_grid_names = conf.get('use_grid_names', False)
|
|
2091
|
+
layers = self.tile_layers(conf, use_grid_names=use_grid_names)
|
|
2092
|
+
return TileServer(layers, md, max_tile_age=max_tile_age, use_dimension_layers=use_grid_names,
|
|
2093
|
+
origin=origin)
|
|
2094
|
+
|
|
2095
|
+
def wmts_service(self, conf):
|
|
2096
|
+
from mapproxy.service.wmts import WMTSServer, WMTSRestServer
|
|
2097
|
+
|
|
2098
|
+
md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
|
|
2099
|
+
md.update(conf.get('md', {}))
|
|
2100
|
+
layers = self.tile_layers(conf, use_grid_names=True)
|
|
2101
|
+
|
|
2102
|
+
kvp = conf.get('kvp')
|
|
2103
|
+
restful = conf.get('restful')
|
|
2104
|
+
|
|
2105
|
+
max_tile_age = self.context.globals.get_value('tiles.expires_hours')
|
|
2106
|
+
max_tile_age *= 60 * 60 # seconds
|
|
2107
|
+
|
|
2108
|
+
info_formats = conf.get('featureinfo_formats', [])
|
|
2109
|
+
info_formats = odict((f['suffix'], f['mimetype']) for f in info_formats)
|
|
2110
|
+
|
|
2111
|
+
if kvp is None and restful is None:
|
|
2112
|
+
kvp = restful = True
|
|
2113
|
+
|
|
2114
|
+
services = []
|
|
2115
|
+
if kvp:
|
|
2116
|
+
services.append(
|
|
2117
|
+
WMTSServer(
|
|
2118
|
+
layers, md, max_tile_age=max_tile_age,
|
|
2119
|
+
info_formats=info_formats,
|
|
2120
|
+
)
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2123
|
+
if restful:
|
|
2124
|
+
template = conf.get('restful_template')
|
|
2125
|
+
fi_template = conf.get('restful_featureinfo_template')
|
|
2126
|
+
if template and '{{' in template:
|
|
2127
|
+
# TODO remove warning in 1.6
|
|
2128
|
+
log.warning("double braces in WMTS restful_template are deprecated {{x}} -> {x}")
|
|
2129
|
+
services.append(
|
|
2130
|
+
WMTSRestServer(
|
|
2131
|
+
layers, md, template=template,
|
|
2132
|
+
fi_template=fi_template,
|
|
2133
|
+
max_tile_age=max_tile_age,
|
|
2134
|
+
info_formats=info_formats,
|
|
2135
|
+
)
|
|
2136
|
+
)
|
|
2137
|
+
|
|
2138
|
+
return services
|
|
2139
|
+
|
|
2140
|
+
def wms_service(self, conf):
|
|
2141
|
+
from mapproxy.service.wms import WMSServer
|
|
2142
|
+
from mapproxy.request.wms import Version
|
|
2143
|
+
|
|
2144
|
+
md = conf.get('md', {})
|
|
2145
|
+
inspire_md = conf.get('inspire_md', {})
|
|
2146
|
+
tile_layers = self.tile_layers(conf)
|
|
2147
|
+
attribution = conf.get('attribution')
|
|
2148
|
+
strict = self.context.globals.get_value('strict', conf, global_key='wms.strict')
|
|
2149
|
+
on_source_errors = self.context.globals.get_value('on_source_errors',
|
|
2150
|
+
conf, global_key='wms.on_source_errors')
|
|
2151
|
+
root_layer = self.context.wms_root_layer.wms_layer()
|
|
2152
|
+
if not root_layer:
|
|
2153
|
+
raise ConfigurationError("found no WMS layer")
|
|
2154
|
+
if not root_layer.title:
|
|
2155
|
+
# set title of root layer to WMS title
|
|
2156
|
+
root_layer.title = md.get('title')
|
|
2157
|
+
concurrent_layer_renderer = self.context.globals.get_value(
|
|
2158
|
+
'concurrent_layer_renderer', conf,
|
|
2159
|
+
global_key='wms.concurrent_layer_renderer')
|
|
2160
|
+
image_formats_names = self.context.globals.get_value('image_formats', conf,
|
|
2161
|
+
global_key='wms.image_formats')
|
|
2162
|
+
image_formats = odict()
|
|
2163
|
+
for format in image_formats_names:
|
|
2164
|
+
opts = self.context.globals.image_options.image_opts({}, format)
|
|
2165
|
+
if opts.format in image_formats:
|
|
2166
|
+
log.warning('duplicate mime-type for WMS image_formats: "%s" already configured, will use last format',
|
|
2167
|
+
opts.format)
|
|
2168
|
+
image_formats[opts.format] = opts
|
|
2169
|
+
info_types = conf.get('featureinfo_types')
|
|
2170
|
+
srs = self.context.globals.get_value('srs', conf, global_key='wms.srs')
|
|
2171
|
+
self.context.globals.base_config.wms.srs = srs
|
|
2172
|
+
srs_extents = extents_for_srs(conf.get('bbox_srs', []))
|
|
2173
|
+
|
|
2174
|
+
versions = conf.get('versions')
|
|
2175
|
+
if versions:
|
|
2176
|
+
versions = sorted([Version(v) for v in versions])
|
|
2177
|
+
|
|
2178
|
+
max_output_pixels = self.context.globals.get_value('max_output_pixels', conf,
|
|
2179
|
+
global_key='wms.max_output_pixels')
|
|
2180
|
+
if isinstance(max_output_pixels, list):
|
|
2181
|
+
max_output_pixels = max_output_pixels[0] * max_output_pixels[1]
|
|
2182
|
+
|
|
2183
|
+
max_tile_age = self.context.globals.get_value('tiles.expires_hours')
|
|
2184
|
+
max_tile_age *= 60 * 60 # seconds
|
|
2185
|
+
|
|
2186
|
+
server = WMSServer(root_layer, md, attribution=attribution,
|
|
2187
|
+
image_formats=image_formats, info_types=info_types,
|
|
2188
|
+
srs=srs, tile_layers=tile_layers, strict=strict, on_error=on_source_errors,
|
|
2189
|
+
concurrent_layer_renderer=concurrent_layer_renderer,
|
|
2190
|
+
max_output_pixels=max_output_pixels, srs_extents=srs_extents,
|
|
2191
|
+
max_tile_age=max_tile_age, versions=versions,
|
|
2192
|
+
inspire_md=inspire_md,
|
|
2193
|
+
)
|
|
2194
|
+
|
|
2195
|
+
server.fi_transformers = fi_xslt_transformers(conf, self.context)
|
|
2196
|
+
|
|
2197
|
+
return server
|
|
2198
|
+
|
|
2199
|
+
def demo_service(self, conf):
|
|
2200
|
+
from mapproxy.service.demo import DemoServer
|
|
2201
|
+
services = list(self.context.services.conf.keys())
|
|
2202
|
+
md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
|
|
2203
|
+
md.update(conf.get('md', {}))
|
|
2204
|
+
layers = odict()
|
|
2205
|
+
for layer_name, layer_conf in self.context.layers.items():
|
|
2206
|
+
lyr = layer_conf.wms_layer()
|
|
2207
|
+
if lyr:
|
|
2208
|
+
layers[layer_name] = lyr
|
|
2209
|
+
image_formats = self.context.globals.get_value('image_formats', conf, global_key='wms.image_formats')
|
|
2210
|
+
srs = self.context.globals.get_value('srs', conf, global_key='wms.srs')
|
|
2211
|
+
tms_conf = self.context.services.conf.get('tms', {}) or {}
|
|
2212
|
+
use_grid_names = tms_conf.get('use_grid_names', False)
|
|
2213
|
+
tile_layers = self.tile_layers(tms_conf, use_grid_names=use_grid_names)
|
|
2214
|
+
|
|
2215
|
+
# WMTS restful template
|
|
2216
|
+
wmts_conf = self.context.services.conf.get('wmts', {}) or {}
|
|
2217
|
+
from mapproxy.service.wmts import WMTSRestServer
|
|
2218
|
+
if wmts_conf:
|
|
2219
|
+
restful_template = wmts_conf.get('restful_template', WMTSRestServer.default_template)
|
|
2220
|
+
else:
|
|
2221
|
+
restful_template = WMTSRestServer.default_template
|
|
2222
|
+
|
|
2223
|
+
if 'wmts' in self.context.services.conf:
|
|
2224
|
+
kvp = wmts_conf.get('kvp')
|
|
2225
|
+
restful = wmts_conf.get('restful')
|
|
2226
|
+
|
|
2227
|
+
if kvp is None and restful is None:
|
|
2228
|
+
kvp = restful = True
|
|
2229
|
+
|
|
2230
|
+
if kvp:
|
|
2231
|
+
services.append('wmts_kvp')
|
|
2232
|
+
if restful:
|
|
2233
|
+
services.append('wmts_restful')
|
|
2234
|
+
|
|
2235
|
+
if 'wms' in self.context.services.conf:
|
|
2236
|
+
versions = self.context.services.conf['wms'].get('versions', ['1.1.1'])
|
|
2237
|
+
if '1.1.1' in versions:
|
|
2238
|
+
# demo service only supports 1.1.1, use wms_111 as an indicator
|
|
2239
|
+
services.append('wms_111')
|
|
2240
|
+
|
|
2241
|
+
layers = odict(sorted(layers.items(), key=lambda x: x[1].name))
|
|
2242
|
+
background = self.context.globals.get_value('background', conf)
|
|
2243
|
+
|
|
2244
|
+
return DemoServer(
|
|
2245
|
+
layers, md, tile_layers=tile_layers, image_formats=image_formats, srs=srs, services=services,
|
|
2246
|
+
restful_template=restful_template, background=background)
|
|
2247
|
+
|
|
2248
|
+
|
|
2249
|
+
def load_plugins():
|
|
2250
|
+
""" Locate plugins that belong to the 'mapproxy' group and load them """
|
|
2251
|
+
try:
|
|
2252
|
+
import importlib.metadata
|
|
2253
|
+
except ImportError:
|
|
2254
|
+
return
|
|
2255
|
+
|
|
2256
|
+
for dist in importlib.metadata.distributions():
|
|
2257
|
+
for ep in dist.entry_points:
|
|
2258
|
+
if ep.group == 'mapproxy':
|
|
2259
|
+
log.info('Loading plugin from package %s' % dist.metadata['name'])
|
|
2260
|
+
ep.load().plugin_entrypoint()
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
def load_configuration(mapproxy_conf, seed=False, ignore_warnings=True, renderd=False):
|
|
2264
|
+
|
|
2265
|
+
load_plugins()
|
|
2266
|
+
|
|
2267
|
+
conf_base_dir = os.path.abspath(os.path.dirname(mapproxy_conf))
|
|
2268
|
+
|
|
2269
|
+
# A configuration is checked/validated four times, each step has a different
|
|
2270
|
+
# focus and returns different errors. The steps are:
|
|
2271
|
+
# 1. YAML loading: checks YAML syntax like tabs vs. space, indention errors, etc.
|
|
2272
|
+
# 2. Options: checks all options agains the spec and validates their types,
|
|
2273
|
+
# e.g is disable_storage a bool, is layers a list, etc.
|
|
2274
|
+
# 3. References: checks if all referenced caches, sources and grids exist
|
|
2275
|
+
# 4. Initialization: creates all MapProxy objects, returns on first error
|
|
2276
|
+
|
|
2277
|
+
try:
|
|
2278
|
+
conf_dict = load_configuration_file([os.path.basename(mapproxy_conf)], conf_base_dir)
|
|
2279
|
+
except YAMLError as ex:
|
|
2280
|
+
raise ConfigurationError(ex)
|
|
2281
|
+
errors, informal_only = validate_options(conf_dict)
|
|
2282
|
+
for error in errors:
|
|
2283
|
+
log.warning(error)
|
|
2284
|
+
if not informal_only or (errors and not ignore_warnings):
|
|
2285
|
+
raise ConfigurationError('invalid configuration')
|
|
2286
|
+
errors = validate(conf_dict)
|
|
2287
|
+
for error in errors:
|
|
2288
|
+
log.warning(error)
|
|
2289
|
+
|
|
2290
|
+
return ProxyConfiguration(conf_dict, conf_base_dir=conf_base_dir, seed=seed,
|
|
2291
|
+
renderd=renderd)
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
def load_configuration_file(files, working_dir):
|
|
2295
|
+
"""
|
|
2296
|
+
Return configuration dict from imported files
|
|
2297
|
+
"""
|
|
2298
|
+
# record all config files with timestamp for reloading
|
|
2299
|
+
conf_dict = {'__config_files__': {}}
|
|
2300
|
+
for conf_file in files:
|
|
2301
|
+
conf_file = os.path.normpath(os.path.join(working_dir, conf_file))
|
|
2302
|
+
log.info('reading: %s' % conf_file)
|
|
2303
|
+
current_dict = load_yaml_file(conf_file)
|
|
2304
|
+
conf_dict['__config_files__'][os.path.abspath(conf_file)] = os.path.getmtime(conf_file)
|
|
2305
|
+
if 'base' in current_dict:
|
|
2306
|
+
current_working_dir = os.path.dirname(conf_file)
|
|
2307
|
+
base_files = current_dict.pop('base')
|
|
2308
|
+
if isinstance(base_files, str):
|
|
2309
|
+
base_files = [base_files]
|
|
2310
|
+
imported_dict = load_configuration_file(base_files, current_working_dir)
|
|
2311
|
+
current_dict = merge_dict(current_dict, imported_dict)
|
|
2312
|
+
conf_dict = merge_dict(conf_dict, current_dict)
|
|
2313
|
+
|
|
2314
|
+
return conf_dict
|
|
2315
|
+
|
|
2316
|
+
|
|
2317
|
+
def merge_dict(conf, base):
|
|
2318
|
+
"""
|
|
2319
|
+
Return `base` dict with values from `conf` merged in.
|
|
2320
|
+
"""
|
|
2321
|
+
for k, v in conf.items():
|
|
2322
|
+
if k not in base:
|
|
2323
|
+
base[k] = v
|
|
2324
|
+
else:
|
|
2325
|
+
if isinstance(base[k], dict):
|
|
2326
|
+
if v is not None:
|
|
2327
|
+
base[k] = merge_dict(v, base[k])
|
|
2328
|
+
elif isinstance(base[k], list):
|
|
2329
|
+
if v is not None:
|
|
2330
|
+
if k in ['bbox', 'tile_size', 'max_output_pixels', 'sources', 'grids']:
|
|
2331
|
+
base[k] = v
|
|
2332
|
+
elif k in ['layers']:
|
|
2333
|
+
base[k] = merge_layers(v, base[k])
|
|
2334
|
+
elif len(v) == 0: # delete
|
|
2335
|
+
base[k] = None
|
|
2336
|
+
else:
|
|
2337
|
+
base[k] = base[k] + v
|
|
2338
|
+
else:
|
|
2339
|
+
base[k] = v
|
|
2340
|
+
return base
|
|
2341
|
+
|
|
2342
|
+
|
|
2343
|
+
def merge_layers(conf, base):
|
|
2344
|
+
"""
|
|
2345
|
+
Return `base` dict with values from `conf` merged in.
|
|
2346
|
+
"""
|
|
2347
|
+
out = []
|
|
2348
|
+
remaining_conf = []
|
|
2349
|
+
for conf_layer in conf:
|
|
2350
|
+
remaining_conf.append(conf_layer['name'])
|
|
2351
|
+
|
|
2352
|
+
for base_layer in base:
|
|
2353
|
+
found = False
|
|
2354
|
+
for conf_layer in conf:
|
|
2355
|
+
if conf_layer['name'] in remaining_conf and base_layer['name'] == conf_layer['name']:
|
|
2356
|
+
new_layer = merge_dict(conf_layer, base_layer)
|
|
2357
|
+
out.append(new_layer)
|
|
2358
|
+
remaining_conf.remove(conf_layer['name'])
|
|
2359
|
+
found = True
|
|
2360
|
+
break
|
|
2361
|
+
|
|
2362
|
+
if not found:
|
|
2363
|
+
out.append(base_layer)
|
|
2364
|
+
|
|
2365
|
+
for conf_layer in conf:
|
|
2366
|
+
if conf_layer['name'] in remaining_conf:
|
|
2367
|
+
out.append(conf_layer)
|
|
2368
|
+
|
|
2369
|
+
return out
|
|
2370
|
+
|
|
2371
|
+
|
|
2372
|
+
def parse_color(color):
|
|
2373
|
+
"""
|
|
2374
|
+
>>> parse_color((100, 12, 55))
|
|
2375
|
+
(100, 12, 55)
|
|
2376
|
+
>>> parse_color('0xff0530')
|
|
2377
|
+
(255, 5, 48)
|
|
2378
|
+
>>> parse_color('#FF0530')
|
|
2379
|
+
(255, 5, 48)
|
|
2380
|
+
>>> parse_color('#FF053080')
|
|
2381
|
+
(255, 5, 48, 128)
|
|
2382
|
+
"""
|
|
2383
|
+
if isinstance(color, (list, tuple)) and 3 <= len(color) <= 4:
|
|
2384
|
+
return tuple(color)
|
|
2385
|
+
if not isinstance(color, str):
|
|
2386
|
+
raise ValueError('color needs to be a tuple/list or 0xrrggbb/#rrggbb(aa) string, got %r' % color)
|
|
2387
|
+
|
|
2388
|
+
if color.startswith('0x'):
|
|
2389
|
+
color = color[2:]
|
|
2390
|
+
if color.startswith('#'):
|
|
2391
|
+
color = color[1:]
|
|
2392
|
+
|
|
2393
|
+
r, g, b = map(lambda x: int(x, 16), [color[:2], color[2:4], color[4:6]])
|
|
2394
|
+
|
|
2395
|
+
if len(color) == 8:
|
|
2396
|
+
a = int(color[6:8], 16)
|
|
2397
|
+
return r, g, b, a
|
|
2398
|
+
|
|
2399
|
+
return r, g, b
|