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
mapproxy/srs.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# This file is part of the MapProxy project.
|
|
3
|
+
# Copyright (C) 2010 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
|
+
"""
|
|
18
|
+
Spatial reference systems and transformation of coordinates.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import division
|
|
21
|
+
|
|
22
|
+
import math
|
|
23
|
+
import threading
|
|
24
|
+
from mapproxy.proj import USE_PROJ4_API
|
|
25
|
+
# Old Proj.4 API
|
|
26
|
+
from mapproxy.proj import Proj, transform, set_datapath
|
|
27
|
+
# New Proj API
|
|
28
|
+
from mapproxy.proj import CRS, Transformer
|
|
29
|
+
from mapproxy.config import base_config
|
|
30
|
+
|
|
31
|
+
import logging
|
|
32
|
+
log_system = logging.getLogger('mapproxy.system')
|
|
33
|
+
log_proj = logging.getLogger('mapproxy.proj')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_epsg_num(epsg_code):
|
|
37
|
+
"""
|
|
38
|
+
>>> get_epsg_num('ePsG:4326')
|
|
39
|
+
4326
|
|
40
|
+
>>> get_epsg_num(4313)
|
|
41
|
+
4313
|
|
42
|
+
>>> get_epsg_num('31466')
|
|
43
|
+
31466
|
|
44
|
+
>>> get_epsg_num('IGNF:ETRS89UTM28') is None
|
|
45
|
+
True
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(epsg_code, str):
|
|
48
|
+
if ':' in epsg_code and epsg_code.upper().startswith('EPSG'):
|
|
49
|
+
epsg_code = int(epsg_code.split(':')[1])
|
|
50
|
+
elif epsg_code.isdigit():
|
|
51
|
+
epsg_code = int(epsg_code)
|
|
52
|
+
else:
|
|
53
|
+
return
|
|
54
|
+
return epsg_code
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_authority(srs_code):
|
|
58
|
+
"""
|
|
59
|
+
>>> get_authority('IAU:1000')
|
|
60
|
+
('IAU', '1000')
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(srs_code, str) and ':' in srs_code:
|
|
63
|
+
auth_name, auth_id = srs_code.rsplit(':', 1)
|
|
64
|
+
return auth_name, auth_id
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _clean_srs_code(code):
|
|
68
|
+
"""
|
|
69
|
+
>>> _clean_srs_code(4326)
|
|
70
|
+
'EPSG:4326'
|
|
71
|
+
>>> _clean_srs_code('31466')
|
|
72
|
+
'EPSG:31466'
|
|
73
|
+
>>> _clean_srs_code('crs:84')
|
|
74
|
+
'CRS:84'
|
|
75
|
+
"""
|
|
76
|
+
if isinstance(code, str) and ':' in code:
|
|
77
|
+
return code.upper()
|
|
78
|
+
else:
|
|
79
|
+
return 'EPSG:' + str(code)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TransformationError(Exception):
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
_proj_initialized = False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _init_proj():
|
|
90
|
+
global _proj_initialized
|
|
91
|
+
if not _proj_initialized and 'proj_data_dir' in base_config().srs:
|
|
92
|
+
proj_data_dir = base_config().srs['proj_data_dir']
|
|
93
|
+
if proj_data_dir is None:
|
|
94
|
+
_proj_initialized = True
|
|
95
|
+
return
|
|
96
|
+
log_system.info('loading proj data from %s', proj_data_dir)
|
|
97
|
+
set_datapath(proj_data_dir)
|
|
98
|
+
_proj_initialized = True
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_thread_local = threading.local()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def SRS(srs_code):
|
|
105
|
+
_init_proj()
|
|
106
|
+
if isinstance(srs_code, _srs_impl):
|
|
107
|
+
return srs_code
|
|
108
|
+
|
|
109
|
+
srs_code = _clean_srs_code(srs_code)
|
|
110
|
+
|
|
111
|
+
if not hasattr(_thread_local, 'srs_cache'):
|
|
112
|
+
_thread_local.srs_cache = {}
|
|
113
|
+
|
|
114
|
+
if srs_code in _thread_local.srs_cache:
|
|
115
|
+
return _thread_local.srs_cache[srs_code]
|
|
116
|
+
else:
|
|
117
|
+
srs = _srs_impl(srs_code)
|
|
118
|
+
_thread_local.srs_cache[srs_code] = srs
|
|
119
|
+
return srs
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
WEBMERCATOR_EPSG = set(('EPSG:900913', 'EPSG:3857',
|
|
123
|
+
'EPSG:102100', 'EPSG:102113'))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class _SRS_Proj4_API(object):
|
|
127
|
+
# http://trac.openlayers.org/wiki/SphericalMercator
|
|
128
|
+
proj_init = {
|
|
129
|
+
'EPSG:4326': lambda: Proj('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +over'),
|
|
130
|
+
'CRS:84': lambda: Proj('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs +over'),
|
|
131
|
+
}
|
|
132
|
+
for _epsg in WEBMERCATOR_EPSG:
|
|
133
|
+
proj_init[_epsg] = lambda: Proj(
|
|
134
|
+
'+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 '
|
|
135
|
+
'+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m '
|
|
136
|
+
'+nadgrids=@null +no_defs +over')
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
This class represents a Spatial Reference System.
|
|
140
|
+
|
|
141
|
+
Abstracts transformations between different projections.
|
|
142
|
+
Uses the old Proj.4 API, either via pyproj 1 or c-types.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(self, srs_code):
|
|
146
|
+
"""
|
|
147
|
+
Create a new SRS with the given `srs_code` code.
|
|
148
|
+
"""
|
|
149
|
+
self.srs_code = srs_code
|
|
150
|
+
|
|
151
|
+
init = self.proj_init.get(srs_code, None)
|
|
152
|
+
if init is not None:
|
|
153
|
+
self.proj = init()
|
|
154
|
+
else:
|
|
155
|
+
epsg_num = get_epsg_num(srs_code)
|
|
156
|
+
if epsg_num is not None:
|
|
157
|
+
self.proj = Proj(init='epsg:%d' % epsg_num)
|
|
158
|
+
else:
|
|
159
|
+
raise ValueError("the old Proj.4 API doesn't support non-EPSG authorities")
|
|
160
|
+
|
|
161
|
+
def transform_to(self, other_srs, points):
|
|
162
|
+
"""
|
|
163
|
+
:type points: ``(x, y)`` or ``[(x1, y1), (x2, y2), …]``
|
|
164
|
+
|
|
165
|
+
>>> srs1 = SRS(4326)
|
|
166
|
+
>>> srs2 = SRS(900913)
|
|
167
|
+
>>> [str(round(x, 5)) for x in srs1.transform_to(srs2, (8.22, 53.15))]
|
|
168
|
+
['915046.21432', '7010792.20171']
|
|
169
|
+
>>> srs1.transform_to(srs1, (8.25, 53.5))
|
|
170
|
+
(8.25, 53.5)
|
|
171
|
+
>>> [(str(round(x, 5)), str(round(y, 5))) for x, y in
|
|
172
|
+
... srs1.transform_to(srs2, [(8.2, 53.1), (8.22, 53.15), (8.3, 53.2)])]
|
|
173
|
+
... #doctest: +NORMALIZE_WHITESPACE
|
|
174
|
+
[('912819.8245', '7001516.67745'),
|
|
175
|
+
('915046.21432', '7010792.20171'),
|
|
176
|
+
('923951.77358', '7020078.53264')]
|
|
177
|
+
"""
|
|
178
|
+
if self == other_srs:
|
|
179
|
+
return points
|
|
180
|
+
if isinstance(points[0], (int, float)) and 2 >= len(points) <= 3:
|
|
181
|
+
return transform(self.proj, other_srs.proj, *points)
|
|
182
|
+
|
|
183
|
+
x = [p[0] for p in points]
|
|
184
|
+
y = [p[1] for p in points]
|
|
185
|
+
transf_pts = transform(self.proj, other_srs.proj, x, y)
|
|
186
|
+
return zip(transf_pts[0], transf_pts[1])
|
|
187
|
+
|
|
188
|
+
def transform_bbox_to(self, other_srs, bbox, with_points=16):
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
:param with_points: the number of points to use for the transformation.
|
|
192
|
+
A bbox transformation with only two or four points may cut off some
|
|
193
|
+
parts due to distortions.
|
|
194
|
+
|
|
195
|
+
>>> ['%.3f' % x for x in
|
|
196
|
+
... SRS(4326).transform_bbox_to(SRS(3857), (-180.0, -90.0, 180.0, 90.0))]
|
|
197
|
+
['-20037508.343', '-147730762.670', '20037508.343', '147730758.195']
|
|
198
|
+
>>> ['%.5f' % x for x in
|
|
199
|
+
... SRS(4326).transform_bbox_to(SRS(3857), (8.2, 53.1, 8.3, 53.2))]
|
|
200
|
+
['912819.82450', '7001516.67745', '923951.77358', '7020078.53264']
|
|
201
|
+
>>> SRS(4326).transform_bbox_to(SRS(4326), (8.25, 53.0, 8.5, 53.75))
|
|
202
|
+
(8.25, 53.0, 8.5, 53.75)
|
|
203
|
+
"""
|
|
204
|
+
if self == other_srs:
|
|
205
|
+
return bbox
|
|
206
|
+
bbox = self.align_bbox(bbox)
|
|
207
|
+
points = generate_envelope_points(bbox, with_points)
|
|
208
|
+
transf_pts = self.transform_to(other_srs, points)
|
|
209
|
+
result = calculate_bbox(transf_pts)
|
|
210
|
+
|
|
211
|
+
log_proj.debug('transformed from %r to %r (%s -> %s)',
|
|
212
|
+
self, other_srs, bbox, result)
|
|
213
|
+
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
def align_bbox(self, bbox):
|
|
217
|
+
"""
|
|
218
|
+
Align bbox to reasonable values to prevent errors in transformations.
|
|
219
|
+
E.g. transformations from EPSG:4326 with lat=90 or -90 will fail, so
|
|
220
|
+
we subtract a tiny delta.
|
|
221
|
+
|
|
222
|
+
At the moment only EPSG:4326 bbox will be modifyed.
|
|
223
|
+
|
|
224
|
+
>>> bbox = SRS(4326).align_bbox((-180, -90, 180, 90))
|
|
225
|
+
>>> -90 < bbox[1] < -89.99999998
|
|
226
|
+
True
|
|
227
|
+
>>> 90 > bbox[3] > 89.99999998
|
|
228
|
+
True
|
|
229
|
+
"""
|
|
230
|
+
# TODO should not be needed anymore since we transform with +over
|
|
231
|
+
# still a few tests depend on the rounding behavior of this
|
|
232
|
+
if self.srs_code == 'EPSG:4326':
|
|
233
|
+
delta = 0.00000001
|
|
234
|
+
(minx, miny, maxx, maxy) = bbox
|
|
235
|
+
if abs(miny - -90.0) < 1e-6:
|
|
236
|
+
miny = -90.0 + delta
|
|
237
|
+
if abs(maxy - 90.0) < 1e-6:
|
|
238
|
+
maxy = 90.0 - delta
|
|
239
|
+
bbox = minx, miny, maxx, maxy
|
|
240
|
+
return bbox
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def is_latlong(self):
|
|
244
|
+
"""
|
|
245
|
+
>>> SRS(4326).is_latlong
|
|
246
|
+
True
|
|
247
|
+
>>> SRS(31466).is_latlong
|
|
248
|
+
False
|
|
249
|
+
"""
|
|
250
|
+
return self.proj.is_latlong()
|
|
251
|
+
|
|
252
|
+
def get_geographic_srs(self):
|
|
253
|
+
""" Return the "canonical" geographic CRS corresponding to this CRS.
|
|
254
|
+
Always EPSG:4326 for Proj4 implementation """
|
|
255
|
+
return SRS(4326)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def is_axis_order_ne(self):
|
|
259
|
+
"""
|
|
260
|
+
Returns `True` if the axis order is North, then East
|
|
261
|
+
(i.e. y/x or lat/lon).
|
|
262
|
+
|
|
263
|
+
>>> SRS(4326).is_axis_order_ne
|
|
264
|
+
True
|
|
265
|
+
>>> SRS('CRS:84').is_axis_order_ne
|
|
266
|
+
False
|
|
267
|
+
>>> SRS(31468).is_axis_order_ne
|
|
268
|
+
True
|
|
269
|
+
>>> SRS(31463).is_axis_order_ne
|
|
270
|
+
False
|
|
271
|
+
>>> SRS(25831).is_axis_order_ne
|
|
272
|
+
False
|
|
273
|
+
"""
|
|
274
|
+
if self.srs_code in base_config().srs.axis_order_ne:
|
|
275
|
+
return True
|
|
276
|
+
if self.srs_code in base_config().srs.axis_order_en:
|
|
277
|
+
return False
|
|
278
|
+
if self.is_latlong:
|
|
279
|
+
return True
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def is_axis_order_en(self):
|
|
284
|
+
"""
|
|
285
|
+
Returns `True` if the axis order is East then North
|
|
286
|
+
(i.e. x/y or lon/lat).
|
|
287
|
+
"""
|
|
288
|
+
return not self.is_axis_order_ne
|
|
289
|
+
|
|
290
|
+
def __eq__(self, other):
|
|
291
|
+
"""
|
|
292
|
+
>>> SRS(4326) == SRS("EpsG:4326")
|
|
293
|
+
True
|
|
294
|
+
>>> SRS(4326) == SRS("4326")
|
|
295
|
+
True
|
|
296
|
+
>>> SRS(4326) == SRS(3857)
|
|
297
|
+
False
|
|
298
|
+
"""
|
|
299
|
+
if isinstance(other, _SRS_Proj4_API):
|
|
300
|
+
return self.proj.srs == other.proj.srs
|
|
301
|
+
else:
|
|
302
|
+
return NotImplemented
|
|
303
|
+
|
|
304
|
+
def __ne__(self, other):
|
|
305
|
+
"""
|
|
306
|
+
>>> SRS(3857) != SRS(3857)
|
|
307
|
+
False
|
|
308
|
+
>>> SRS(4326) != SRS(900913)
|
|
309
|
+
True
|
|
310
|
+
"""
|
|
311
|
+
equal_result = self.__eq__(other)
|
|
312
|
+
if equal_result is NotImplemented:
|
|
313
|
+
return NotImplemented
|
|
314
|
+
else:
|
|
315
|
+
return not equal_result
|
|
316
|
+
|
|
317
|
+
def __str__(self):
|
|
318
|
+
return "SRS %s ('%s')" % (self.srs_code, self.proj.srs)
|
|
319
|
+
|
|
320
|
+
def __repr__(self):
|
|
321
|
+
"""
|
|
322
|
+
>>> repr(SRS(4326))
|
|
323
|
+
"SRS('EPSG:4326')"
|
|
324
|
+
"""
|
|
325
|
+
return "SRS('%s')" % (self.srs_code,)
|
|
326
|
+
|
|
327
|
+
def __hash__(self):
|
|
328
|
+
return hash(self.srs_code)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class _SRS(object):
|
|
332
|
+
"""
|
|
333
|
+
This class represents a Spatial Reference System.
|
|
334
|
+
|
|
335
|
+
Abstracts transformations between different projections.
|
|
336
|
+
Uses the new Proj API via pyproj >=2.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, srs_code):
|
|
340
|
+
"""
|
|
341
|
+
Create a new SRS with the given `srs_code` code.
|
|
342
|
+
"""
|
|
343
|
+
self.srs_code = srs_code
|
|
344
|
+
|
|
345
|
+
if srs_code in WEBMERCATOR_EPSG:
|
|
346
|
+
epsg_num = 3857
|
|
347
|
+
elif srs_code == 'CRS:84':
|
|
348
|
+
epsg_num = 4326
|
|
349
|
+
else:
|
|
350
|
+
epsg_num = get_epsg_num(srs_code)
|
|
351
|
+
|
|
352
|
+
if epsg_num is not None:
|
|
353
|
+
self.proj = CRS.from_epsg(epsg_num)
|
|
354
|
+
else:
|
|
355
|
+
auth_name, auth_id = get_authority(srs_code)
|
|
356
|
+
self.proj = CRS.from_authority(auth_name, auth_id)
|
|
357
|
+
|
|
358
|
+
self._transformers = {}
|
|
359
|
+
|
|
360
|
+
def _transformer(self, other_srs):
|
|
361
|
+
if other_srs in self._transformers:
|
|
362
|
+
return self._transformers[other_srs]
|
|
363
|
+
|
|
364
|
+
t = Transformer.from_crs(self.proj, other_srs.proj, always_xy=True)
|
|
365
|
+
self._transformers[other_srs] = t
|
|
366
|
+
return t
|
|
367
|
+
|
|
368
|
+
def transform_to(self, other_srs, points):
|
|
369
|
+
"""
|
|
370
|
+
:type points: ``(x, y)`` or ``[(x1, y1), (x2, y2), …]``
|
|
371
|
+
|
|
372
|
+
>>> srs1 = SRS(4326)
|
|
373
|
+
>>> srs2 = SRS(900913)
|
|
374
|
+
>>> [str(round(x, 5)) for x in srs1.transform_to(srs2, (8.22, 53.15))]
|
|
375
|
+
['915046.21432', '7010792.20171']
|
|
376
|
+
>>> srs1.transform_to(srs1, (8.25, 53.5))
|
|
377
|
+
(8.25, 53.5)
|
|
378
|
+
>>> [(str(round(x, 5)), str(round(y, 5))) for x, y in
|
|
379
|
+
... srs1.transform_to(srs2, [(8.2, 53.1), (8.22, 53.15), (8.3, 53.2)])]
|
|
380
|
+
... #doctest: +NORMALIZE_WHITESPACE
|
|
381
|
+
[('912819.8245', '7001516.67745'),
|
|
382
|
+
('915046.21432', '7010792.20171'),
|
|
383
|
+
('923951.77358', '7020078.53264')]
|
|
384
|
+
"""
|
|
385
|
+
if self == other_srs:
|
|
386
|
+
return points
|
|
387
|
+
|
|
388
|
+
transformer = self._transformer(other_srs)
|
|
389
|
+
if isinstance(points[0], (int, float)) and 2 >= len(points) <= 3:
|
|
390
|
+
return transformer.transform(*points)
|
|
391
|
+
|
|
392
|
+
x = [p[0] for p in points]
|
|
393
|
+
y = [p[1] for p in points]
|
|
394
|
+
transf_pts = transformer.transform(x, y)
|
|
395
|
+
return zip(transf_pts[0], transf_pts[1])
|
|
396
|
+
|
|
397
|
+
def transform_bbox_to(self, other_srs, bbox, with_points=16):
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
:param with_points: the number of points to use for the transformation.
|
|
401
|
+
A bbox transformation with only two or four points may cut off some
|
|
402
|
+
parts due to distortions.
|
|
403
|
+
|
|
404
|
+
>>> ['%.3f' % x for x in
|
|
405
|
+
... SRS(4326).transform_bbox_to(SRS(3857), (-180.0, -90.0, 180.0, 90.0))]
|
|
406
|
+
['-20037508.343', '-20037508.343', '20037508.343', '20037508.343']
|
|
407
|
+
>>> ['%.5f' % x for x in
|
|
408
|
+
... SRS(4326).transform_bbox_to(SRS(3857), (8.2, 53.1, 8.3, 53.2))]
|
|
409
|
+
['912819.82450', '7001516.67745', '923951.77358', '7020078.53264']
|
|
410
|
+
>>> SRS(4326).transform_bbox_to(SRS(4326), (8.25, 53.0, 8.5, 53.75))
|
|
411
|
+
(8.25, 53.0, 8.5, 53.75)
|
|
412
|
+
"""
|
|
413
|
+
if self == other_srs:
|
|
414
|
+
return bbox
|
|
415
|
+
bbox = bbox
|
|
416
|
+
points = generate_envelope_points(bbox, with_points)
|
|
417
|
+
transf_pts = list(self.transform_to(other_srs, points))
|
|
418
|
+
result = calculate_bbox(transf_pts)
|
|
419
|
+
|
|
420
|
+
log_proj.debug('transformed from %r to %r (%s -> %s)',
|
|
421
|
+
self, other_srs, bbox, result)
|
|
422
|
+
|
|
423
|
+
# XXX: 3857 is only defined within 85.06 N/S, new Proj returns 'inf' for coords
|
|
424
|
+
# outside of these bounds. Adjust bbox for 4326->3857 transformations to the old
|
|
425
|
+
# behavior, as this is expected in a few places (WMS layer extents and quite a few
|
|
426
|
+
# tests).
|
|
427
|
+
if self.srs_code == 'EPSG:4326' and other_srs.srs_code in ('EPSG:3857', 'EPSG:900913'):
|
|
428
|
+
minx, miny, maxx, maxy = result
|
|
429
|
+
if bbox[0] <= -180.0:
|
|
430
|
+
minx = -20037508.342789244
|
|
431
|
+
if bbox[1] <= -85.06:
|
|
432
|
+
miny = -20037508.342789244
|
|
433
|
+
if bbox[2] >= 180.0:
|
|
434
|
+
maxx = 20037508.342789244
|
|
435
|
+
if bbox[3] >= 85.06:
|
|
436
|
+
maxy = 20037508.342789244
|
|
437
|
+
result = (minx, miny, maxx, maxy)
|
|
438
|
+
return result
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def is_latlong(self):
|
|
442
|
+
"""
|
|
443
|
+
>>> SRS(4326).is_latlong
|
|
444
|
+
True
|
|
445
|
+
>>> SRS(31466).is_latlong
|
|
446
|
+
False
|
|
447
|
+
"""
|
|
448
|
+
return self.proj.is_geographic
|
|
449
|
+
|
|
450
|
+
def get_geographic_srs(self):
|
|
451
|
+
""" Return the "canonical" geographic CRS corresponding to this CRS.
|
|
452
|
+
EPSG:4326 for Earth CRS, or another one from other celestial bodies """
|
|
453
|
+
auth = self.proj.to_authority()
|
|
454
|
+
if auth is None or not auth[0].startswith('IAU'):
|
|
455
|
+
ret = SRS(4326)
|
|
456
|
+
else:
|
|
457
|
+
return _SRS(':'.join(self.proj.geodetic_crs.to_authority()))
|
|
458
|
+
return ret
|
|
459
|
+
|
|
460
|
+
@property
|
|
461
|
+
def is_axis_order_ne(self):
|
|
462
|
+
"""
|
|
463
|
+
Returns `True` if the axis order is North, then East
|
|
464
|
+
(i.e. y/x or lat/lon).
|
|
465
|
+
|
|
466
|
+
>>> SRS(4326).is_axis_order_ne
|
|
467
|
+
True
|
|
468
|
+
>>> SRS('CRS:84').is_axis_order_ne
|
|
469
|
+
False
|
|
470
|
+
>>> SRS(31468).is_axis_order_ne
|
|
471
|
+
True
|
|
472
|
+
>>> SRS(31463).is_axis_order_ne
|
|
473
|
+
False
|
|
474
|
+
>>> SRS(25831).is_axis_order_ne
|
|
475
|
+
False
|
|
476
|
+
"""
|
|
477
|
+
if self.srs_code == 'CRS:84':
|
|
478
|
+
return False
|
|
479
|
+
return self.proj.axis_info[0].direction == 'north'
|
|
480
|
+
|
|
481
|
+
@property
|
|
482
|
+
def is_axis_order_en(self):
|
|
483
|
+
"""
|
|
484
|
+
Returns `True` if the axis order is East then North
|
|
485
|
+
(i.e. x/y or lon/lat).
|
|
486
|
+
"""
|
|
487
|
+
return not self.is_axis_order_ne
|
|
488
|
+
|
|
489
|
+
def align_bbox(self, bbox):
|
|
490
|
+
"""
|
|
491
|
+
Align bbox to reasonable values to prevent errors in transformations.
|
|
492
|
+
E.g. transformations from EPSG:4326 with lat=90 or -90 will fail, so
|
|
493
|
+
we subtract a tiny delta.
|
|
494
|
+
|
|
495
|
+
At the moment only EPSG:4326 bbox will be modifyed.
|
|
496
|
+
|
|
497
|
+
>>> bbox = SRS(4326).align_bbox((-180, -90, 180, 90))
|
|
498
|
+
>>> -90 < bbox[1] < -89.99999998
|
|
499
|
+
True
|
|
500
|
+
>>> 90 > bbox[3] > 89.99999998
|
|
501
|
+
True
|
|
502
|
+
"""
|
|
503
|
+
# TODO should not be needed anymore since we transform with +over
|
|
504
|
+
# still a few tests depend on the rounding behavior of this
|
|
505
|
+
if self.srs_code == 'EPSG:4326':
|
|
506
|
+
delta = 0.00000001
|
|
507
|
+
(minx, miny, maxx, maxy) = bbox
|
|
508
|
+
if abs(miny - -90.0) < 1e-6:
|
|
509
|
+
miny = -90.0 + delta
|
|
510
|
+
if abs(maxy - 90.0) < 1e-6:
|
|
511
|
+
maxy = 90.0 - delta
|
|
512
|
+
bbox = minx, miny, maxx, maxy
|
|
513
|
+
return bbox
|
|
514
|
+
|
|
515
|
+
def __eq__(self, other):
|
|
516
|
+
"""
|
|
517
|
+
>>> SRS(4326) == SRS("EpsG:4326")
|
|
518
|
+
True
|
|
519
|
+
>>> SRS(4326) == SRS("4326")
|
|
520
|
+
True
|
|
521
|
+
>>> SRS(4326) == SRS(3857)
|
|
522
|
+
False
|
|
523
|
+
"""
|
|
524
|
+
if isinstance(other, _SRS):
|
|
525
|
+
return self.proj.srs == other.proj.srs
|
|
526
|
+
else:
|
|
527
|
+
return NotImplemented
|
|
528
|
+
|
|
529
|
+
def __ne__(self, other):
|
|
530
|
+
"""
|
|
531
|
+
>>> SRS(3857) != SRS(3857)
|
|
532
|
+
False
|
|
533
|
+
>>> SRS(4326) != SRS(900913)
|
|
534
|
+
True
|
|
535
|
+
"""
|
|
536
|
+
equal_result = self.__eq__(other)
|
|
537
|
+
if equal_result is NotImplemented:
|
|
538
|
+
return NotImplemented
|
|
539
|
+
else:
|
|
540
|
+
return not equal_result
|
|
541
|
+
|
|
542
|
+
def __str__(self):
|
|
543
|
+
return "SRS %s ('%s')" % (self.srs_code, self.proj.srs)
|
|
544
|
+
|
|
545
|
+
def __repr__(self):
|
|
546
|
+
"""
|
|
547
|
+
>>> repr(SRS(4326))
|
|
548
|
+
"SRS('EPSG:4326')"
|
|
549
|
+
"""
|
|
550
|
+
return "SRS('%s')" % (self.srs_code,)
|
|
551
|
+
|
|
552
|
+
def __hash__(self):
|
|
553
|
+
return hash(self.srs_code)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
if USE_PROJ4_API:
|
|
557
|
+
_srs_impl = _SRS_Proj4_API
|
|
558
|
+
del _SRS
|
|
559
|
+
else:
|
|
560
|
+
_srs_impl = _SRS
|
|
561
|
+
del _SRS_Proj4_API
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def generate_envelope_points(bbox, n):
|
|
565
|
+
"""
|
|
566
|
+
Generates points that form a linestring around a given bbox.
|
|
567
|
+
|
|
568
|
+
@param bbox: bbox to generate linestring for
|
|
569
|
+
@param n: the number of points to generate around the bbox
|
|
570
|
+
|
|
571
|
+
>>> generate_envelope_points((10.0, 5.0, 20.0, 15.0), 4)
|
|
572
|
+
[(10.0, 5.0), (20.0, 5.0), (20.0, 15.0), (10.0, 15.0)]
|
|
573
|
+
>>> generate_envelope_points((10.0, 5.0, 20.0, 15.0), 8)
|
|
574
|
+
... #doctest: +NORMALIZE_WHITESPACE
|
|
575
|
+
[(10.0, 5.0), (15.0, 5.0), (20.0, 5.0), (20.0, 10.0),\
|
|
576
|
+
(20.0, 15.0), (15.0, 15.0), (10.0, 15.0), (10.0, 10.0)]
|
|
577
|
+
"""
|
|
578
|
+
(minx, miny, maxx, maxy) = bbox
|
|
579
|
+
if n <= 4:
|
|
580
|
+
n = 0
|
|
581
|
+
else:
|
|
582
|
+
n = int(math.ceil((n - 4) / 4.0))
|
|
583
|
+
|
|
584
|
+
width = maxx - minx
|
|
585
|
+
height = maxy - miny
|
|
586
|
+
|
|
587
|
+
minx, maxx = min(minx, maxx), max(minx, maxx)
|
|
588
|
+
miny, maxy = min(miny, maxy), max(miny, maxy)
|
|
589
|
+
|
|
590
|
+
n += 1
|
|
591
|
+
xstep = width / n
|
|
592
|
+
ystep = height / n
|
|
593
|
+
result = []
|
|
594
|
+
for i in range(n+1):
|
|
595
|
+
result.append((minx + i*xstep, miny))
|
|
596
|
+
for i in range(1, n):
|
|
597
|
+
result.append((maxx, miny + i*ystep))
|
|
598
|
+
for i in range(n, -1, -1):
|
|
599
|
+
result.append((minx + i*xstep, maxy))
|
|
600
|
+
for i in range(n-1, 0, -1):
|
|
601
|
+
result.append((minx, miny + i*ystep))
|
|
602
|
+
return result
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def calculate_bbox(points):
|
|
606
|
+
"""
|
|
607
|
+
Calculates the bbox of a list of points.
|
|
608
|
+
|
|
609
|
+
>>> calculate_bbox([(-5, 20), (3, 8), (99, 0)])
|
|
610
|
+
(-5, 0, 99, 20)
|
|
611
|
+
|
|
612
|
+
@param points: list of points [(x0, y0), (x1, y2), ...]
|
|
613
|
+
@returns: bbox of the input points.
|
|
614
|
+
"""
|
|
615
|
+
points = list(points)
|
|
616
|
+
# points can be INF for invalid transformations, filter out
|
|
617
|
+
try:
|
|
618
|
+
minx = min(p[0] for p in points if p[0] != float('inf'))
|
|
619
|
+
miny = min(p[1] for p in points if p[1] != float('inf'))
|
|
620
|
+
maxx = max(p[0] for p in points if p[0] != float('inf'))
|
|
621
|
+
maxy = max(p[1] for p in points if p[1] != float('inf'))
|
|
622
|
+
return (minx, miny, maxx, maxy)
|
|
623
|
+
except ValueError: # min/max are called with empty list when everything is inf
|
|
624
|
+
raise TransformationError()
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def merge_bbox(bbox1, bbox2):
|
|
628
|
+
"""
|
|
629
|
+
Merge two bboxes.
|
|
630
|
+
|
|
631
|
+
>>> merge_bbox((-10, 20, 0, 30), (30, -20, 90, 10))
|
|
632
|
+
(-10, -20, 90, 30)
|
|
633
|
+
|
|
634
|
+
"""
|
|
635
|
+
minx = min(bbox1[0], bbox2[0])
|
|
636
|
+
miny = min(bbox1[1], bbox2[1])
|
|
637
|
+
maxx = max(bbox1[2], bbox2[2])
|
|
638
|
+
maxy = max(bbox1[3], bbox2[3])
|
|
639
|
+
return (minx, miny, maxx, maxy)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def bbox_equals(src_bbox, dst_bbox, x_delta=None, y_delta=None):
|
|
643
|
+
"""
|
|
644
|
+
Compares two bbox and checks if they are equal, or nearly equal.
|
|
645
|
+
|
|
646
|
+
:param x_delta: how precise the comparison should be.
|
|
647
|
+
should be reasonable small, like a tenth of a pixel.
|
|
648
|
+
defaults to 1/1.000.000th of the width.
|
|
649
|
+
:type x_delta: bbox units
|
|
650
|
+
|
|
651
|
+
>>> src_bbox = (939258.20356824622, 6887893.4928338043,
|
|
652
|
+
... 1095801.2374962866, 7044436.5267618448)
|
|
653
|
+
>>> dst_bbox = (939258.20260000182, 6887893.4908000007,
|
|
654
|
+
... 1095801.2365000017, 7044436.5247000009)
|
|
655
|
+
>>> bbox_equals(src_bbox, dst_bbox, 61.1, 61.1)
|
|
656
|
+
True
|
|
657
|
+
>>> bbox_equals(src_bbox, dst_bbox, 0.0001)
|
|
658
|
+
False
|
|
659
|
+
"""
|
|
660
|
+
if x_delta is None:
|
|
661
|
+
x_delta = abs(src_bbox[0] - src_bbox[2]) / 1000000.0
|
|
662
|
+
if y_delta is None:
|
|
663
|
+
y_delta = x_delta
|
|
664
|
+
return (abs(src_bbox[0] - dst_bbox[0]) < x_delta and
|
|
665
|
+
abs(src_bbox[1] - dst_bbox[1]) < x_delta and
|
|
666
|
+
abs(src_bbox[2] - dst_bbox[2]) < y_delta and
|
|
667
|
+
abs(src_bbox[3] - dst_bbox[3]) < y_delta)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def make_lin_transf(src_bbox, dst_bbox):
|
|
671
|
+
"""
|
|
672
|
+
Create a transformation function that transforms linear between two
|
|
673
|
+
plane coordinate systems.
|
|
674
|
+
One needs to be cartesian (0, 0 at the lower left, x goes up) and one
|
|
675
|
+
needs to be an image coordinate system (0, 0 at the top left, x goes down).
|
|
676
|
+
|
|
677
|
+
:return: function that takes src x/y and returns dest x/y coordinates
|
|
678
|
+
|
|
679
|
+
>>> transf = make_lin_transf((7, 50, 8, 51), (0, 0, 500, 400))
|
|
680
|
+
>>> transf((7.5, 50.5))
|
|
681
|
+
(250.0, 200.0)
|
|
682
|
+
>>> transf((7.0, 50.0))
|
|
683
|
+
(0.0, 400.0)
|
|
684
|
+
>>> transf = make_lin_transf((7, 50, 8, 51), (200, 300, 700, 700))
|
|
685
|
+
>>> transf((7.5, 50.5))
|
|
686
|
+
(450.0, 500.0)
|
|
687
|
+
"""
|
|
688
|
+
def func(x_y): return (dst_bbox[0] + (x_y[0] - src_bbox[0]) *
|
|
689
|
+
(dst_bbox[2]-dst_bbox[0]) / (src_bbox[2] - src_bbox[0]),
|
|
690
|
+
dst_bbox[1] + (src_bbox[3] - x_y[1]) *
|
|
691
|
+
(dst_bbox[3]-dst_bbox[1]) / (src_bbox[3] - src_bbox[1]))
|
|
692
|
+
return func
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class PreferredSrcSRS(object):
|
|
696
|
+
def __init__(self):
|
|
697
|
+
self.target_proj = {}
|
|
698
|
+
|
|
699
|
+
def add(self, target, prefered_srs):
|
|
700
|
+
self.target_proj[target] = prefered_srs
|
|
701
|
+
|
|
702
|
+
def preferred_src(self, target, available_src):
|
|
703
|
+
if not available_src:
|
|
704
|
+
raise ValueError("no available src SRS")
|
|
705
|
+
if target in available_src:
|
|
706
|
+
return target
|
|
707
|
+
if target in self.target_proj:
|
|
708
|
+
for preferred in self.target_proj[target]:
|
|
709
|
+
if preferred in available_src:
|
|
710
|
+
return preferred
|
|
711
|
+
|
|
712
|
+
for avail in available_src:
|
|
713
|
+
if avail.is_latlong == target.is_latlong:
|
|
714
|
+
return avail
|
|
715
|
+
return available_src[0]
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
class SupportedSRS(object):
|
|
719
|
+
def __init__(self, supported_srs, preferred_srs=None):
|
|
720
|
+
self.supported_srs = supported_srs
|
|
721
|
+
self.preferred_srs = preferred_srs or PreferredSrcSRS()
|
|
722
|
+
|
|
723
|
+
def __iter__(self):
|
|
724
|
+
return iter(self.supported_srs)
|
|
725
|
+
|
|
726
|
+
def __contains__(self, srs):
|
|
727
|
+
return srs in self.supported_srs
|
|
728
|
+
|
|
729
|
+
def best_srs(self, target):
|
|
730
|
+
return self.preferred_srs.preferred_src(target, self.supported_srs)
|
|
731
|
+
|
|
732
|
+
def __eq__(self, other):
|
|
733
|
+
# .prefered_srs is set global, so we only compare .supported_srs
|
|
734
|
+
return self.supported_srs == other.supported_srs
|