MapProxy 1.16.1__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/__init__.py +0 -0
- mapproxy/cache/__init__.py +36 -0
- mapproxy/cache/azureblob.py +145 -0
- mapproxy/cache/base.py +111 -0
- mapproxy/cache/compact.py +664 -0
- mapproxy/cache/couchdb.py +295 -0
- mapproxy/cache/dummy.py +34 -0
- mapproxy/cache/file.py +185 -0
- mapproxy/cache/geopackage.py +609 -0
- mapproxy/cache/legend.py +83 -0
- mapproxy/cache/mbtiles.py +392 -0
- mapproxy/cache/meta.py +78 -0
- mapproxy/cache/path.py +250 -0
- mapproxy/cache/redis.py +88 -0
- mapproxy/cache/renderd.py +95 -0
- mapproxy/cache/riak.py +202 -0
- mapproxy/cache/s3.py +177 -0
- mapproxy/cache/tile.py +699 -0
- mapproxy/client/__init__.py +0 -0
- mapproxy/client/arcgis.py +79 -0
- mapproxy/client/cgi.py +139 -0
- mapproxy/client/http.py +315 -0
- mapproxy/client/log.py +33 -0
- mapproxy/client/tile.py +150 -0
- mapproxy/client/wms.py +254 -0
- mapproxy/compat/__init__.py +46 -0
- mapproxy/compat/image.py +79 -0
- mapproxy/compat/itertools.py +29 -0
- mapproxy/compat/modules.py +13 -0
- mapproxy/config/__init__.py +22 -0
- mapproxy/config/config.py +201 -0
- mapproxy/config/coverage.py +107 -0
- mapproxy/config/defaults.py +98 -0
- mapproxy/config/loader.py +2286 -0
- mapproxy/config/spec.py +644 -0
- mapproxy/config/validator.py +239 -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 +593 -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 +142 -0
- mapproxy/featureinfo.py +252 -0
- mapproxy/grid.py +1170 -0
- mapproxy/image/__init__.py +536 -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 +75 -0
- mapproxy/image/merge.py +316 -0
- mapproxy/image/message.py +347 -0
- mapproxy/image/opts.py +182 -0
- mapproxy/image/tile.py +167 -0
- mapproxy/image/transform.py +350 -0
- mapproxy/layer.py +470 -0
- mapproxy/multiapp.py +231 -0
- mapproxy/proj.py +302 -0
- mapproxy/request/__init__.py +18 -0
- mapproxy/request/arcgis.py +259 -0
- mapproxy/request/base.py +476 -0
- mapproxy/request/tile.py +128 -0
- mapproxy/request/wms/__init__.py +793 -0
- mapproxy/request/wms/exception.py +99 -0
- mapproxy/request/wmts.py +436 -0
- mapproxy/response.py +237 -0
- mapproxy/script/__init__.py +0 -0
- mapproxy/script/conf/__init__.py +0 -0
- mapproxy/script/conf/app.py +195 -0
- mapproxy/script/conf/caches.py +45 -0
- mapproxy/script/conf/layers.py +54 -0
- mapproxy/script/conf/seeds.py +37 -0
- mapproxy/script/conf/sources.py +86 -0
- mapproxy/script/conf/utils.py +143 -0
- mapproxy/script/defrag.py +184 -0
- mapproxy/script/export.py +333 -0
- mapproxy/script/grids.py +188 -0
- mapproxy/script/scales.py +126 -0
- mapproxy/script/util.py +406 -0
- mapproxy/script/wms_capabilities.py +152 -0
- mapproxy/seed/__init__.py +0 -0
- mapproxy/seed/cachelock.py +121 -0
- mapproxy/seed/cleanup.py +187 -0
- mapproxy/seed/config.py +469 -0
- mapproxy/seed/script.py +388 -0
- mapproxy/seed/seeder.py +538 -0
- mapproxy/seed/spec.py +64 -0
- mapproxy/seed/util.py +254 -0
- mapproxy/service/__init__.py +14 -0
- mapproxy/service/base.py +46 -0
- mapproxy/service/demo.py +356 -0
- mapproxy/service/kml.py +331 -0
- mapproxy/service/ows.py +38 -0
- mapproxy/service/template_helper.py +53 -0
- mapproxy/service/templates/demo/capabilities_demo.html +16 -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 +103 -0
- mapproxy/service/templates/demo/wms_demo.html +140 -0
- mapproxy/service/templates/demo/wmts_demo.html +110 -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 +536 -0
- mapproxy/service/wms.py +851 -0
- mapproxy/service/wmts.py +381 -0
- mapproxy/source/__init__.py +75 -0
- mapproxy/source/arcgis.py +39 -0
- mapproxy/source/error.py +39 -0
- mapproxy/source/mapnik.py +259 -0
- mapproxy/source/tile.py +96 -0
- mapproxy/source/wms.py +270 -0
- mapproxy/srs.py +726 -0
- mapproxy/template.py +54 -0
- mapproxy/test/__init__.py +0 -0
- mapproxy/test/conftest.py +7 -0
- mapproxy/test/helper.py +247 -0
- mapproxy/test/http.py +494 -0
- mapproxy/test/image.py +210 -0
- mapproxy/test/mocker.py +2268 -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_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 +100 -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 +30 -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 +1134 -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_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 +106 -0
- mapproxy/test/system/test_demo_with_extra_service.py +53 -0
- mapproxy/test/system/test_dimensions.py +278 -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 +262 -0
- mapproxy/test/system/test_layergroups.py +160 -0
- mapproxy/test/system/test_legendgraphic.py +308 -0
- mapproxy/test/system/test_mapnik.py +161 -0
- mapproxy/test/system/test_mapserver.py +81 -0
- mapproxy/test/system/test_mixed_mode_format.py +195 -0
- mapproxy/test/system/test_multi_cache_layers.py +167 -0
- mapproxy/test/system/test_multiapp.py +92 -0
- mapproxy/test/system/test_refresh.py +207 -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 +422 -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 +276 -0
- mapproxy/test/system/test_tms_origin.py +46 -0
- mapproxy/test/system/test_util_conf.py +304 -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 +1611 -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 +425 -0
- mapproxy/test/test_http_helper.py +219 -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 +245 -0
- mapproxy/test/unit/test_auth.py +419 -0
- mapproxy/test/unit/test_cache.py +1193 -0
- mapproxy/test/unit/test_cache_azureblob.py +94 -0
- mapproxy/test/unit/test_cache_compact.py +319 -0
- mapproxy/test/unit/test_cache_couchdb.py +114 -0
- mapproxy/test/unit/test_cache_geopackage.py +221 -0
- mapproxy/test/unit/test_cache_redis.py +67 -0
- mapproxy/test/unit/test_cache_riak.py +76 -0
- mapproxy/test/unit/test_cache_s3.py +84 -0
- mapproxy/test/unit/test_cache_tile.py +427 -0
- mapproxy/test/unit/test_client.py +479 -0
- mapproxy/test/unit/test_client_arcgis.py +73 -0
- mapproxy/test/unit/test_client_cgi.py +136 -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 +1061 -0
- mapproxy/test/unit/test_conf_validator.py +416 -0
- mapproxy/test/unit/test_config.py +117 -0
- mapproxy/test/unit/test_decorate_img.py +185 -0
- mapproxy/test/unit/test_exceptions.py +258 -0
- mapproxy/test/unit/test_featureinfo.py +291 -0
- mapproxy/test/unit/test_file_lock_load.py +49 -0
- mapproxy/test/unit/test_geom.py +503 -0
- mapproxy/test/unit/test_grid.py +1258 -0
- mapproxy/test/unit/test_image.py +1053 -0
- mapproxy/test/unit/test_image_mask.py +181 -0
- mapproxy/test/unit/test_image_messages.py +197 -0
- mapproxy/test/unit/test_image_options.py +160 -0
- mapproxy/test/unit/test_isodate.py +122 -0
- mapproxy/test/unit/test_multiapp.py +163 -0
- mapproxy/test/unit/test_ogr_reader.py +50 -0
- mapproxy/test/unit/test_request.py +745 -0
- mapproxy/test/unit/test_request_wmts.py +178 -0
- mapproxy/test/unit/test_response.py +79 -0
- mapproxy/test/unit/test_seed.py +365 -0
- mapproxy/test/unit/test_seed_cachelock.py +90 -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 +69 -0
- mapproxy/tilefilter.py +59 -0
- mapproxy/util/__init__.py +0 -0
- mapproxy/util/async_.py +227 -0
- mapproxy/util/collections.py +132 -0
- mapproxy/util/coverage.py +329 -0
- mapproxy/util/escape.py +10 -0
- mapproxy/util/ext/__init__.py +14 -0
- mapproxy/util/ext/dictspec/__init__.py +1 -0
- mapproxy/util/ext/dictspec/spec.py +124 -0
- mapproxy/util/ext/dictspec/test/__init__.py +0 -0
- mapproxy/util/ext/dictspec/test/test_validator.py +274 -0
- mapproxy/util/ext/dictspec/validator.py +189 -0
- mapproxy/util/ext/local.py +196 -0
- mapproxy/util/ext/lockfile.py +138 -0
- mapproxy/util/ext/odict.py +330 -0
- mapproxy/util/ext/serving.py +508 -0
- mapproxy/util/ext/tempita/__init__.py +1174 -0
- mapproxy/util/ext/tempita/_looper.py +163 -0
- mapproxy/util/ext/tempita/compat3.py +46 -0
- mapproxy/util/ext/wmsparse/__init__.py +3 -0
- mapproxy/util/ext/wmsparse/duration.py +597 -0
- mapproxy/util/ext/wmsparse/parse.py +305 -0
- mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
- mapproxy/util/ext/wmsparse/test/test_parse.py +162 -0
- mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
- mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
- mapproxy/util/ext/wmsparse/test/wms-omniscale-111.xml +90 -0
- mapproxy/util/ext/wmsparse/test/wms-omniscale-130.xml +120 -0
- mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
- mapproxy/util/ext/wmsparse/util.py +187 -0
- mapproxy/util/fs.py +156 -0
- mapproxy/util/geom.py +295 -0
- mapproxy/util/lib.py +115 -0
- mapproxy/util/lock.py +163 -0
- mapproxy/util/ogr.py +231 -0
- mapproxy/util/py.py +81 -0
- mapproxy/util/times.py +75 -0
- mapproxy/util/yaml.py +56 -0
- mapproxy/version.py +31 -0
- mapproxy/wsgiapp.py +164 -0
- mapproxy-1.16.1.dist-info/METADATA +151 -0
- mapproxy-1.16.1.dist-info/RECORD +458 -0
- mapproxy-1.16.1.dist-info/WHEEL +5 -0
- mapproxy-1.16.1.dist-info/entry_points.txt +3 -0
- mapproxy-1.16.1.dist-info/licenses/AUTHORS.txt +33 -0
- mapproxy-1.16.1.dist-info/licenses/COPYING.txt +60 -0
- mapproxy-1.16.1.dist-info/licenses/LICENSE.txt +202 -0
- mapproxy-1.16.1.dist-info/top_level.txt +1 -0
mapproxy/service/wms.py
ADDED
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2010-2014 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
|
+
WMS service handler
|
|
18
|
+
"""
|
|
19
|
+
from mapproxy.compat import iteritems
|
|
20
|
+
from mapproxy.compat.itertools import chain
|
|
21
|
+
from functools import partial
|
|
22
|
+
from math import sqrt
|
|
23
|
+
from mapproxy.cache.tile import CacheInfo
|
|
24
|
+
from mapproxy.featureinfo import combine_docs
|
|
25
|
+
from mapproxy.request.wms import (wms_request, WMS111LegendGraphicRequest,
|
|
26
|
+
mimetype_from_infotype, infotype_from_mimetype, switch_bbox_epsg_axis_order)
|
|
27
|
+
from mapproxy.srs import SRS, TransformationError
|
|
28
|
+
from mapproxy.service.base import Server
|
|
29
|
+
from mapproxy.response import Response
|
|
30
|
+
from mapproxy.source import SourceError
|
|
31
|
+
from mapproxy.exception import RequestError
|
|
32
|
+
from mapproxy.image import bbox_position_in_image, SubImageSource, BlankImageSource, GeoReference
|
|
33
|
+
from mapproxy.image.merge import concat_legends, LayerMerger
|
|
34
|
+
from mapproxy.image.opts import ImageOptions
|
|
35
|
+
from mapproxy.image.message import attribution_image, message_image
|
|
36
|
+
from mapproxy.layer import BlankImage, MapQuery, InfoQuery, LegendQuery, MapError, LimitedLayer
|
|
37
|
+
from mapproxy.layer import MapBBOXError, merge_layer_extents, merge_layer_res_ranges
|
|
38
|
+
from mapproxy.util import async_
|
|
39
|
+
from mapproxy.util.py import cached_property, reraise
|
|
40
|
+
from mapproxy.util.coverage import load_limited_to
|
|
41
|
+
from mapproxy.util.ext.odict import odict
|
|
42
|
+
from mapproxy.template import template_loader, bunch, recursive_bunch
|
|
43
|
+
from mapproxy.service import template_helper
|
|
44
|
+
from mapproxy.layer import DefaultMapExtent, MapExtent
|
|
45
|
+
|
|
46
|
+
get_template = template_loader(__name__, 'templates', namespace=template_helper.__dict__)
|
|
47
|
+
|
|
48
|
+
class PERMIT_ALL_LAYERS(object):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
class WMSServer(Server):
|
|
52
|
+
service = 'wms'
|
|
53
|
+
fi_transformers = None
|
|
54
|
+
|
|
55
|
+
def __init__(self, root_layer, md, srs, image_formats,
|
|
56
|
+
request_parser=None, tile_layers=None, attribution=None,
|
|
57
|
+
info_types=None, strict=False, on_error='raise',
|
|
58
|
+
concurrent_layer_renderer=1, max_output_pixels=None,
|
|
59
|
+
srs_extents=None, max_tile_age=None,
|
|
60
|
+
versions=None,
|
|
61
|
+
inspire_md=None,
|
|
62
|
+
):
|
|
63
|
+
Server.__init__(self)
|
|
64
|
+
self.request_parser = request_parser or partial(wms_request, strict=strict, versions=versions)
|
|
65
|
+
self.root_layer = root_layer
|
|
66
|
+
self.layers = root_layer.child_layers()
|
|
67
|
+
self.tile_layers = tile_layers or {}
|
|
68
|
+
self.strict = strict
|
|
69
|
+
self.attribution = attribution
|
|
70
|
+
self.md = md
|
|
71
|
+
self.on_error = on_error
|
|
72
|
+
self.concurrent_layer_renderer = concurrent_layer_renderer
|
|
73
|
+
self.image_formats = image_formats
|
|
74
|
+
self.info_types = info_types
|
|
75
|
+
self.srs = srs
|
|
76
|
+
self.srs_extents = srs_extents
|
|
77
|
+
self.max_output_pixels = max_output_pixels
|
|
78
|
+
self.max_tile_age = max_tile_age
|
|
79
|
+
self.inspire_md = inspire_md
|
|
80
|
+
|
|
81
|
+
def map(self, map_request):
|
|
82
|
+
self.check_map_request(map_request)
|
|
83
|
+
|
|
84
|
+
params = map_request.params
|
|
85
|
+
query = MapQuery(params.bbox, params.size, SRS(params.srs), params.format, dimensions=map_request.dimensions)
|
|
86
|
+
|
|
87
|
+
if map_request.params.get('tiled', 'false').lower() == 'true':
|
|
88
|
+
query.tiled_only = True
|
|
89
|
+
orig_query = query
|
|
90
|
+
|
|
91
|
+
if self.srs_extents and params.srs in self.srs_extents:
|
|
92
|
+
# limit query to srs_extent if query is larger
|
|
93
|
+
query_extent = MapExtent(params.bbox, SRS(params.srs))
|
|
94
|
+
if not self.srs_extents[params.srs].contains(query_extent):
|
|
95
|
+
limited_extent = self.srs_extents[params.srs].intersection(query_extent)
|
|
96
|
+
if not limited_extent:
|
|
97
|
+
img_opts = self.image_formats[params.format_mime_type].copy()
|
|
98
|
+
img_opts.bgcolor = params.bgcolor
|
|
99
|
+
img_opts.transparent = params.transparent
|
|
100
|
+
img = BlankImageSource(size=params.size, image_opts=img_opts, cacheable=True)
|
|
101
|
+
return Response(img.as_buffer(), content_type=img_opts.format.mime_type)
|
|
102
|
+
sub_size, offset, sub_bbox = bbox_position_in_image(params.bbox, params.size, limited_extent.bbox)
|
|
103
|
+
query = MapQuery(sub_bbox, sub_size, SRS(params.srs), params.format)
|
|
104
|
+
|
|
105
|
+
actual_layers = odict()
|
|
106
|
+
for layer_name in map_request.params.layers:
|
|
107
|
+
layer = self.layers[layer_name]
|
|
108
|
+
# only add if layer renders the query
|
|
109
|
+
if layer.renders_query(query):
|
|
110
|
+
# if layer is not transparent and will be rendered,
|
|
111
|
+
# remove already added (then hidden) layers
|
|
112
|
+
if layer.is_opaque(query):
|
|
113
|
+
actual_layers = odict()
|
|
114
|
+
for layer_name, map_layers in layer.map_layers_for_query(query):
|
|
115
|
+
actual_layers[layer_name] = map_layers
|
|
116
|
+
|
|
117
|
+
authorized_layers, coverage = self.authorized_layers('map', actual_layers.keys(),
|
|
118
|
+
map_request.http.environ, query_extent=(query.srs.srs_code, query.bbox))
|
|
119
|
+
|
|
120
|
+
self.filter_actual_layers(actual_layers, map_request.params.layers, authorized_layers)
|
|
121
|
+
|
|
122
|
+
render_layers = []
|
|
123
|
+
for layers in actual_layers.values():
|
|
124
|
+
render_layers.extend(layers)
|
|
125
|
+
|
|
126
|
+
self.update_query_with_fwd_params(query, params=params,
|
|
127
|
+
layers=render_layers)
|
|
128
|
+
|
|
129
|
+
raise_source_errors = True if self.on_error == 'raise' else False
|
|
130
|
+
renderer = LayerRenderer(render_layers, query, map_request,
|
|
131
|
+
raise_source_errors=raise_source_errors,
|
|
132
|
+
concurrent_rendering=self.concurrent_layer_renderer)
|
|
133
|
+
|
|
134
|
+
merger = LayerMerger()
|
|
135
|
+
renderer.render(merger)
|
|
136
|
+
|
|
137
|
+
if self.attribution and self.attribution.get('text') and not query.tiled_only:
|
|
138
|
+
merger.add(attribution_image(self.attribution['text'], query.size))
|
|
139
|
+
img_opts = self.image_formats[params.format_mime_type].copy()
|
|
140
|
+
img_opts.bgcolor = params.bgcolor
|
|
141
|
+
img_opts.transparent = params.transparent
|
|
142
|
+
result = merger.merge(size=query.size, image_opts=img_opts,
|
|
143
|
+
bbox=query.bbox, bbox_srs=params.srs, coverage=coverage)
|
|
144
|
+
|
|
145
|
+
if query != orig_query:
|
|
146
|
+
result = SubImageSource(result, size=orig_query.size, offset=offset, image_opts=img_opts)
|
|
147
|
+
|
|
148
|
+
# Provide the wrapping WSGI app or filter the opportunity to process the
|
|
149
|
+
# image before it's wrapped up in a response
|
|
150
|
+
result = self.decorate_img(result, 'wms.map', actual_layers.keys(),
|
|
151
|
+
map_request.http.environ, (query.srs.srs_code, query.bbox))
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
result.georef = GeoReference(bbox=orig_query.bbox, srs=orig_query.srs)
|
|
155
|
+
result_buf = result.as_buffer(img_opts)
|
|
156
|
+
except IOError as ex:
|
|
157
|
+
raise RequestError('error while processing image file: %s' % ex,
|
|
158
|
+
request=map_request)
|
|
159
|
+
|
|
160
|
+
resp = Response(result_buf, content_type=img_opts.format.mime_type)
|
|
161
|
+
|
|
162
|
+
if query.tiled_only and isinstance(result.cacheable, CacheInfo):
|
|
163
|
+
cache_info = result.cacheable
|
|
164
|
+
resp.cache_headers(cache_info.timestamp, etag_data=(cache_info.timestamp, cache_info.size),
|
|
165
|
+
max_age=self.max_tile_age)
|
|
166
|
+
resp.make_conditional(map_request.http)
|
|
167
|
+
|
|
168
|
+
if not result.cacheable:
|
|
169
|
+
resp.cache_headers(no_cache=True)
|
|
170
|
+
|
|
171
|
+
return resp
|
|
172
|
+
|
|
173
|
+
def capabilities(self, map_request):
|
|
174
|
+
# TODO: debug layer
|
|
175
|
+
# if '__debug__' in map_request.params:
|
|
176
|
+
# layers = self.layers.values()
|
|
177
|
+
# else:
|
|
178
|
+
# layers = [layer for name, layer in iteritems(self.layers)
|
|
179
|
+
# if name != '__debug__']
|
|
180
|
+
|
|
181
|
+
if map_request.params.get('tiled', 'false').lower() == 'true':
|
|
182
|
+
tile_layers = self.tile_layers.values()
|
|
183
|
+
else:
|
|
184
|
+
tile_layers = []
|
|
185
|
+
|
|
186
|
+
service = self._service_md(map_request)
|
|
187
|
+
root_layer = self.authorized_capability_layers(map_request.http.environ)
|
|
188
|
+
|
|
189
|
+
info_types = ['text', 'html', 'xml'] # defaults
|
|
190
|
+
if self.info_types:
|
|
191
|
+
info_types = self.info_types
|
|
192
|
+
elif self.fi_transformers:
|
|
193
|
+
info_types = self.fi_transformers.keys()
|
|
194
|
+
info_formats = [mimetype_from_infotype(map_request.version, info_type) for info_type in info_types]
|
|
195
|
+
result = Capabilities(service, root_layer, tile_layers,
|
|
196
|
+
self.image_formats, info_formats, srs=self.srs, srs_extents=self.srs_extents,
|
|
197
|
+
inspire_md=self.inspire_md, max_output_pixels=self.max_output_pixels
|
|
198
|
+
).render(map_request)
|
|
199
|
+
return Response(result, mimetype=map_request.mime_type)
|
|
200
|
+
|
|
201
|
+
def featureinfo(self, request):
|
|
202
|
+
infos = []
|
|
203
|
+
self.check_featureinfo_request(request)
|
|
204
|
+
|
|
205
|
+
p = request.params
|
|
206
|
+
query = InfoQuery(p.bbox, p.size, SRS(p.srs), p.pos,
|
|
207
|
+
p['info_format'], format=request.params.format or None,
|
|
208
|
+
feature_count=p.get('feature_count'))
|
|
209
|
+
|
|
210
|
+
actual_layers = odict()
|
|
211
|
+
|
|
212
|
+
for layer_name in request.params.query_layers:
|
|
213
|
+
layer = self.layers[layer_name]
|
|
214
|
+
if not layer.queryable:
|
|
215
|
+
raise RequestError('layer %s is not queryable' % layer_name, request=request)
|
|
216
|
+
for layer_name, info_layers in layer.info_layers_for_query(query):
|
|
217
|
+
actual_layers[layer_name] = info_layers
|
|
218
|
+
|
|
219
|
+
authorized_layers, coverage = self.authorized_layers('featureinfo', actual_layers.keys(),
|
|
220
|
+
request.http.environ, query_extent=(query.srs.srs_code, query.bbox))
|
|
221
|
+
self.filter_actual_layers(actual_layers, request.params.layers, authorized_layers)
|
|
222
|
+
|
|
223
|
+
# outside of auth-coverage
|
|
224
|
+
if coverage and not coverage.contains(query.coord, query.srs):
|
|
225
|
+
infos = []
|
|
226
|
+
else:
|
|
227
|
+
info_layers = []
|
|
228
|
+
for layers in actual_layers.values():
|
|
229
|
+
info_layers.extend(layers)
|
|
230
|
+
|
|
231
|
+
for layer in info_layers:
|
|
232
|
+
info = layer.get_info(query)
|
|
233
|
+
if info is None:
|
|
234
|
+
continue
|
|
235
|
+
infos.append(info)
|
|
236
|
+
|
|
237
|
+
mimetype = None
|
|
238
|
+
if 'info_format' in request.params:
|
|
239
|
+
mimetype = request.params.info_format
|
|
240
|
+
|
|
241
|
+
if not infos:
|
|
242
|
+
return Response('', mimetype=mimetype)
|
|
243
|
+
|
|
244
|
+
if self.fi_transformers:
|
|
245
|
+
if not mimetype:
|
|
246
|
+
if 'xml' in self.fi_transformers:
|
|
247
|
+
info_type = 'xml'
|
|
248
|
+
elif 'html' in self.fi_transformers:
|
|
249
|
+
info_type = 'html'
|
|
250
|
+
else:
|
|
251
|
+
info_type = 'text'
|
|
252
|
+
mimetype = mimetype_from_infotype(request.version, info_type)
|
|
253
|
+
else:
|
|
254
|
+
info_type = infotype_from_mimetype(request.version, mimetype)
|
|
255
|
+
resp, actual_info_type = combine_docs(infos, self.fi_transformers[info_type])
|
|
256
|
+
if actual_info_type is not None and info_type != actual_info_type:
|
|
257
|
+
mimetype = mimetype_from_infotype(request.version, actual_info_type)
|
|
258
|
+
else:
|
|
259
|
+
resp, info_type = combine_docs(infos)
|
|
260
|
+
mimetype = mimetype_from_infotype(request.version, info_type)
|
|
261
|
+
|
|
262
|
+
return Response(resp, mimetype=mimetype)
|
|
263
|
+
|
|
264
|
+
def check_map_request(self, request):
|
|
265
|
+
if self.max_output_pixels and \
|
|
266
|
+
(request.params.size[0] * request.params.size[1]) > self.max_output_pixels:
|
|
267
|
+
request.prevent_image_exception = True
|
|
268
|
+
raise RequestError("image size too large", request=request)
|
|
269
|
+
|
|
270
|
+
self.validate_layers(request)
|
|
271
|
+
request.validate_format(self.image_formats)
|
|
272
|
+
request.validate_srs(self.srs)
|
|
273
|
+
|
|
274
|
+
def update_query_with_fwd_params(self, query, params, layers):
|
|
275
|
+
# forward relevant request params into MapQuery.dimensions
|
|
276
|
+
for layer in layers:
|
|
277
|
+
if not hasattr(layer, 'fwd_req_params'):
|
|
278
|
+
continue
|
|
279
|
+
for p in layer.fwd_req_params:
|
|
280
|
+
if p in params:
|
|
281
|
+
query.dimensions[p] = params[p]
|
|
282
|
+
|
|
283
|
+
def check_featureinfo_request(self, request):
|
|
284
|
+
self.validate_layers(request)
|
|
285
|
+
request.validate_srs(self.srs)
|
|
286
|
+
|
|
287
|
+
def validate_layers(self, request):
|
|
288
|
+
query_layers = request.params.query_layers if hasattr(request, 'query_layers') else []
|
|
289
|
+
for layer in chain(request.params.layers, query_layers):
|
|
290
|
+
if layer not in self.layers:
|
|
291
|
+
raise RequestError('unknown layer: ' + str(layer), code='LayerNotDefined',
|
|
292
|
+
request=request)
|
|
293
|
+
|
|
294
|
+
def check_legend_request(self, request):
|
|
295
|
+
if request.params.layer not in self.layers:
|
|
296
|
+
raise RequestError('unknown layer: ' + request.params.layer,
|
|
297
|
+
code='LayerNotDefined', request=request)
|
|
298
|
+
|
|
299
|
+
#TODO: If layer not in self.layers raise RequestError
|
|
300
|
+
def legendgraphic(self, request):
|
|
301
|
+
legends = []
|
|
302
|
+
self.check_legend_request(request)
|
|
303
|
+
layer = request.params.layer
|
|
304
|
+
if not self.layers[layer].has_legend:
|
|
305
|
+
raise RequestError('layer %s has no legend graphic' % layer, request=request)
|
|
306
|
+
legend = self.layers[layer].legend(request)
|
|
307
|
+
|
|
308
|
+
[legends.append(i) for i in legend if i is not None]
|
|
309
|
+
if 'format' in request.params:
|
|
310
|
+
mimetype = request.params.format_mime_type
|
|
311
|
+
else:
|
|
312
|
+
mimetype = 'image/png'
|
|
313
|
+
|
|
314
|
+
if mimetype == 'application/json':
|
|
315
|
+
return Response(legends[0].encode(), mimetype='application/json')
|
|
316
|
+
|
|
317
|
+
result = concat_legends(legends)
|
|
318
|
+
|
|
319
|
+
img_opts = self.image_formats[request.params.format_mime_type]
|
|
320
|
+
return Response(result.as_buffer(img_opts), mimetype=mimetype)
|
|
321
|
+
|
|
322
|
+
def _service_md(self, map_request):
|
|
323
|
+
md = dict(self.md)
|
|
324
|
+
md['url'] = map_request.url
|
|
325
|
+
md['has_legend'] = self.root_layer.has_legend
|
|
326
|
+
return md
|
|
327
|
+
|
|
328
|
+
def authorized_layers(self, feature, layers, env, query_extent):
|
|
329
|
+
if 'mapproxy.authorize' in env:
|
|
330
|
+
result = env['mapproxy.authorize']('wms.' + feature, layers[:],
|
|
331
|
+
environ=env, query_extent=query_extent)
|
|
332
|
+
if result['authorized'] == 'unauthenticated':
|
|
333
|
+
raise RequestError('unauthorized', status=401)
|
|
334
|
+
if result['authorized'] == 'full':
|
|
335
|
+
return PERMIT_ALL_LAYERS, None
|
|
336
|
+
layers = {}
|
|
337
|
+
if result['authorized'] == 'partial':
|
|
338
|
+
for layer_name, permissions in iteritems(result['layers']):
|
|
339
|
+
if permissions.get(feature, False) == True:
|
|
340
|
+
layers[layer_name] = permissions.get('limited_to')
|
|
341
|
+
limited_to = result.get('limited_to')
|
|
342
|
+
if limited_to:
|
|
343
|
+
coverage = load_limited_to(limited_to)
|
|
344
|
+
else:
|
|
345
|
+
coverage = None
|
|
346
|
+
return layers, coverage
|
|
347
|
+
else:
|
|
348
|
+
return PERMIT_ALL_LAYERS, None
|
|
349
|
+
|
|
350
|
+
def filter_actual_layers(self, actual_layers, requested_layers, authorized_layers):
|
|
351
|
+
if authorized_layers is not PERMIT_ALL_LAYERS:
|
|
352
|
+
requested_layer_names = set(requested_layers)
|
|
353
|
+
for layer_name in actual_layers.keys():
|
|
354
|
+
if layer_name not in authorized_layers:
|
|
355
|
+
# check whether layer was requested explicit...
|
|
356
|
+
if layer_name in requested_layer_names:
|
|
357
|
+
raise RequestError('forbidden', status=403)
|
|
358
|
+
# or implicit (part of group layer)
|
|
359
|
+
else:
|
|
360
|
+
del actual_layers[layer_name]
|
|
361
|
+
elif authorized_layers[layer_name] is not None:
|
|
362
|
+
limited_to = load_limited_to(authorized_layers[layer_name])
|
|
363
|
+
actual_layers[layer_name] = [LimitedLayer(lyr, limited_to) for lyr in actual_layers[layer_name]]
|
|
364
|
+
|
|
365
|
+
def authorized_capability_layers(self, env):
|
|
366
|
+
if 'mapproxy.authorize' in env:
|
|
367
|
+
result = env['mapproxy.authorize']('wms.capabilities', self.layers.keys(), environ=env)
|
|
368
|
+
if result['authorized'] == 'unauthenticated':
|
|
369
|
+
raise RequestError('unauthorized', status=401)
|
|
370
|
+
if result['authorized'] == 'full':
|
|
371
|
+
return self.root_layer
|
|
372
|
+
if result['authorized'] == 'partial':
|
|
373
|
+
limited_to = result.get('limited_to')
|
|
374
|
+
if limited_to:
|
|
375
|
+
coverage = load_limited_to(limited_to)
|
|
376
|
+
else:
|
|
377
|
+
coverage = None
|
|
378
|
+
return FilteredRootLayer(self.root_layer, result['layers'], coverage=coverage)
|
|
379
|
+
raise RequestError('forbidden', status=403)
|
|
380
|
+
else:
|
|
381
|
+
return self.root_layer
|
|
382
|
+
|
|
383
|
+
class FilteredRootLayer(object):
|
|
384
|
+
def __init__(self, root_layer, permissions, coverage=None):
|
|
385
|
+
self.root_layer = root_layer
|
|
386
|
+
self.permissions = permissions
|
|
387
|
+
self.coverage = coverage
|
|
388
|
+
|
|
389
|
+
def __getattr__(self, name):
|
|
390
|
+
return getattr(self.root_layer, name)
|
|
391
|
+
|
|
392
|
+
@cached_property
|
|
393
|
+
def extent(self):
|
|
394
|
+
layer_name = self.root_layer.name
|
|
395
|
+
limited_to = self.permissions.get(layer_name, {}).get('limited_to')
|
|
396
|
+
extent = self.root_layer.extent
|
|
397
|
+
|
|
398
|
+
if limited_to:
|
|
399
|
+
coverage = load_limited_to(limited_to)
|
|
400
|
+
limited_coverage = coverage.intersection(extent.bbox, extent.srs)
|
|
401
|
+
extent = limited_coverage.extent
|
|
402
|
+
|
|
403
|
+
if self.coverage:
|
|
404
|
+
limited_coverage = self.coverage.intersection(extent.bbox, extent.srs)
|
|
405
|
+
extent = limited_coverage.extent
|
|
406
|
+
return extent
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def queryable(self):
|
|
410
|
+
if not self.root_layer.queryable: return False
|
|
411
|
+
|
|
412
|
+
layer_name = self.root_layer.name
|
|
413
|
+
if not layer_name or self.permissions.get(layer_name, {}).get('featureinfo', False):
|
|
414
|
+
return True
|
|
415
|
+
return False
|
|
416
|
+
|
|
417
|
+
def layer_permitted(self, layer):
|
|
418
|
+
if not self.permissions.get(layer.name, {}).get('map', False):
|
|
419
|
+
return False
|
|
420
|
+
extent = layer.extent
|
|
421
|
+
|
|
422
|
+
limited_to = self.permissions.get(layer.name, {}).get('limited_to')
|
|
423
|
+
if limited_to:
|
|
424
|
+
coverage = load_limited_to(limited_to)
|
|
425
|
+
if not coverage.intersects(extent.bbox, extent.srs):
|
|
426
|
+
return False
|
|
427
|
+
|
|
428
|
+
if self.coverage:
|
|
429
|
+
if not self.coverage.intersects(extent.bbox, extent.srs):
|
|
430
|
+
return False
|
|
431
|
+
return True
|
|
432
|
+
|
|
433
|
+
@cached_property
|
|
434
|
+
def layers(self):
|
|
435
|
+
layers = []
|
|
436
|
+
for layer in self.root_layer.layers:
|
|
437
|
+
if not layer.name or self.layer_permitted(layer):
|
|
438
|
+
filtered_layer = FilteredRootLayer(layer, self.permissions, self.coverage)
|
|
439
|
+
if filtered_layer.is_active or filtered_layer.layers:
|
|
440
|
+
# add filtered_layer only if it is active (no grouping layer)
|
|
441
|
+
# or if it contains other active layers
|
|
442
|
+
layers.append(filtered_layer)
|
|
443
|
+
return layers
|
|
444
|
+
|
|
445
|
+
DEFAULT_EXTENTS = {
|
|
446
|
+
'EPSG:3857': DefaultMapExtent(),
|
|
447
|
+
'EPSG:4326': DefaultMapExtent(),
|
|
448
|
+
'EPSG:900913': DefaultMapExtent(),
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
def limit_srs_extents(srs_extents, supported_srs):
|
|
452
|
+
"""
|
|
453
|
+
Limit srs_extents to supported_srs.
|
|
454
|
+
"""
|
|
455
|
+
if srs_extents:
|
|
456
|
+
srs_extents = srs_extents.copy()
|
|
457
|
+
else:
|
|
458
|
+
srs_extents = DEFAULT_EXTENTS.copy()
|
|
459
|
+
|
|
460
|
+
for srs in list(srs_extents.keys()):
|
|
461
|
+
if srs not in supported_srs:
|
|
462
|
+
srs_extents.pop(srs)
|
|
463
|
+
|
|
464
|
+
return srs_extents
|
|
465
|
+
|
|
466
|
+
class Capabilities(object):
|
|
467
|
+
"""
|
|
468
|
+
Renders WMS capabilities documents.
|
|
469
|
+
"""
|
|
470
|
+
def __init__(self, server_md, layers, tile_layers, image_formats, info_formats,
|
|
471
|
+
srs, srs_extents=None, epsg_axis_order=False,
|
|
472
|
+
inspire_md=None, max_output_pixels=None
|
|
473
|
+
):
|
|
474
|
+
self.service = server_md
|
|
475
|
+
self.layers = layers
|
|
476
|
+
self.tile_layers = tile_layers
|
|
477
|
+
self.image_formats = image_formats
|
|
478
|
+
self.info_formats = info_formats
|
|
479
|
+
self.srs = srs
|
|
480
|
+
self.srs_extents = limit_srs_extents(srs_extents, srs)
|
|
481
|
+
self.inspire_md = inspire_md
|
|
482
|
+
self.max_output_pixels = max_output_pixels
|
|
483
|
+
|
|
484
|
+
def layer_srs_bbox(self, layer, epsg_axis_order=False):
|
|
485
|
+
for srs, extent in iteritems(self.srs_extents):
|
|
486
|
+
if srs not in self.srs:
|
|
487
|
+
continue
|
|
488
|
+
|
|
489
|
+
# is_default is True when no explicit bbox is defined for this srs
|
|
490
|
+
# use layer extent
|
|
491
|
+
if extent.is_default:
|
|
492
|
+
bbox = layer.extent.bbox_for(SRS(srs))
|
|
493
|
+
elif layer.extent.is_default:
|
|
494
|
+
bbox = extent.bbox_for(SRS(srs))
|
|
495
|
+
else:
|
|
496
|
+
# Use intersection of srs_extent and layer.extent.
|
|
497
|
+
# Use 4326 extents to avoid transformation errors.
|
|
498
|
+
a = extent.transform(SRS(4326))
|
|
499
|
+
b = layer.extent.transform(SRS(4326))
|
|
500
|
+
bbox = a.intersection(b).bbox_for(SRS(srs))
|
|
501
|
+
|
|
502
|
+
if epsg_axis_order:
|
|
503
|
+
bbox = switch_bbox_epsg_axis_order(bbox, srs)
|
|
504
|
+
|
|
505
|
+
yield srs, bbox
|
|
506
|
+
|
|
507
|
+
# add native srs
|
|
508
|
+
layer_srs_code = layer.extent.srs.srs_code
|
|
509
|
+
if layer_srs_code not in self.srs_extents:
|
|
510
|
+
bbox = layer.extent.bbox
|
|
511
|
+
if epsg_axis_order:
|
|
512
|
+
bbox = switch_bbox_epsg_axis_order(bbox, layer_srs_code)
|
|
513
|
+
if layer_srs_code in self.srs:
|
|
514
|
+
yield layer_srs_code, bbox
|
|
515
|
+
|
|
516
|
+
def layer_llbbox(self, layer):
|
|
517
|
+
if 'EPSG:4326' in self.srs_extents:
|
|
518
|
+
intersection = self.srs_extents['EPSG:4326'].intersection(layer.extent)
|
|
519
|
+
if intersection is not None:
|
|
520
|
+
llbbox = intersection.llbbox
|
|
521
|
+
return limit_llbbox(llbbox)
|
|
522
|
+
return limit_llbbox(layer.extent.llbbox)
|
|
523
|
+
|
|
524
|
+
def render(self, _map_request):
|
|
525
|
+
return self._render_template(_map_request.capabilities_template)
|
|
526
|
+
|
|
527
|
+
def _render_template(self, template):
|
|
528
|
+
template = get_template(template)
|
|
529
|
+
inspire_md = None
|
|
530
|
+
if self.inspire_md:
|
|
531
|
+
inspire_md = recursive_bunch(default='', **self.inspire_md)
|
|
532
|
+
|
|
533
|
+
max_output_size = None
|
|
534
|
+
if self.max_output_pixels:
|
|
535
|
+
output_width = output_height = int(sqrt(self.max_output_pixels))
|
|
536
|
+
max_output_size = (output_width, output_height)
|
|
537
|
+
|
|
538
|
+
doc = template.substitute(service=bunch(default='', **self.service),
|
|
539
|
+
layers=self.layers,
|
|
540
|
+
formats=self.image_formats,
|
|
541
|
+
info_formats=self.info_formats,
|
|
542
|
+
srs=self.srs,
|
|
543
|
+
tile_layers=self.tile_layers,
|
|
544
|
+
layer_srs_bbox=self.layer_srs_bbox,
|
|
545
|
+
layer_llbbox=self.layer_llbbox,
|
|
546
|
+
inspire_md=inspire_md,
|
|
547
|
+
max_output_size=max_output_size,
|
|
548
|
+
)
|
|
549
|
+
# strip blank lines
|
|
550
|
+
doc = '\n'.join(l for l in doc.split('\n') if l.rstrip())
|
|
551
|
+
return doc
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def limit_llbbox(bbox):
|
|
555
|
+
"""
|
|
556
|
+
Limit the long/lat bounding box to +-180/89.99999999 degrees.
|
|
557
|
+
|
|
558
|
+
Some clients can't handle +-90 north/south, so we subtract a tiny bit.
|
|
559
|
+
|
|
560
|
+
>>> ', '.join('%.6f' % x for x in limit_llbbox((-200,-90.0, 180, 90)))
|
|
561
|
+
'-180.000000, -89.999999, 180.000000, 89.999999'
|
|
562
|
+
>>> ', '.join('%.6f' % x for x in limit_llbbox((-20,-9.0, 10, 10)))
|
|
563
|
+
'-20.000000, -9.000000, 10.000000, 10.000000'
|
|
564
|
+
"""
|
|
565
|
+
minx, miny, maxx, maxy = bbox
|
|
566
|
+
|
|
567
|
+
minx = max(-180, minx)
|
|
568
|
+
miny = max(-89.999999, miny)
|
|
569
|
+
maxx = min(180, maxx)
|
|
570
|
+
maxy = min(89.999999, maxy)
|
|
571
|
+
|
|
572
|
+
return minx, miny, maxx, maxy
|
|
573
|
+
|
|
574
|
+
class LayerRenderer(object):
|
|
575
|
+
def __init__(self, layers, query, request, raise_source_errors=True,
|
|
576
|
+
concurrent_rendering=1):
|
|
577
|
+
self.layers = layers
|
|
578
|
+
self.query = query
|
|
579
|
+
self.request = request
|
|
580
|
+
self.raise_source_errors = raise_source_errors
|
|
581
|
+
self.concurrent_rendering = concurrent_rendering
|
|
582
|
+
|
|
583
|
+
def render(self, layer_merger):
|
|
584
|
+
render_layers = combined_layers(self.layers, self.query)
|
|
585
|
+
if not render_layers: return
|
|
586
|
+
|
|
587
|
+
async_pool = async_.Pool(size=min(len(render_layers), self.concurrent_rendering))
|
|
588
|
+
|
|
589
|
+
if self.raise_source_errors:
|
|
590
|
+
return self._render_raise_exceptions(async_pool, render_layers, layer_merger)
|
|
591
|
+
else:
|
|
592
|
+
return self._render_capture_source_errors(async_pool, render_layers,
|
|
593
|
+
layer_merger)
|
|
594
|
+
|
|
595
|
+
def _render_raise_exceptions(self, async_pool, render_layers, layer_merger):
|
|
596
|
+
# call _render_layer, raise all exceptions
|
|
597
|
+
try:
|
|
598
|
+
for layer_task in async_pool.imap(self._render_layer, render_layers,
|
|
599
|
+
use_result_objects=True):
|
|
600
|
+
if layer_task.exception is None:
|
|
601
|
+
layer, layer_img = layer_task.result
|
|
602
|
+
if layer_img is not None:
|
|
603
|
+
layer_merger.add(layer_img, layer.coverage)
|
|
604
|
+
else:
|
|
605
|
+
ex = layer_task.exception
|
|
606
|
+
async_pool.shutdown(True)
|
|
607
|
+
reraise(ex)
|
|
608
|
+
except SourceError as ex:
|
|
609
|
+
raise RequestError(ex.args[0], request=self.request)
|
|
610
|
+
|
|
611
|
+
def _render_capture_source_errors(self, async_pool, render_layers, layer_merger):
|
|
612
|
+
# call _render_layer, capture SourceError exceptions
|
|
613
|
+
errors = []
|
|
614
|
+
rendered = 0
|
|
615
|
+
|
|
616
|
+
for layer_task in async_pool.imap(self._render_layer, render_layers,
|
|
617
|
+
use_result_objects=True):
|
|
618
|
+
if layer_task.exception is None:
|
|
619
|
+
layer, layer_img = layer_task.result
|
|
620
|
+
if layer_img is not None:
|
|
621
|
+
layer_merger.add(layer_img, layer.coverage)
|
|
622
|
+
rendered += 1
|
|
623
|
+
else:
|
|
624
|
+
layer_merger.cacheable = False
|
|
625
|
+
ex = layer_task.exception
|
|
626
|
+
if isinstance(ex[1], SourceError):
|
|
627
|
+
errors.append(ex[1].args[0])
|
|
628
|
+
else:
|
|
629
|
+
async_pool.shutdown(True)
|
|
630
|
+
reraise(ex)
|
|
631
|
+
|
|
632
|
+
if render_layers and not rendered:
|
|
633
|
+
errors = '\n'.join(errors)
|
|
634
|
+
raise RequestError('Could not get any sources:\n'+errors, request=self.request)
|
|
635
|
+
|
|
636
|
+
if errors:
|
|
637
|
+
layer_merger.add(message_image('\n'.join(errors), self.query.size,
|
|
638
|
+
image_opts=ImageOptions(transparent=True)))
|
|
639
|
+
|
|
640
|
+
def _render_layer(self, layer):
|
|
641
|
+
try:
|
|
642
|
+
layer_img = layer.get_map(self.query)
|
|
643
|
+
if layer_img is not None:
|
|
644
|
+
layer_img.opacity = layer.opacity
|
|
645
|
+
|
|
646
|
+
return layer, layer_img
|
|
647
|
+
except SourceError:
|
|
648
|
+
raise
|
|
649
|
+
except MapBBOXError as e:
|
|
650
|
+
raise RequestError('Request too large or invalid BBOX. (%s)' % e, request=self.request)
|
|
651
|
+
except MapError as e:
|
|
652
|
+
raise RequestError('Invalid request: %s' % e.args[0], request=self.request)
|
|
653
|
+
except TransformationError:
|
|
654
|
+
raise RequestError('Could not transform BBOX: Invalid result.',
|
|
655
|
+
request=self.request)
|
|
656
|
+
except BlankImage:
|
|
657
|
+
return layer, None
|
|
658
|
+
|
|
659
|
+
class WMSLayerBase(object):
|
|
660
|
+
"""
|
|
661
|
+
Base class for WMS layer (layer groups and leaf layers).
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
"True if layer is an actual layer (not a group only)"
|
|
665
|
+
is_active = True
|
|
666
|
+
|
|
667
|
+
"list of sublayers"
|
|
668
|
+
layers = []
|
|
669
|
+
|
|
670
|
+
"metadata dictionary with tile, name, etc."
|
|
671
|
+
md = {}
|
|
672
|
+
|
|
673
|
+
"True if .info() is supported"
|
|
674
|
+
queryable = False
|
|
675
|
+
|
|
676
|
+
"True is .legend() is supported"
|
|
677
|
+
has_legend = False
|
|
678
|
+
legend_url = None
|
|
679
|
+
legend_size = None
|
|
680
|
+
|
|
681
|
+
"resolution range (i.e. ScaleHint) of the layer"
|
|
682
|
+
res_range = None
|
|
683
|
+
"MapExtend of the layer"
|
|
684
|
+
extent = None
|
|
685
|
+
|
|
686
|
+
def map_layers_for_query(self, query):
|
|
687
|
+
raise NotImplementedError()
|
|
688
|
+
|
|
689
|
+
def legend(self, query):
|
|
690
|
+
raise NotImplementedError()
|
|
691
|
+
|
|
692
|
+
def info(self, query):
|
|
693
|
+
raise NotImplementedError()
|
|
694
|
+
|
|
695
|
+
class WMSLayer(WMSLayerBase):
|
|
696
|
+
"""
|
|
697
|
+
Class for WMS layers.
|
|
698
|
+
|
|
699
|
+
Combines map, info and legend sources with metadata.
|
|
700
|
+
"""
|
|
701
|
+
is_active = True
|
|
702
|
+
layers = []
|
|
703
|
+
def __init__(self, name, title, map_layers, info_layers=[], legend_layers=[],
|
|
704
|
+
res_range=None, md=None,dimensions=None):
|
|
705
|
+
self.name = name
|
|
706
|
+
self.title = title
|
|
707
|
+
self.md = md or {}
|
|
708
|
+
self.map_layers = map_layers
|
|
709
|
+
self.info_layers = info_layers
|
|
710
|
+
self.legend_layers = legend_layers
|
|
711
|
+
self.extent = merge_layer_extents(map_layers)
|
|
712
|
+
self.dimensions = dimensions
|
|
713
|
+
|
|
714
|
+
if res_range is None:
|
|
715
|
+
res_range = merge_layer_res_ranges(map_layers)
|
|
716
|
+
self.res_range = res_range
|
|
717
|
+
self.queryable = True if info_layers else False
|
|
718
|
+
self.has_legend = True if legend_layers else False
|
|
719
|
+
self.dimensions = dimensions
|
|
720
|
+
|
|
721
|
+
def is_opaque(self, query):
|
|
722
|
+
return any(l.is_opaque(query) for l in self.map_layers)
|
|
723
|
+
|
|
724
|
+
def renders_query(self, query):
|
|
725
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size, query.srs):
|
|
726
|
+
return False
|
|
727
|
+
return True
|
|
728
|
+
|
|
729
|
+
def map_layers_for_query(self, query):
|
|
730
|
+
if not self.map_layers:
|
|
731
|
+
return []
|
|
732
|
+
return [(self.name, self.map_layers)]
|
|
733
|
+
|
|
734
|
+
def info_layers_for_query(self, query):
|
|
735
|
+
if not self.info_layers:
|
|
736
|
+
return []
|
|
737
|
+
return [(self.name, self.info_layers)]
|
|
738
|
+
|
|
739
|
+
def legend(self, request):
|
|
740
|
+
p = request.params
|
|
741
|
+
query = LegendQuery(p.format, p.scale)
|
|
742
|
+
|
|
743
|
+
for lyr in self.legend_layers:
|
|
744
|
+
yield lyr.get_legend(query)
|
|
745
|
+
|
|
746
|
+
@property
|
|
747
|
+
def legend_size(self):
|
|
748
|
+
width = 0
|
|
749
|
+
height = 0
|
|
750
|
+
for layer in self.legend_layers:
|
|
751
|
+
width = max(layer.size[0], width)
|
|
752
|
+
height += layer.size[1]
|
|
753
|
+
return (width, height)
|
|
754
|
+
|
|
755
|
+
@property
|
|
756
|
+
def legend_url(self):
|
|
757
|
+
if self.has_legend:
|
|
758
|
+
req = WMS111LegendGraphicRequest(url='?',
|
|
759
|
+
param=dict(format='image/png', layer=self.name, sld_version='1.1.0'))
|
|
760
|
+
return req.complete_url
|
|
761
|
+
else:
|
|
762
|
+
return None
|
|
763
|
+
|
|
764
|
+
def child_layers(self):
|
|
765
|
+
return {self.name: self}
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
class WMSGroupLayer(WMSLayerBase):
|
|
769
|
+
"""
|
|
770
|
+
Class for WMS group layers.
|
|
771
|
+
|
|
772
|
+
Groups multiple wms layers, but can also contain a single layer (``this``)
|
|
773
|
+
that represents this layer.
|
|
774
|
+
"""
|
|
775
|
+
def __init__(self, name, title, this, layers, md=None):
|
|
776
|
+
self.name = name
|
|
777
|
+
self.title = title
|
|
778
|
+
self.this = this
|
|
779
|
+
self.md = md or {}
|
|
780
|
+
self.is_active = True if this is not None else False
|
|
781
|
+
self.layers = layers
|
|
782
|
+
self.has_legend = True if this and this.has_legend or any(l.has_legend for l in layers) else False
|
|
783
|
+
self.queryable = True if this and this.queryable or any(l.queryable for l in layers) else False
|
|
784
|
+
all_layers = layers + ([self.this] if self.this else [])
|
|
785
|
+
self.extent = merge_layer_extents(all_layers)
|
|
786
|
+
self.res_range = merge_layer_res_ranges(all_layers)
|
|
787
|
+
|
|
788
|
+
def is_opaque(self, query):
|
|
789
|
+
return any(l.is_opaque(query) for l in self.layers)
|
|
790
|
+
|
|
791
|
+
@property
|
|
792
|
+
def legend_size(self):
|
|
793
|
+
return self.this.legend_size
|
|
794
|
+
|
|
795
|
+
@property
|
|
796
|
+
def legend_url(self):
|
|
797
|
+
return self.this.legend_url
|
|
798
|
+
|
|
799
|
+
def renders_query(self, query):
|
|
800
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size, query.srs):
|
|
801
|
+
return False
|
|
802
|
+
return True
|
|
803
|
+
|
|
804
|
+
def map_layers_for_query(self, query):
|
|
805
|
+
if self.this:
|
|
806
|
+
return self.this.map_layers_for_query(query)
|
|
807
|
+
else:
|
|
808
|
+
layers = []
|
|
809
|
+
for layer in self.layers:
|
|
810
|
+
layers.extend(layer.map_layers_for_query(query))
|
|
811
|
+
return layers
|
|
812
|
+
|
|
813
|
+
def info_layers_for_query(self, query):
|
|
814
|
+
if self.this:
|
|
815
|
+
return self.this.info_layers_for_query(query)
|
|
816
|
+
else:
|
|
817
|
+
layers = []
|
|
818
|
+
for layer in self.layers:
|
|
819
|
+
layers.extend(layer.info_layers_for_query(query))
|
|
820
|
+
return layers
|
|
821
|
+
|
|
822
|
+
def child_layers(self):
|
|
823
|
+
layers = odict()
|
|
824
|
+
if self.name:
|
|
825
|
+
layers[self.name] = self
|
|
826
|
+
for lyr in self.layers:
|
|
827
|
+
if hasattr(lyr, 'child_layers'):
|
|
828
|
+
layers.update(lyr.child_layers())
|
|
829
|
+
elif lyr.name:
|
|
830
|
+
layers[lyr.name] = lyr
|
|
831
|
+
return layers
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def combined_layers(layers, query):
|
|
835
|
+
"""
|
|
836
|
+
Returns a new list of the layers where all adjacent layers are combined
|
|
837
|
+
if possible.
|
|
838
|
+
"""
|
|
839
|
+
if len(layers) <= 1:
|
|
840
|
+
return layers
|
|
841
|
+
layers = layers[:]
|
|
842
|
+
combined_layers = [layers.pop(0)]
|
|
843
|
+
while layers:
|
|
844
|
+
current_layer = layers.pop(0)
|
|
845
|
+
combined = combined_layers[-1].combined_layer(current_layer, query)
|
|
846
|
+
if combined:
|
|
847
|
+
# change last layer with combined
|
|
848
|
+
combined_layers[-1] = combined
|
|
849
|
+
else:
|
|
850
|
+
combined_layers.append(current_layer)
|
|
851
|
+
return combined_layers
|