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,357 @@
|
|
|
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
|
+
from __future__ import division
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import importlib_resources
|
|
21
|
+
|
|
22
|
+
from mapproxy.config import base_config, abspath
|
|
23
|
+
from mapproxy.compat.image import Image, ImageColor, ImageDraw, ImageFont
|
|
24
|
+
from mapproxy.image import ImageSource
|
|
25
|
+
from mapproxy.image.opts import create_image, ImageOptions
|
|
26
|
+
|
|
27
|
+
_pil_ttf_support = True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
log_system = logging.getLogger('mapproxy.system')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def message_image(message, size, image_opts, bgcolor='#ffffff',
|
|
34
|
+
transparent=False):
|
|
35
|
+
"""
|
|
36
|
+
Creates an image with text (`message`). This can be used
|
|
37
|
+
to create in_image exceptions.
|
|
38
|
+
|
|
39
|
+
For dark `bgcolor` the font color is white, otherwise black.
|
|
40
|
+
|
|
41
|
+
:param message: the message to put in the image
|
|
42
|
+
:param size: the size of the output image
|
|
43
|
+
:param format: the output format of the image
|
|
44
|
+
:param bgcolor: the background color of the image
|
|
45
|
+
:param transparent: if True and the `format` supports it,
|
|
46
|
+
return a transparent image
|
|
47
|
+
:rtype: `ImageSource`
|
|
48
|
+
"""
|
|
49
|
+
eimg = ExceptionImage(message, image_opts=image_opts)
|
|
50
|
+
return eimg.draw(size=size)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def attribution_image(message, size, image_opts=None, inverse=False):
|
|
54
|
+
"""
|
|
55
|
+
Creates an image with text attribution (`message`).
|
|
56
|
+
|
|
57
|
+
:param message: the message to put in the image
|
|
58
|
+
:param size: the size of the output image
|
|
59
|
+
:param format: the output format of the image
|
|
60
|
+
:param inverse: if true, write white text
|
|
61
|
+
:param transparent: if True and the `format` supports it,
|
|
62
|
+
return a transparent image
|
|
63
|
+
:rtype: `ImageSource`
|
|
64
|
+
"""
|
|
65
|
+
if image_opts is None:
|
|
66
|
+
image_opts = ImageOptions(transparent=True)
|
|
67
|
+
aimg = AttributionImage(message, image_opts=image_opts,
|
|
68
|
+
inverse=inverse)
|
|
69
|
+
return aimg.draw(size=size)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MessageImage(object):
|
|
73
|
+
"""
|
|
74
|
+
Base class for text rendering in images (for watermarks, exception images, etc.)
|
|
75
|
+
|
|
76
|
+
:ivar font_name: the font name for the text
|
|
77
|
+
:ivar font_size: the font size of the text
|
|
78
|
+
:ivar font_color: the color of the font as a tuple
|
|
79
|
+
:ivar box_color: the color of the box behind the text.
|
|
80
|
+
color as a tuple or ``None``
|
|
81
|
+
"""
|
|
82
|
+
font_name = 'DejaVu Sans Mono'
|
|
83
|
+
font_size = 10
|
|
84
|
+
font_color = ImageColor.getrgb('black')
|
|
85
|
+
box_color = None
|
|
86
|
+
linespacing = 5
|
|
87
|
+
padding = 3
|
|
88
|
+
placement = 'ul'
|
|
89
|
+
|
|
90
|
+
def __init__(self, message, image_opts):
|
|
91
|
+
self.message = message
|
|
92
|
+
self.image_opts = image_opts
|
|
93
|
+
self._font = None
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def font(self):
|
|
97
|
+
global _pil_ttf_support
|
|
98
|
+
if self._font is None:
|
|
99
|
+
if self.font_name != 'default' and _pil_ttf_support:
|
|
100
|
+
try:
|
|
101
|
+
self._font = ImageFont.truetype(font_file(self.font_name),
|
|
102
|
+
self.font_size)
|
|
103
|
+
except ImportError:
|
|
104
|
+
_pil_ttf_support = False
|
|
105
|
+
log_system.warning("Couldn't load TrueType fonts, "
|
|
106
|
+
"PIL needs to be build with freetype support.")
|
|
107
|
+
except IOError:
|
|
108
|
+
_pil_ttf_support = False
|
|
109
|
+
log_system.warning("Couldn't load find TrueType font ", self.font_name)
|
|
110
|
+
if self._font is None:
|
|
111
|
+
self._font = ImageFont.load_default()
|
|
112
|
+
return self._font
|
|
113
|
+
|
|
114
|
+
def new_image(self, size):
|
|
115
|
+
return Image.new('RGBA', size)
|
|
116
|
+
|
|
117
|
+
def draw(self, img=None, size=None, in_place=True):
|
|
118
|
+
"""
|
|
119
|
+
Create the message image. Either draws on top of `img` or creates a
|
|
120
|
+
new image with the given `size`.
|
|
121
|
+
"""
|
|
122
|
+
if not ((img and not size) or (size and not img)):
|
|
123
|
+
raise TypeError('need either img or size argument')
|
|
124
|
+
|
|
125
|
+
if img is None:
|
|
126
|
+
base_img = self.new_image(size)
|
|
127
|
+
elif not in_place:
|
|
128
|
+
size = img.size
|
|
129
|
+
base_img = self.new_image(size)
|
|
130
|
+
else:
|
|
131
|
+
base_img = img.as_image()
|
|
132
|
+
size = base_img.size
|
|
133
|
+
|
|
134
|
+
if not self.message:
|
|
135
|
+
if img is not None:
|
|
136
|
+
return img
|
|
137
|
+
return ImageSource(base_img, size=size, image_opts=self.image_opts)
|
|
138
|
+
|
|
139
|
+
draw = ImageDraw.Draw(base_img)
|
|
140
|
+
self.draw_msg(draw, size)
|
|
141
|
+
image_opts = self.image_opts
|
|
142
|
+
if not in_place and img:
|
|
143
|
+
image_opts = image_opts or img.image_opts
|
|
144
|
+
img = img.as_image()
|
|
145
|
+
converted = False
|
|
146
|
+
if len(self.font_color) == 4 and img.mode != 'RGBA':
|
|
147
|
+
# we need RGBA to keep transparency from text
|
|
148
|
+
converted = img.mode
|
|
149
|
+
img = img.convert('RGBA')
|
|
150
|
+
img.paste(base_img, (0, 0), base_img)
|
|
151
|
+
if converted == 'RGB':
|
|
152
|
+
# convert image back
|
|
153
|
+
img = img.convert('RGB')
|
|
154
|
+
base_img = img
|
|
155
|
+
|
|
156
|
+
return ImageSource(base_img, size=size, image_opts=image_opts)
|
|
157
|
+
|
|
158
|
+
def draw_msg(self, draw, size):
|
|
159
|
+
td = TextDraw(self.message, font=self.font, bg_color=self.box_color,
|
|
160
|
+
font_color=self.font_color, placement=self.placement,
|
|
161
|
+
linespacing=self.linespacing, padding=self.padding)
|
|
162
|
+
td.draw(draw, size)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ExceptionImage(MessageImage):
|
|
166
|
+
"""
|
|
167
|
+
Image for exceptions.
|
|
168
|
+
"""
|
|
169
|
+
font_name = 'default'
|
|
170
|
+
font_size = 9
|
|
171
|
+
|
|
172
|
+
def __init__(self, message, image_opts):
|
|
173
|
+
MessageImage.__init__(self, message, image_opts=image_opts.copy())
|
|
174
|
+
if not self.image_opts.bgcolor:
|
|
175
|
+
self.image_opts.bgcolor = '#ffffff'
|
|
176
|
+
|
|
177
|
+
def new_image(self, size):
|
|
178
|
+
return create_image(size, self.image_opts)
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def font_color(self):
|
|
182
|
+
if self.image_opts.transparent:
|
|
183
|
+
return ImageColor.getrgb('black')
|
|
184
|
+
if _luminance(ImageColor.getrgb(self.image_opts.bgcolor)) < 128:
|
|
185
|
+
return ImageColor.getrgb('white')
|
|
186
|
+
return ImageColor.getrgb('black')
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class WatermarkImage(MessageImage):
|
|
190
|
+
"""
|
|
191
|
+
Image with large, faded message.
|
|
192
|
+
"""
|
|
193
|
+
font_name = 'DejaVu Sans'
|
|
194
|
+
font_size = 24
|
|
195
|
+
font_color = (128, 128, 128)
|
|
196
|
+
|
|
197
|
+
def __init__(self, message, image_opts, placement='c', opacity=None, font_color=None, font_size=None):
|
|
198
|
+
MessageImage.__init__(self, message, image_opts=image_opts)
|
|
199
|
+
if opacity is None:
|
|
200
|
+
opacity = 30
|
|
201
|
+
if font_size:
|
|
202
|
+
self.font_size = font_size
|
|
203
|
+
if font_color:
|
|
204
|
+
self.font_color = font_color
|
|
205
|
+
self.font_color = self.font_color + tuple([opacity])
|
|
206
|
+
self.placement = placement
|
|
207
|
+
|
|
208
|
+
def draw_msg(self, draw, size):
|
|
209
|
+
td = TextDraw(self.message, self.font, self.font_color)
|
|
210
|
+
if self.placement in ('l', 'b'):
|
|
211
|
+
td.placement = 'cL'
|
|
212
|
+
td.draw(draw, size)
|
|
213
|
+
if self.placement in ('r', 'b'):
|
|
214
|
+
td.placement = 'cR'
|
|
215
|
+
td.draw(draw, size)
|
|
216
|
+
if self.placement == 'c':
|
|
217
|
+
td.placement = 'cc'
|
|
218
|
+
td.draw(draw, size)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class AttributionImage(MessageImage):
|
|
222
|
+
"""
|
|
223
|
+
Image with attribution information.
|
|
224
|
+
"""
|
|
225
|
+
font_name = 'DejaVu Sans'
|
|
226
|
+
font_size = 10
|
|
227
|
+
placement = 'lr'
|
|
228
|
+
|
|
229
|
+
def __init__(self, message, image_opts, inverse=False):
|
|
230
|
+
MessageImage.__init__(self, message, image_opts=image_opts)
|
|
231
|
+
self.inverse = inverse
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def font_color(self):
|
|
235
|
+
if self.inverse:
|
|
236
|
+
return ImageColor.getrgb('white')
|
|
237
|
+
else:
|
|
238
|
+
return ImageColor.getrgb('black')
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def box_color(self):
|
|
242
|
+
if self.inverse:
|
|
243
|
+
return (0, 0, 0, 100)
|
|
244
|
+
else:
|
|
245
|
+
return (255, 255, 255, 120)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TextDraw(object):
|
|
249
|
+
def __init__(self, text, font, font_color=None, bg_color=None,
|
|
250
|
+
placement='ul', padding=5, linespacing=3):
|
|
251
|
+
if isinstance(text, str):
|
|
252
|
+
text = text.split('\n')
|
|
253
|
+
self.text = text
|
|
254
|
+
self.font = font
|
|
255
|
+
self.bg_color = bg_color
|
|
256
|
+
self.font_color = font_color
|
|
257
|
+
self.placement = placement
|
|
258
|
+
self.padding = (padding, padding, padding, padding)
|
|
259
|
+
self.linespacing = linespacing
|
|
260
|
+
|
|
261
|
+
def text_boxes(self, draw, size):
|
|
262
|
+
try:
|
|
263
|
+
total_bbox, boxes = self._relative_text_boxes(draw)
|
|
264
|
+
except UnicodeEncodeError:
|
|
265
|
+
# raised if font does not support unicode
|
|
266
|
+
self.text = [x.encode('ascii', 'replace') for x in self.text]
|
|
267
|
+
total_bbox, boxes = self._relative_text_boxes(draw)
|
|
268
|
+
return self._place_boxes(total_bbox, boxes, size)
|
|
269
|
+
|
|
270
|
+
def draw(self, draw, size):
|
|
271
|
+
total_bbox, boxes = self.text_boxes(draw, size)
|
|
272
|
+
if self.bg_color:
|
|
273
|
+
draw.rectangle(
|
|
274
|
+
(total_bbox[0]-self.padding[0],
|
|
275
|
+
total_bbox[1]-self.padding[1],
|
|
276
|
+
total_bbox[2]+self.padding[2],
|
|
277
|
+
total_bbox[3]+self.padding[3]),
|
|
278
|
+
fill=self.bg_color)
|
|
279
|
+
|
|
280
|
+
for text, box in zip(self.text, boxes):
|
|
281
|
+
draw.text((box[0], box[1]), text, font=self.font, fill=self.font_color)
|
|
282
|
+
|
|
283
|
+
def _relative_text_boxes(self, draw):
|
|
284
|
+
total_bbox = (1e9, 1e9, -1e9, -1e9)
|
|
285
|
+
boxes = []
|
|
286
|
+
y_offset = 0
|
|
287
|
+
for i, line in enumerate(self.text):
|
|
288
|
+
try:
|
|
289
|
+
text_box = draw.textbbox((0, y_offset), line, font=self.font)
|
|
290
|
+
y_offset = text_box[3] + self.linespacing
|
|
291
|
+
except AttributeError:
|
|
292
|
+
# Pillow < 8
|
|
293
|
+
text_size = draw.textsize(line, font=self.font)
|
|
294
|
+
text_box = (0, y_offset, text_size[0], text_size[1]+y_offset)
|
|
295
|
+
y_offset += text_size[1] + self.linespacing
|
|
296
|
+
boxes.append(text_box)
|
|
297
|
+
total_bbox = (min(total_bbox[0], text_box[0]),
|
|
298
|
+
min(total_bbox[1], text_box[1]),
|
|
299
|
+
max(total_bbox[2], text_box[2]),
|
|
300
|
+
max(total_bbox[3], text_box[3]),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return total_bbox, boxes
|
|
304
|
+
|
|
305
|
+
def _move_bboxes(self, boxes, offsets):
|
|
306
|
+
result = []
|
|
307
|
+
for box in boxes:
|
|
308
|
+
box = box[0]+offsets[0], box[1]+offsets[1], box[2]+offsets[0], box[3]+offsets[1]
|
|
309
|
+
result.append(tuple(int(x) for x in box))
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
def _place_boxes(self, total_bbox, boxes, size):
|
|
313
|
+
x_offset = y_offset = None
|
|
314
|
+
text_size = (total_bbox[2] - total_bbox[0]), (total_bbox[3] - total_bbox[1])
|
|
315
|
+
|
|
316
|
+
if self.placement[0] == 'u':
|
|
317
|
+
y_offset = self.padding[1]
|
|
318
|
+
elif self.placement[0] == 'l':
|
|
319
|
+
y_offset = size[1] - self.padding[3] - text_size[1]
|
|
320
|
+
elif self.placement[0] == 'c':
|
|
321
|
+
y_offset = size[1] // 2 - text_size[1] // 2
|
|
322
|
+
|
|
323
|
+
if self.placement[1] == 'l':
|
|
324
|
+
x_offset = self.padding[0]
|
|
325
|
+
if self.placement[1] == 'L':
|
|
326
|
+
x_offset = -text_size[0] // 2
|
|
327
|
+
elif self.placement[1] == 'r':
|
|
328
|
+
x_offset = size[0] - self.padding[1] - text_size[0]
|
|
329
|
+
elif self.placement[1] == 'R':
|
|
330
|
+
x_offset = size[0] - text_size[0] // 2
|
|
331
|
+
elif self.placement[1] == 'c':
|
|
332
|
+
x_offset = size[0] // 2 - text_size[0] // 2
|
|
333
|
+
|
|
334
|
+
if x_offset is None or y_offset is None:
|
|
335
|
+
raise ValueError('placement %r not supported' % self.placement)
|
|
336
|
+
|
|
337
|
+
offsets = x_offset, y_offset
|
|
338
|
+
return self._move_bboxes([total_bbox], offsets)[0], self._move_bboxes(boxes, offsets)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def font_file(font_name):
|
|
342
|
+
font_dir = base_config().image.font_dir
|
|
343
|
+
font_name = font_name.replace(' ', '')
|
|
344
|
+
if font_dir:
|
|
345
|
+
abspath(font_dir)
|
|
346
|
+
path = os.path.join(font_dir, font_name + '.ttf')
|
|
347
|
+
else:
|
|
348
|
+
path = str(importlib_resources.files(__package__).joinpath('fonts').joinpath(font_name + '.ttf'))
|
|
349
|
+
return path
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _luminance(color):
|
|
353
|
+
"""
|
|
354
|
+
Returns the luminance of a RGB tuple. Uses ITU-R 601-2 luma transform.
|
|
355
|
+
"""
|
|
356
|
+
r, g, b = color
|
|
357
|
+
return r * 299/1000 + g * 587/1000 + b * 114/1000
|
mapproxy/image/opts.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# -:- encoding: utf-8 -:-
|
|
2
|
+
# This file is part of the MapProxy project.
|
|
3
|
+
# Copyright (C) 2011 Omniscale <http://omniscale.de>
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
import copy
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImageOptions(object):
|
|
21
|
+
def __init__(self, mode=None, transparent=None, opacity=None, resampling=None,
|
|
22
|
+
format=None, bgcolor=None, colors=None, encoding_options=None):
|
|
23
|
+
self.transparent = transparent
|
|
24
|
+
self.opacity = opacity
|
|
25
|
+
self.resampling = resampling
|
|
26
|
+
if format is not None:
|
|
27
|
+
format = ImageFormat(format)
|
|
28
|
+
self.format = format
|
|
29
|
+
self.mode = mode
|
|
30
|
+
self.bgcolor = bgcolor
|
|
31
|
+
self.colors = colors
|
|
32
|
+
self.encoding_options = encoding_options or {}
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
options = []
|
|
36
|
+
for k in dir(self):
|
|
37
|
+
if k.startswith('_'):
|
|
38
|
+
continue
|
|
39
|
+
v = getattr(self, k)
|
|
40
|
+
if v is not None and not hasattr(v, 'im_func') and not hasattr(v, '__func__'):
|
|
41
|
+
options.append('%s=%r' % (k, v))
|
|
42
|
+
return 'ImageOptions(%s)' % (', '.join(options), )
|
|
43
|
+
|
|
44
|
+
def __eq__(self, other):
|
|
45
|
+
if not isinstance(other, ImageOptions):
|
|
46
|
+
return NotImplemented
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
self.transparent == other.transparent
|
|
50
|
+
and self.opacity == other.opacity
|
|
51
|
+
and self.resampling == other.resampling
|
|
52
|
+
and self.format == other.format
|
|
53
|
+
and self.mode == other.mode
|
|
54
|
+
and self.bgcolor == other.bgcolor
|
|
55
|
+
and self.colors == other.colors
|
|
56
|
+
and self.encoding_options == other.encoding_options
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def copy(self):
|
|
60
|
+
return copy.copy(self)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ImageFormat(str):
|
|
64
|
+
def __new__(cls, value, *args, **keywargs):
|
|
65
|
+
if isinstance(value, ImageFormat):
|
|
66
|
+
return value
|
|
67
|
+
return str.__new__(cls, value)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def mime_type(self):
|
|
71
|
+
if self.startswith('image/'):
|
|
72
|
+
return self
|
|
73
|
+
return 'image/' + self
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def ext(self):
|
|
77
|
+
ext = self
|
|
78
|
+
if '/' in ext:
|
|
79
|
+
ext = ext.split('/', 1)[1]
|
|
80
|
+
if ';' in ext:
|
|
81
|
+
ext = ext.split(';', 1)[0]
|
|
82
|
+
|
|
83
|
+
return ext.strip()
|
|
84
|
+
|
|
85
|
+
def __eq__(self, other):
|
|
86
|
+
if isinstance(other, str):
|
|
87
|
+
other = ImageFormat(other)
|
|
88
|
+
elif not isinstance(other, ImageFormat):
|
|
89
|
+
return NotImplemented
|
|
90
|
+
|
|
91
|
+
return self.ext == other.ext
|
|
92
|
+
|
|
93
|
+
def __hash__(self):
|
|
94
|
+
return hash(str(self))
|
|
95
|
+
|
|
96
|
+
def __ne__(self, other):
|
|
97
|
+
return not (self == other)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def create_image(size, image_opts=None):
|
|
101
|
+
"""
|
|
102
|
+
Create a new image that is compatible with the given `image_opts`.
|
|
103
|
+
Takes into account mode, transparent, bgcolor.
|
|
104
|
+
"""
|
|
105
|
+
from mapproxy.compat.image import Image, ImageColor
|
|
106
|
+
|
|
107
|
+
if image_opts is None:
|
|
108
|
+
mode = 'RGB'
|
|
109
|
+
bgcolor = (255, 255, 255)
|
|
110
|
+
else:
|
|
111
|
+
mode = image_opts.mode
|
|
112
|
+
if mode in (None, 'P'):
|
|
113
|
+
if image_opts.transparent:
|
|
114
|
+
mode = 'RGBA'
|
|
115
|
+
else:
|
|
116
|
+
mode = 'RGB'
|
|
117
|
+
|
|
118
|
+
bgcolor = image_opts.bgcolor or (255, 255, 255)
|
|
119
|
+
|
|
120
|
+
if isinstance(bgcolor, str):
|
|
121
|
+
bgcolor = ImageColor.getrgb(bgcolor)
|
|
122
|
+
|
|
123
|
+
if image_opts.transparent and len(bgcolor) == 3:
|
|
124
|
+
bgcolor = bgcolor + (0, )
|
|
125
|
+
|
|
126
|
+
if image_opts.mode == 'I':
|
|
127
|
+
bgcolor = bgcolor[0]
|
|
128
|
+
|
|
129
|
+
return Image.new(mode, size, bgcolor)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ImageFormats(object):
|
|
133
|
+
def __init__(self):
|
|
134
|
+
self.format_options = {}
|
|
135
|
+
|
|
136
|
+
def add(self, opts):
|
|
137
|
+
assert opts.format is not None
|
|
138
|
+
self.format_options[opts.format] = opts
|
|
139
|
+
|
|
140
|
+
def options(self, format):
|
|
141
|
+
opts = self.format_options.get(format)
|
|
142
|
+
if not opts:
|
|
143
|
+
opts = ImageOptions(transparent=False, format=format)
|
|
144
|
+
return opts
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def compatible_image_options(img_opts, base_opts=None):
|
|
148
|
+
"""
|
|
149
|
+
Return ImageOptions that is compatible with all given `img_opts`.
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
if any(True for o in img_opts if o.colors == 0):
|
|
153
|
+
colors = 0
|
|
154
|
+
else:
|
|
155
|
+
colors = max(o.colors or 0 for o in img_opts)
|
|
156
|
+
|
|
157
|
+
transparent = None
|
|
158
|
+
for o in img_opts:
|
|
159
|
+
if o.transparent is False:
|
|
160
|
+
transparent = False
|
|
161
|
+
break
|
|
162
|
+
if o.transparent is True:
|
|
163
|
+
transparent = True
|
|
164
|
+
|
|
165
|
+
if any(True for o in img_opts if o.mode):
|
|
166
|
+
# I < P < RGB < RGBA :)
|
|
167
|
+
mode = max(o.mode for o in img_opts if o.mode)
|
|
168
|
+
else:
|
|
169
|
+
mode = None
|
|
170
|
+
|
|
171
|
+
if base_opts:
|
|
172
|
+
options = base_opts.copy()
|
|
173
|
+
if options.colors is None:
|
|
174
|
+
options.colors = colors
|
|
175
|
+
if options.mode is None:
|
|
176
|
+
options.mode = mode
|
|
177
|
+
if options.transparent is None:
|
|
178
|
+
options.transparent = transparent
|
|
179
|
+
else:
|
|
180
|
+
options = img_opts[0].copy()
|
|
181
|
+
options.colors = colors
|
|
182
|
+
options.transparent = transparent
|
|
183
|
+
options.mode = mode
|
|
184
|
+
|
|
185
|
+
return options
|
mapproxy/image/tile.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
import os
|
|
17
|
+
from mapproxy.image import ImageSource
|
|
18
|
+
from mapproxy.image.transform import ImageTransformer
|
|
19
|
+
from mapproxy.image.opts import create_image
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TileMerger(object):
|
|
26
|
+
"""
|
|
27
|
+
Merge multiple tiles into one image.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, tile_grid, tile_size):
|
|
31
|
+
"""
|
|
32
|
+
:param tile_grid: the grid size
|
|
33
|
+
:type tile_grid: ``(int(x_tiles), int(y_tiles))``
|
|
34
|
+
:param tile_size: the size of each tile
|
|
35
|
+
"""
|
|
36
|
+
self.tile_grid = tile_grid
|
|
37
|
+
self.tile_size = tile_size
|
|
38
|
+
|
|
39
|
+
def merge(self, ordered_tiles, image_opts):
|
|
40
|
+
"""
|
|
41
|
+
Merge all tiles into one image.
|
|
42
|
+
|
|
43
|
+
:param ordered_tiles: list of tiles, sorted row-wise (top to bottom)
|
|
44
|
+
:rtype: `ImageSource`
|
|
45
|
+
"""
|
|
46
|
+
if self.tile_grid == (1, 1):
|
|
47
|
+
assert len(ordered_tiles) == 1
|
|
48
|
+
if ordered_tiles[0] is not None:
|
|
49
|
+
tile = ordered_tiles.pop()
|
|
50
|
+
return tile
|
|
51
|
+
src_size = self._src_size()
|
|
52
|
+
|
|
53
|
+
result = create_image(src_size, image_opts)
|
|
54
|
+
|
|
55
|
+
cacheable = True
|
|
56
|
+
|
|
57
|
+
for i, source in enumerate(ordered_tiles):
|
|
58
|
+
if source is None:
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
if not source.cacheable:
|
|
62
|
+
cacheable = False
|
|
63
|
+
tile = source.as_image()
|
|
64
|
+
pos = self._tile_offset(i)
|
|
65
|
+
tile.draft(image_opts.mode, self.tile_size)
|
|
66
|
+
result.paste(tile, pos)
|
|
67
|
+
source.close_buffers()
|
|
68
|
+
except IOError as e:
|
|
69
|
+
if e.errno is None: # PIL error
|
|
70
|
+
log.warning('unable to load tile %s, removing it (reason was: %s)'
|
|
71
|
+
% (source, str(e)))
|
|
72
|
+
if getattr(source, 'filename'):
|
|
73
|
+
if os.path.exists(source.filename):
|
|
74
|
+
os.remove(source.filename)
|
|
75
|
+
else:
|
|
76
|
+
raise
|
|
77
|
+
return ImageSource(result, size=src_size, image_opts=image_opts, cacheable=cacheable)
|
|
78
|
+
|
|
79
|
+
def _src_size(self):
|
|
80
|
+
width = self.tile_grid[0]*self.tile_size[0]
|
|
81
|
+
height = self.tile_grid[1]*self.tile_size[1]
|
|
82
|
+
return width, height
|
|
83
|
+
|
|
84
|
+
def _tile_offset(self, i):
|
|
85
|
+
"""
|
|
86
|
+
Return the image offset (upper-left coord) of the i-th tile,
|
|
87
|
+
where the tiles are ordered row-wise, top to bottom.
|
|
88
|
+
"""
|
|
89
|
+
return (i % self.tile_grid[0]*self.tile_size[0],
|
|
90
|
+
i//self.tile_grid[0]*self.tile_size[1])
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TileSplitter(object):
|
|
94
|
+
"""
|
|
95
|
+
Splits a large image into multiple tiles.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, meta_tile, image_opts):
|
|
99
|
+
self.meta_img = meta_tile.as_image()
|
|
100
|
+
self.image_opts = image_opts
|
|
101
|
+
|
|
102
|
+
def get_tile(self, crop_coord, tile_size):
|
|
103
|
+
"""
|
|
104
|
+
Return the cropped tile.
|
|
105
|
+
:param crop_coord: the upper left pixel coord to start
|
|
106
|
+
:param tile_size: width and height of the new tile
|
|
107
|
+
:rtype: `ImageSource`
|
|
108
|
+
"""
|
|
109
|
+
minx, miny = crop_coord
|
|
110
|
+
maxx = minx + tile_size[0]
|
|
111
|
+
maxy = miny + tile_size[1]
|
|
112
|
+
|
|
113
|
+
if (minx < 0 or miny < 0 or maxx > self.meta_img.size[0]
|
|
114
|
+
or maxy > self.meta_img.size[1]):
|
|
115
|
+
|
|
116
|
+
crop = self.meta_img.crop((
|
|
117
|
+
max(minx, 0),
|
|
118
|
+
max(miny, 0),
|
|
119
|
+
min(maxx, self.meta_img.size[0]),
|
|
120
|
+
min(maxy, self.meta_img.size[1])))
|
|
121
|
+
result = create_image(tile_size, self.image_opts)
|
|
122
|
+
result.paste(crop, (abs(min(minx, 0)), abs(min(miny, 0))))
|
|
123
|
+
crop = result
|
|
124
|
+
else:
|
|
125
|
+
crop = self.meta_img.crop((minx, miny, maxx, maxy))
|
|
126
|
+
return ImageSource(crop, size=tile_size, image_opts=self.image_opts)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TiledImage(object):
|
|
130
|
+
"""
|
|
131
|
+
An image built-up from multiple tiles.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, tiles, tile_grid, tile_size, src_bbox, src_srs):
|
|
135
|
+
"""
|
|
136
|
+
:param tiles: all tiles (sorted row-wise, top to bottom)
|
|
137
|
+
:param tile_grid: the tile grid size
|
|
138
|
+
:type tile_grid: ``(int(x_tiles), int(y_tiles))``
|
|
139
|
+
:param tile_size: the size of each tile
|
|
140
|
+
:param src_bbox: the bbox of all tiles
|
|
141
|
+
:param src_srs: the srs of the bbox
|
|
142
|
+
:param transparent: if the sources are transparent
|
|
143
|
+
"""
|
|
144
|
+
self.tiles = tiles
|
|
145
|
+
self.tile_grid = tile_grid
|
|
146
|
+
self.tile_size = tile_size
|
|
147
|
+
self.src_bbox = src_bbox
|
|
148
|
+
self.src_srs = src_srs
|
|
149
|
+
|
|
150
|
+
def image(self, image_opts):
|
|
151
|
+
"""
|
|
152
|
+
Return the tiles as one merged image.
|
|
153
|
+
|
|
154
|
+
:rtype: `ImageSource`
|
|
155
|
+
"""
|
|
156
|
+
tm = TileMerger(self.tile_grid, self.tile_size)
|
|
157
|
+
return tm.merge(self.tiles, image_opts=image_opts)
|
|
158
|
+
|
|
159
|
+
def transform(self, req_bbox, req_srs, out_size, image_opts):
|
|
160
|
+
"""
|
|
161
|
+
Return the the tiles as one merged and transformed image.
|
|
162
|
+
|
|
163
|
+
:param req_bbox: the bbox of the output image
|
|
164
|
+
:param req_srs: the srs of the req_bbox
|
|
165
|
+
:param out_size: the size in pixel of the output image
|
|
166
|
+
:rtype: `ImageSource`
|
|
167
|
+
"""
|
|
168
|
+
transformer = ImageTransformer(self.src_srs, req_srs)
|
|
169
|
+
src_img = self.image(image_opts)
|
|
170
|
+
return transformer.transform(src_img, self.src_bbox, out_size, req_bbox,
|
|
171
|
+
image_opts)
|