MapProxy 1.16.1__py3-none-any.whl

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