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,262 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2011 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
|
+
from __future__ import absolute_import
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
import threading
|
|
22
|
+
import multiprocessing
|
|
23
|
+
from io import BytesIO
|
|
24
|
+
|
|
25
|
+
from mapproxy.grid import tile_grid
|
|
26
|
+
from mapproxy.image import ImageSource
|
|
27
|
+
from mapproxy.image.opts import ImageOptions
|
|
28
|
+
from mapproxy.layer import MapExtent, DefaultMapExtent, BlankImage, MapLayer
|
|
29
|
+
from mapproxy.source import SourceError
|
|
30
|
+
from mapproxy.client.log import log_request
|
|
31
|
+
from mapproxy.util.py import reraise_exception
|
|
32
|
+
from mapproxy.util.async_ import run_non_blocking
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
import mapnik
|
|
36
|
+
mapnik
|
|
37
|
+
except ImportError:
|
|
38
|
+
try:
|
|
39
|
+
# for 2.0 alpha/rcs and first 2.0 release
|
|
40
|
+
import mapnik2 as mapnik
|
|
41
|
+
except ImportError:
|
|
42
|
+
mapnik = None
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
import queue
|
|
46
|
+
Queue = queue.Queue
|
|
47
|
+
Empty = queue.Empty
|
|
48
|
+
Full = queue.Full
|
|
49
|
+
except ImportError: # in python2 it is called Queue
|
|
50
|
+
import Queue
|
|
51
|
+
Empty = Queue.Empty
|
|
52
|
+
Full = Queue.Full
|
|
53
|
+
MAX_UNUSED_MAPS = 10
|
|
54
|
+
|
|
55
|
+
# fake 2.0 API for older versions
|
|
56
|
+
if mapnik and not hasattr(mapnik, 'Box2d'):
|
|
57
|
+
mapnik.Box2d = mapnik.Envelope
|
|
58
|
+
|
|
59
|
+
log = logging.getLogger(__package__)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MapnikSource(MapLayer):
|
|
63
|
+
supports_meta_tiles = True
|
|
64
|
+
|
|
65
|
+
def __init__(self, mapfile, layers=None, image_opts=None, coverage=None,
|
|
66
|
+
res_range=None, lock=None, reuse_map_objects=False,
|
|
67
|
+
scale_factor=None, multithreaded=False):
|
|
68
|
+
MapLayer.__init__(self, image_opts=image_opts)
|
|
69
|
+
self.mapfile = mapfile
|
|
70
|
+
self.coverage = coverage
|
|
71
|
+
self.res_range = res_range
|
|
72
|
+
self.layers = set(layers) if layers else None
|
|
73
|
+
self.scale_factor = scale_factor
|
|
74
|
+
self.lock = lock
|
|
75
|
+
self.multithreaded = multithreaded
|
|
76
|
+
self._cache_map_obj = reuse_map_objects
|
|
77
|
+
if self.coverage:
|
|
78
|
+
self.extent = MapExtent(self.coverage.bbox, self.coverage.srs)
|
|
79
|
+
else:
|
|
80
|
+
self.extent = DefaultMapExtent()
|
|
81
|
+
if multithreaded:
|
|
82
|
+
# global objects allow caching over multiple instances within the same worker process
|
|
83
|
+
global _map_objs # mapnik map objects by cachekey
|
|
84
|
+
_map_objs = {}
|
|
85
|
+
global _map_objs_lock
|
|
86
|
+
_map_objs_lock = threading.Lock()
|
|
87
|
+
global _map_objs_queues # queues of unused mapnik map objects by PID and mapfile
|
|
88
|
+
_map_objs_queues = {}
|
|
89
|
+
else:
|
|
90
|
+
# instance variables guarantee separation of caches
|
|
91
|
+
self._map_objs = {}
|
|
92
|
+
self._map_objs_lock = threading.Lock()
|
|
93
|
+
|
|
94
|
+
def _map_cache(self):
|
|
95
|
+
"""Get the cache for map objects.
|
|
96
|
+
|
|
97
|
+
Uses an instance variable for containment when multithreaded
|
|
98
|
+
operation is disabled.
|
|
99
|
+
"""
|
|
100
|
+
if self.multithreaded:
|
|
101
|
+
return _map_objs
|
|
102
|
+
return self._map_objs
|
|
103
|
+
|
|
104
|
+
def _map_cache_lock(self):
|
|
105
|
+
"""Get the cache-locks for map objects.
|
|
106
|
+
|
|
107
|
+
Uses an instance variable for containment when multithreaded
|
|
108
|
+
operation is disabled.
|
|
109
|
+
"""
|
|
110
|
+
if self.multithreaded:
|
|
111
|
+
return _map_objs_lock
|
|
112
|
+
return self._map_objs_lock
|
|
113
|
+
|
|
114
|
+
def get_map(self, query):
|
|
115
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size,
|
|
116
|
+
query.srs):
|
|
117
|
+
raise BlankImage()
|
|
118
|
+
if self.coverage and not self.coverage.intersects(query.bbox, query.srs):
|
|
119
|
+
raise BlankImage()
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
resp = self.render(query)
|
|
123
|
+
except RuntimeError as ex:
|
|
124
|
+
log.error('could not render Mapnik map: %s', ex)
|
|
125
|
+
reraise_exception(SourceError(ex.args[0]), sys.exc_info())
|
|
126
|
+
resp.opacity = self.opacity
|
|
127
|
+
return resp
|
|
128
|
+
|
|
129
|
+
def render(self, query):
|
|
130
|
+
mapfile = self.mapfile
|
|
131
|
+
if '%(webmercator_level)' in mapfile:
|
|
132
|
+
_bbox, level = tile_grid(3857).get_affected_bbox_and_level(
|
|
133
|
+
query.bbox, query.size, req_srs=query.srs)
|
|
134
|
+
mapfile = mapfile % {'webmercator_level': level}
|
|
135
|
+
|
|
136
|
+
if self.lock:
|
|
137
|
+
with self.lock():
|
|
138
|
+
return self.render_mapfile(mapfile, query)
|
|
139
|
+
else:
|
|
140
|
+
return self.render_mapfile(mapfile, query)
|
|
141
|
+
|
|
142
|
+
def _create_map_obj(self, mapfile, process_id):
|
|
143
|
+
m = mapnik.Map(0, 0)
|
|
144
|
+
mapnik.load_map(m, str(mapfile))
|
|
145
|
+
m.map_obj_pid = process_id
|
|
146
|
+
return m
|
|
147
|
+
|
|
148
|
+
def _get_map_obj(self, mapfile):
|
|
149
|
+
if not self.multithreaded:
|
|
150
|
+
return self._create_map_obj(mapfile, None)
|
|
151
|
+
|
|
152
|
+
process_id = multiprocessing.current_process()._identity
|
|
153
|
+
queue_cachekey = (process_id, mapfile)
|
|
154
|
+
if queue_cachekey in _map_objs_queues:
|
|
155
|
+
try:
|
|
156
|
+
m = _map_objs_queues[queue_cachekey].get_nowait()
|
|
157
|
+
# check explicitly for the process ID to ensure that
|
|
158
|
+
# map objects cannot move between processes
|
|
159
|
+
if m.map_obj_pid == process_id:
|
|
160
|
+
return m
|
|
161
|
+
except Empty:
|
|
162
|
+
pass
|
|
163
|
+
return self._create_map_obj(mapfile, process_id)
|
|
164
|
+
|
|
165
|
+
def _put_unused_map_obj(self, mapfile, m):
|
|
166
|
+
process_id = multiprocessing.current_process()._identity
|
|
167
|
+
queue_cachekey = (process_id, mapfile)
|
|
168
|
+
if queue_cachekey not in _map_objs_queues:
|
|
169
|
+
_map_objs_queues[queue_cachekey] = Queue(MAX_UNUSED_MAPS)
|
|
170
|
+
try:
|
|
171
|
+
_map_objs_queues[queue_cachekey].put_nowait(m)
|
|
172
|
+
except Full:
|
|
173
|
+
# cleanup the data and drop the map, so it can be garbage collected
|
|
174
|
+
m.remove_all()
|
|
175
|
+
mapnik.clear_cache()
|
|
176
|
+
|
|
177
|
+
def _get_cachekey(self, mapfile):
|
|
178
|
+
if not self.multithreaded or self._cache_map_obj:
|
|
179
|
+
# all MapnikSources with the same mapfile share the same Mapnik Map.
|
|
180
|
+
return (None, None, mapfile)
|
|
181
|
+
thread_id = threading.current_thread().ident
|
|
182
|
+
process_id = multiprocessing.current_process()._identity
|
|
183
|
+
return (process_id, thread_id, mapfile)
|
|
184
|
+
|
|
185
|
+
def _cleanup_unused_cached_maps(self, mapfile):
|
|
186
|
+
if not self.multithreaded:
|
|
187
|
+
return
|
|
188
|
+
# clean up no longer used cached maps
|
|
189
|
+
process_id = multiprocessing.current_process()._identity
|
|
190
|
+
process_cache_keys = [k for k in self._map_cache().keys()
|
|
191
|
+
if k[0] == process_id]
|
|
192
|
+
# To avoid time-consuming cleanup whenever one thread in the
|
|
193
|
+
# threadpool finishes, allow ignoring up to 5 dead mapnik
|
|
194
|
+
# instances. (5 is empirical)
|
|
195
|
+
if len(process_cache_keys) > (5 + threading.active_count()):
|
|
196
|
+
active_thread_ids = set(i.ident for i in threading.enumerate())
|
|
197
|
+
for k in process_cache_keys:
|
|
198
|
+
with self._map_cache_lock():
|
|
199
|
+
if not k[1] in active_thread_ids and k in self._map_cache():
|
|
200
|
+
try:
|
|
201
|
+
m = self._map_cache()[k]
|
|
202
|
+
del self._map_cache()[k]
|
|
203
|
+
# put the map into the queue of unused
|
|
204
|
+
# maps so it can be re-used from another
|
|
205
|
+
# thread.
|
|
206
|
+
self._put_unused_map_obj(mapfile, m)
|
|
207
|
+
except KeyError:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
def map_obj(self, mapfile):
|
|
211
|
+
# cache loaded map objects
|
|
212
|
+
# only works when a single proc/thread accesses the map
|
|
213
|
+
# (forking the render process doesn't work because of open database
|
|
214
|
+
# file handles that gets passed to the child)
|
|
215
|
+
# segment the cache by process and thread to avoid interference
|
|
216
|
+
cachekey = self._get_cachekey(mapfile)
|
|
217
|
+
with self._map_cache_lock():
|
|
218
|
+
if cachekey not in self._map_cache():
|
|
219
|
+
self._map_cache()[cachekey] = self._get_map_obj(mapfile)
|
|
220
|
+
mapnik_map = self._map_cache()[cachekey]
|
|
221
|
+
|
|
222
|
+
self._cleanup_unused_cached_maps(mapfile)
|
|
223
|
+
|
|
224
|
+
return mapnik_map
|
|
225
|
+
|
|
226
|
+
def render_mapfile(self, mapfile, query):
|
|
227
|
+
return run_non_blocking(self._render_mapfile, (mapfile, query))
|
|
228
|
+
|
|
229
|
+
def _render_mapfile(self, mapfile, query):
|
|
230
|
+
start_time = time.time()
|
|
231
|
+
|
|
232
|
+
m = self.map_obj(mapfile)
|
|
233
|
+
m.resize(query.size[0], query.size[1])
|
|
234
|
+
m.srs = '+init=%s' % str(query.srs.srs_code.lower())
|
|
235
|
+
envelope = mapnik.Box2d(*query.bbox)
|
|
236
|
+
m.zoom_to_box(envelope)
|
|
237
|
+
data = None
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
if self.layers:
|
|
241
|
+
i = 0
|
|
242
|
+
for layer in m.layers[:]:
|
|
243
|
+
if layer.name != 'Unknown' and layer.name not in self.layers:
|
|
244
|
+
del m.layers[i]
|
|
245
|
+
else:
|
|
246
|
+
i += 1
|
|
247
|
+
|
|
248
|
+
img = mapnik.Image(query.size[0], query.size[1])
|
|
249
|
+
if self.scale_factor:
|
|
250
|
+
mapnik.render(m, img, self.scale_factor)
|
|
251
|
+
else:
|
|
252
|
+
mapnik.render(m, img)
|
|
253
|
+
data = img.tostring(str(query.format))
|
|
254
|
+
finally:
|
|
255
|
+
size = None
|
|
256
|
+
if data:
|
|
257
|
+
size = len(data)
|
|
258
|
+
log_request('%s:%s:%s:%s' % (mapfile, query.bbox, query.srs.srs_code, query.size),
|
|
259
|
+
status='200' if data else '500', size=size, method='API', duration=time.time()-start_time)
|
|
260
|
+
|
|
261
|
+
return ImageSource(BytesIO(data), size=query.size,
|
|
262
|
+
image_opts=ImageOptions(format=query.format))
|
mapproxy/source/tile.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2010 Omniscale <http://omniscale.de>
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Retrieve tiles from different tile servers (TMS/TileCache/etc.).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
from mapproxy.image.opts import ImageOptions
|
|
22
|
+
from mapproxy.source import SourceError
|
|
23
|
+
from mapproxy.client.http import HTTPClientError
|
|
24
|
+
from mapproxy.source import InvalidSourceQuery
|
|
25
|
+
from mapproxy.layer import BlankImage, map_extent_from_grid, CacheMapLayer, MapLayer
|
|
26
|
+
from mapproxy.util.py import reraise_exception
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
log = logging.getLogger('mapproxy.source.tile')
|
|
30
|
+
log_config = logging.getLogger('mapproxy.config')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TiledSource(MapLayer):
|
|
34
|
+
def __init__(self, grid, client, coverage=None, image_opts=None, error_handler=None,
|
|
35
|
+
res_range=None):
|
|
36
|
+
MapLayer.__init__(self, image_opts=image_opts)
|
|
37
|
+
self.grid = grid
|
|
38
|
+
self.client = client
|
|
39
|
+
self.image_opts = image_opts or ImageOptions()
|
|
40
|
+
self.coverage = coverage
|
|
41
|
+
self.extent = coverage.extent if coverage else map_extent_from_grid(grid)
|
|
42
|
+
self.res_range = res_range
|
|
43
|
+
self.error_handler = error_handler
|
|
44
|
+
|
|
45
|
+
def get_map(self, query):
|
|
46
|
+
if self.grid.tile_size != query.size:
|
|
47
|
+
ex = InvalidSourceQuery(
|
|
48
|
+
'tile size of cache and tile source do not match: %s != %s'
|
|
49
|
+
% (self.grid.tile_size, query.size)
|
|
50
|
+
)
|
|
51
|
+
log_config.error(ex)
|
|
52
|
+
raise ex
|
|
53
|
+
|
|
54
|
+
if self.grid.srs != query.srs:
|
|
55
|
+
ex = InvalidSourceQuery(
|
|
56
|
+
'SRS of cache and tile source do not match: %r != %r'
|
|
57
|
+
% (self.grid.srs, query.srs)
|
|
58
|
+
)
|
|
59
|
+
log_config.error(ex)
|
|
60
|
+
raise ex
|
|
61
|
+
|
|
62
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size,
|
|
63
|
+
query.srs):
|
|
64
|
+
raise BlankImage()
|
|
65
|
+
if self.coverage and not self.coverage.intersects(query.bbox, query.srs):
|
|
66
|
+
raise BlankImage()
|
|
67
|
+
|
|
68
|
+
_bbox, grid, tiles = self.grid.get_affected_tiles(query.bbox, query.size)
|
|
69
|
+
|
|
70
|
+
if grid != (1, 1):
|
|
71
|
+
raise InvalidSourceQuery('BBOX does not align to tile')
|
|
72
|
+
|
|
73
|
+
tile_coord = next(tiles)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
return self.client.get_tile(tile_coord, format=query.format)
|
|
77
|
+
except HTTPClientError as e:
|
|
78
|
+
if self.error_handler:
|
|
79
|
+
resp = self.error_handler.handle(e.response_code, query)
|
|
80
|
+
if resp:
|
|
81
|
+
return resp
|
|
82
|
+
log.warning('could not retrieve tile: %s', e)
|
|
83
|
+
reraise_exception(SourceError(e.args[0]), sys.exc_info())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CacheSource(CacheMapLayer):
|
|
87
|
+
def __init__(self, tile_manager, extent=None, image_opts=None,
|
|
88
|
+
max_tile_limit=None, tiled_only=False):
|
|
89
|
+
CacheMapLayer.__init__(self, tile_manager, extent=extent, image_opts=image_opts,
|
|
90
|
+
max_tile_limit=max_tile_limit)
|
|
91
|
+
self.supports_meta_tiles = not tiled_only
|
|
92
|
+
self.tiled_only = tiled_only
|
|
93
|
+
|
|
94
|
+
def get_map(self, query):
|
|
95
|
+
if self.tiled_only:
|
|
96
|
+
query.tiled_only = True
|
|
97
|
+
return CacheMapLayer.get_map(self, query)
|
mapproxy/source/wms.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# This file is part of the MapProxy project.
|
|
2
|
+
# Copyright (C) 2010 Omniscale <http://omniscale.de>
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
Retrieve maps/information from WMS servers.
|
|
17
|
+
"""
|
|
18
|
+
import sys
|
|
19
|
+
from mapproxy.request.base import split_mime_type
|
|
20
|
+
from mapproxy.cache.legend import Legend, legend_identifier
|
|
21
|
+
from mapproxy.image import make_transparent, ImageSource, SubImageSource, bbox_position_in_image
|
|
22
|
+
from mapproxy.image.merge import concat_legends, concat_json_legends
|
|
23
|
+
from mapproxy.image.transform import ImageTransformer
|
|
24
|
+
from mapproxy.layer import MapExtent, DefaultMapExtent, BlankImage, LegendQuery, MapQuery, MapLayer
|
|
25
|
+
from mapproxy.source import InfoSource, SourceError, LegendSource
|
|
26
|
+
from mapproxy.client.http import HTTPClientError
|
|
27
|
+
from mapproxy.util.py import reraise_exception
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
log = logging.getLogger('mapproxy.source.wms')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WMSSource(MapLayer):
|
|
34
|
+
supports_meta_tiles = True
|
|
35
|
+
|
|
36
|
+
def __init__(self, client, image_opts=None, coverage=None, res_range=None,
|
|
37
|
+
transparent_color=None, transparent_color_tolerance=None,
|
|
38
|
+
supported_srs=None, supported_formats=None, fwd_req_params=None,
|
|
39
|
+
error_handler=None):
|
|
40
|
+
MapLayer.__init__(self, image_opts=image_opts)
|
|
41
|
+
self.client = client
|
|
42
|
+
self.supported_srs = supported_srs or []
|
|
43
|
+
self.supported_formats = supported_formats or []
|
|
44
|
+
self.fwd_req_params = fwd_req_params or set()
|
|
45
|
+
|
|
46
|
+
self.transparent_color = transparent_color
|
|
47
|
+
self.transparent_color_tolerance = transparent_color_tolerance
|
|
48
|
+
if self.transparent_color:
|
|
49
|
+
self.image_opts.transparent = True
|
|
50
|
+
self.coverage = coverage
|
|
51
|
+
self.res_range = res_range
|
|
52
|
+
if self.coverage:
|
|
53
|
+
self.extent = MapExtent(self.coverage.bbox, self.coverage.srs)
|
|
54
|
+
else:
|
|
55
|
+
self.extent = DefaultMapExtent()
|
|
56
|
+
self.error_handler = error_handler
|
|
57
|
+
|
|
58
|
+
def is_opaque(self, query):
|
|
59
|
+
"""
|
|
60
|
+
Returns true if we are sure that the image is not transparent.
|
|
61
|
+
"""
|
|
62
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size,
|
|
63
|
+
query.srs):
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if self.image_opts.transparent:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
if self.opacity is not None and (0.0 < self.opacity < 0.99):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
if not self.coverage:
|
|
73
|
+
# not transparent and no coverage
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
if self.coverage.contains(query.bbox, query.srs):
|
|
77
|
+
# not transparent and completely inside coverage
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def get_map(self, query):
|
|
83
|
+
if self.res_range and not self.res_range.contains(query.bbox, query.size,
|
|
84
|
+
query.srs):
|
|
85
|
+
raise BlankImage()
|
|
86
|
+
if self.coverage and not self.coverage.intersects(query.bbox, query.srs):
|
|
87
|
+
raise BlankImage()
|
|
88
|
+
try:
|
|
89
|
+
resp = self._get_map(query)
|
|
90
|
+
if self.transparent_color:
|
|
91
|
+
resp = make_transparent(resp, self.transparent_color,
|
|
92
|
+
self.transparent_color_tolerance)
|
|
93
|
+
resp.opacity = self.opacity
|
|
94
|
+
return resp
|
|
95
|
+
|
|
96
|
+
except HTTPClientError as e:
|
|
97
|
+
if self.error_handler:
|
|
98
|
+
resp = self.error_handler.handle(e.response_code, query)
|
|
99
|
+
if resp:
|
|
100
|
+
return resp
|
|
101
|
+
log.warning('could not retrieve WMS map: %s', e.full_msg or e)
|
|
102
|
+
reraise_exception(SourceError(e.args[0]), sys.exc_info())
|
|
103
|
+
|
|
104
|
+
def _get_map(self, query):
|
|
105
|
+
format = self.image_opts.format
|
|
106
|
+
if not format:
|
|
107
|
+
format = query.format
|
|
108
|
+
if self.supported_formats and format not in self.supported_formats:
|
|
109
|
+
format = self.supported_formats[0]
|
|
110
|
+
if self.supported_srs:
|
|
111
|
+
# srs can be equal while still having a different srs_code (EPSG:3857/900913),
|
|
112
|
+
# make sure to use a supported srs_code
|
|
113
|
+
request_srs = None
|
|
114
|
+
for srs in self.supported_srs:
|
|
115
|
+
if query.srs == srs:
|
|
116
|
+
request_srs = srs
|
|
117
|
+
break
|
|
118
|
+
if request_srs is None:
|
|
119
|
+
return self._get_transformed(query, format)
|
|
120
|
+
if query.srs.srs_code != request_srs.srs_code:
|
|
121
|
+
query.srs = request_srs
|
|
122
|
+
if self.extent and not self.extent.contains(MapExtent(query.bbox, query.srs)):
|
|
123
|
+
return self._get_sub_query(query, format)
|
|
124
|
+
resp = self.client.retrieve(query, format)
|
|
125
|
+
return ImageSource(resp, size=query.size, image_opts=self.image_opts)
|
|
126
|
+
|
|
127
|
+
def _get_sub_query(self, query, format):
|
|
128
|
+
size, offset, bbox = bbox_position_in_image(query.bbox, query.size, self.extent.bbox_for(query.srs))
|
|
129
|
+
if size[0] == 0 or size[1] == 0:
|
|
130
|
+
raise BlankImage()
|
|
131
|
+
src_query = MapQuery(bbox, size, query.srs, format, dimensions=query.dimensions)
|
|
132
|
+
resp = self.client.retrieve(src_query, format)
|
|
133
|
+
return SubImageSource(resp, size=query.size, offset=offset, image_opts=self.image_opts)
|
|
134
|
+
|
|
135
|
+
def _get_transformed(self, query, format):
|
|
136
|
+
dst_srs = query.srs
|
|
137
|
+
src_srs = self.supported_srs.best_srs(dst_srs)
|
|
138
|
+
dst_bbox = query.bbox
|
|
139
|
+
src_bbox = dst_srs.transform_bbox_to(src_srs, dst_bbox)
|
|
140
|
+
|
|
141
|
+
src_width, src_height = src_bbox[2]-src_bbox[0], src_bbox[3]-src_bbox[1]
|
|
142
|
+
ratio = src_width/src_height
|
|
143
|
+
dst_size = query.size
|
|
144
|
+
xres, yres = src_width/dst_size[0], src_height/dst_size[1]
|
|
145
|
+
if xres < yres:
|
|
146
|
+
src_size = dst_size[0], int(dst_size[0]/ratio + 0.5)
|
|
147
|
+
else:
|
|
148
|
+
src_size = int(dst_size[1]*ratio + 0.5), dst_size[1]
|
|
149
|
+
|
|
150
|
+
src_query = MapQuery(src_bbox, src_size, src_srs, format, dimensions=query.dimensions)
|
|
151
|
+
|
|
152
|
+
if self.coverage and not self.coverage.contains(src_bbox, src_srs):
|
|
153
|
+
img = self._get_sub_query(src_query, format)
|
|
154
|
+
else:
|
|
155
|
+
resp = self.client.retrieve(src_query, format)
|
|
156
|
+
img = ImageSource(resp, size=src_size, image_opts=self.image_opts)
|
|
157
|
+
|
|
158
|
+
img = ImageTransformer(src_srs, dst_srs).transform(img, src_bbox,
|
|
159
|
+
query.size, dst_bbox, self.image_opts)
|
|
160
|
+
|
|
161
|
+
img.format = format
|
|
162
|
+
return img
|
|
163
|
+
|
|
164
|
+
def _is_compatible(self, other, query):
|
|
165
|
+
if not isinstance(other, WMSSource):
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
if self.opacity is not None or other.opacity is not None:
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
if self.supported_srs != other.supported_srs:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
if self.supported_formats != other.supported_formats:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
if self.transparent_color != other.transparent_color:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
if self.transparent_color_tolerance != other.transparent_color_tolerance:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
if self.coverage != other.coverage:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
if (query.dimensions_for_params(self.fwd_req_params) !=
|
|
187
|
+
query.dimensions_for_params(other.fwd_req_params)):
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
def combined_layer(self, other, query):
|
|
193
|
+
if not self._is_compatible(other, query):
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
client = self.client.combined_client(other.client, query)
|
|
197
|
+
if not client:
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
return WMSSource(client, image_opts=self.image_opts,
|
|
201
|
+
transparent_color=self.transparent_color,
|
|
202
|
+
transparent_color_tolerance=self.transparent_color_tolerance,
|
|
203
|
+
supported_srs=self.supported_srs,
|
|
204
|
+
supported_formats=self.supported_formats,
|
|
205
|
+
res_range=None, # layer outside res_range should already be filtered out
|
|
206
|
+
coverage=self.coverage,
|
|
207
|
+
fwd_req_params=self.fwd_req_params,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class WMSInfoSource(InfoSource):
|
|
212
|
+
def __init__(self, client, fi_transformer=None, coverage=None):
|
|
213
|
+
self.client = client
|
|
214
|
+
self.fi_transformer = fi_transformer
|
|
215
|
+
self.coverage = coverage
|
|
216
|
+
|
|
217
|
+
def get_info(self, query):
|
|
218
|
+
if self.coverage and not self.coverage.contains(query.coord, query.srs):
|
|
219
|
+
return None
|
|
220
|
+
doc = self.client.get_info(query)
|
|
221
|
+
if self.fi_transformer:
|
|
222
|
+
doc = self.fi_transformer(doc)
|
|
223
|
+
return doc
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class WMSLegendSource(LegendSource):
|
|
227
|
+
def __init__(self, clients, legend_cache, static=False):
|
|
228
|
+
self.clients = clients
|
|
229
|
+
self.identifier = legend_identifier([c.identifier for c in self.clients])
|
|
230
|
+
self._cache = legend_cache
|
|
231
|
+
self._size = None
|
|
232
|
+
self.static = static
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def size(self):
|
|
236
|
+
if not self._size:
|
|
237
|
+
legend = self.get_legend(LegendQuery(format='image/png', scale=None))
|
|
238
|
+
# TODO image size without as_image?
|
|
239
|
+
self._size = legend.as_image().size
|
|
240
|
+
return self._size
|
|
241
|
+
|
|
242
|
+
def get_legend(self, query):
|
|
243
|
+
if self.static:
|
|
244
|
+
# prevent caching of static legends for different scales
|
|
245
|
+
legend = Legend(id=self.identifier, scale=None)
|
|
246
|
+
else:
|
|
247
|
+
legend = Legend(id=self.identifier, scale=query.scale)
|
|
248
|
+
|
|
249
|
+
if not self._cache.load(legend) or query.format == 'json':
|
|
250
|
+
legends = []
|
|
251
|
+
error_occured = False
|
|
252
|
+
for client in self.clients:
|
|
253
|
+
try:
|
|
254
|
+
legends.append(client.get_legend(query))
|
|
255
|
+
except HTTPClientError as e:
|
|
256
|
+
error_occured = True
|
|
257
|
+
log.error(e.args[0])
|
|
258
|
+
except SourceError as e:
|
|
259
|
+
error_occured = True
|
|
260
|
+
# TODO errors?
|
|
261
|
+
log.error(e.args[0])
|
|
262
|
+
format = split_mime_type(query.format)[1]
|
|
263
|
+
|
|
264
|
+
if format == 'json':
|
|
265
|
+
return concat_json_legends(legends)
|
|
266
|
+
|
|
267
|
+
legend = Legend(source=concat_legends(legends, format=format),
|
|
268
|
+
id=self.identifier, scale=query.scale)
|
|
269
|
+
if not error_occured:
|
|
270
|
+
self._cache.store(legend)
|
|
271
|
+
|
|
272
|
+
# returns ImageSource
|
|
273
|
+
return legend.source
|