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.
Files changed (459) hide show
  1. MapProxy-2.1.0.dist-info/AUTHORS.txt +33 -0
  2. MapProxy-2.1.0.dist-info/COPYING.txt +60 -0
  3. MapProxy-2.1.0.dist-info/LICENSE.txt +202 -0
  4. MapProxy-2.1.0.dist-info/METADATA +165 -0
  5. MapProxy-2.1.0.dist-info/RECORD +459 -0
  6. MapProxy-2.1.0.dist-info/WHEEL +5 -0
  7. MapProxy-2.1.0.dist-info/entry_points.txt +4 -0
  8. MapProxy-2.1.0.dist-info/top_level.txt +1 -0
  9. mapproxy/__init__.py +0 -0
  10. mapproxy/cache/__init__.py +36 -0
  11. mapproxy/cache/azureblob.py +145 -0
  12. mapproxy/cache/base.py +120 -0
  13. mapproxy/cache/compact.py +665 -0
  14. mapproxy/cache/couchdb.py +301 -0
  15. mapproxy/cache/dummy.py +36 -0
  16. mapproxy/cache/file.py +200 -0
  17. mapproxy/cache/geopackage.py +647 -0
  18. mapproxy/cache/legend.py +87 -0
  19. mapproxy/cache/mbtiles.py +411 -0
  20. mapproxy/cache/meta.py +80 -0
  21. mapproxy/cache/path.py +261 -0
  22. mapproxy/cache/redis.py +152 -0
  23. mapproxy/cache/renderd.py +100 -0
  24. mapproxy/cache/riak.py +206 -0
  25. mapproxy/cache/s3.py +209 -0
  26. mapproxy/cache/tile.py +736 -0
  27. mapproxy/client/__init__.py +0 -0
  28. mapproxy/client/arcgis.py +82 -0
  29. mapproxy/client/cgi.py +141 -0
  30. mapproxy/client/http.py +295 -0
  31. mapproxy/client/log.py +33 -0
  32. mapproxy/client/tile.py +158 -0
  33. mapproxy/client/wms.py +255 -0
  34. mapproxy/compat/__init__.py +0 -0
  35. mapproxy/compat/image.py +86 -0
  36. mapproxy/config/__init__.py +22 -0
  37. mapproxy/config/config-schema.json +813 -0
  38. mapproxy/config/config.py +213 -0
  39. mapproxy/config/coverage.py +108 -0
  40. mapproxy/config/defaults.py +102 -0
  41. mapproxy/config/loader.py +2399 -0
  42. mapproxy/config/spec.py +657 -0
  43. mapproxy/config/validator.py +242 -0
  44. mapproxy/config_template/__init__.py +0 -0
  45. mapproxy/config_template/base_config/config.wsgi +10 -0
  46. mapproxy/config_template/base_config/full_example.yaml +598 -0
  47. mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
  48. mapproxy/config_template/base_config/log.ini +35 -0
  49. mapproxy/config_template/base_config/mapproxy.yaml +60 -0
  50. mapproxy/config_template/base_config/seed.yaml +27 -0
  51. mapproxy/exception.py +149 -0
  52. mapproxy/featureinfo.py +251 -0
  53. mapproxy/grid.py +1199 -0
  54. mapproxy/image/__init__.py +549 -0
  55. mapproxy/image/fonts/DejaVuSans.ttf +0 -0
  56. mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
  57. mapproxy/image/fonts/LICENSE +99 -0
  58. mapproxy/image/fonts/__init__.py +0 -0
  59. mapproxy/image/mask.py +79 -0
  60. mapproxy/image/merge.py +323 -0
  61. mapproxy/image/message.py +357 -0
  62. mapproxy/image/opts.py +185 -0
  63. mapproxy/image/tile.py +171 -0
  64. mapproxy/image/transform.py +350 -0
  65. mapproxy/layer.py +489 -0
  66. mapproxy/multiapp.py +230 -0
  67. mapproxy/proj.py +309 -0
  68. mapproxy/request/__init__.py +18 -0
  69. mapproxy/request/arcgis.py +268 -0
  70. mapproxy/request/base.py +466 -0
  71. mapproxy/request/tile.py +131 -0
  72. mapproxy/request/wms/__init__.py +829 -0
  73. mapproxy/request/wms/exception.py +107 -0
  74. mapproxy/request/wmts.py +442 -0
  75. mapproxy/response.py +237 -0
  76. mapproxy/script/__init__.py +0 -0
  77. mapproxy/script/conf/__init__.py +0 -0
  78. mapproxy/script/conf/app.py +222 -0
  79. mapproxy/script/conf/caches.py +44 -0
  80. mapproxy/script/conf/geopackage.py +136 -0
  81. mapproxy/script/conf/layers.py +54 -0
  82. mapproxy/script/conf/seeds.py +36 -0
  83. mapproxy/script/conf/sources.py +88 -0
  84. mapproxy/script/conf/utils.py +148 -0
  85. mapproxy/script/defrag.py +187 -0
  86. mapproxy/script/export.py +337 -0
  87. mapproxy/script/grids.py +198 -0
  88. mapproxy/script/scales.py +134 -0
  89. mapproxy/script/util.py +410 -0
  90. mapproxy/script/wms_capabilities.py +160 -0
  91. mapproxy/seed/__init__.py +0 -0
  92. mapproxy/seed/cachelock.py +127 -0
  93. mapproxy/seed/cleanup.py +191 -0
  94. mapproxy/seed/config.py +481 -0
  95. mapproxy/seed/script.py +391 -0
  96. mapproxy/seed/seeder.py +551 -0
  97. mapproxy/seed/spec.py +66 -0
  98. mapproxy/seed/util.py +266 -0
  99. mapproxy/service/__init__.py +14 -0
  100. mapproxy/service/base.py +45 -0
  101. mapproxy/service/demo.py +364 -0
  102. mapproxy/service/kml.py +333 -0
  103. mapproxy/service/ows.py +39 -0
  104. mapproxy/service/template_helper.py +55 -0
  105. mapproxy/service/templates/demo/capabilities_demo.html +18 -0
  106. mapproxy/service/templates/demo/demo.html +181 -0
  107. mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
  108. mapproxy/service/templates/demo/static/img/blank.gif +0 -0
  109. mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
  110. mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
  111. mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
  112. mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
  113. mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
  114. mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
  115. mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
  116. mapproxy/service/templates/demo/static/logo.png +0 -0
  117. mapproxy/service/templates/demo/static/ol.css +345 -0
  118. mapproxy/service/templates/demo/static/ol.js +4 -0
  119. mapproxy/service/templates/demo/static/proj4.min.js +1 -0
  120. mapproxy/service/templates/demo/static/proj4defs.js +1 -0
  121. mapproxy/service/templates/demo/static/site.css +137 -0
  122. mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
  123. mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
  124. mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
  125. mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
  126. mapproxy/service/templates/demo/static.html +34 -0
  127. mapproxy/service/templates/demo/tms_demo.html +117 -0
  128. mapproxy/service/templates/demo/wms_demo.html +144 -0
  129. mapproxy/service/templates/demo/wmts_demo.html +118 -0
  130. mapproxy/service/templates/tms_capabilities.xml +13 -0
  131. mapproxy/service/templates/tms_exception.xml +4 -0
  132. mapproxy/service/templates/tms_root_resource.xml +7 -0
  133. mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
  134. mapproxy/service/templates/wms100capabilities.xml +112 -0
  135. mapproxy/service/templates/wms100exception.xml +4 -0
  136. mapproxy/service/templates/wms110capabilities.xml +152 -0
  137. mapproxy/service/templates/wms110exception.xml +5 -0
  138. mapproxy/service/templates/wms111capabilities.xml +183 -0
  139. mapproxy/service/templates/wms111exception.xml +5 -0
  140. mapproxy/service/templates/wms130capabilities.xml +326 -0
  141. mapproxy/service/templates/wms130exception.xml +8 -0
  142. mapproxy/service/templates/wmts100capabilities.xml +155 -0
  143. mapproxy/service/templates/wmts100exception.xml +9 -0
  144. mapproxy/service/tile.py +540 -0
  145. mapproxy/service/wms.py +868 -0
  146. mapproxy/service/wmts.py +387 -0
  147. mapproxy/source/__init__.py +83 -0
  148. mapproxy/source/arcgis.py +39 -0
  149. mapproxy/source/error.py +40 -0
  150. mapproxy/source/mapnik.py +262 -0
  151. mapproxy/source/tile.py +97 -0
  152. mapproxy/source/wms.py +273 -0
  153. mapproxy/srs.py +734 -0
  154. mapproxy/template.py +54 -0
  155. mapproxy/test/__init__.py +0 -0
  156. mapproxy/test/conftest.py +8 -0
  157. mapproxy/test/helper.py +255 -0
  158. mapproxy/test/http.py +511 -0
  159. mapproxy/test/image.py +219 -0
  160. mapproxy/test/mocker.py +2291 -0
  161. mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
  162. mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
  163. mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
  164. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
  165. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
  166. mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
  167. mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
  168. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
  169. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
  170. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
  171. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
  172. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
  173. mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
  174. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
  175. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
  176. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
  177. mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
  178. mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
  179. mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
  180. mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
  181. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
  182. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
  183. mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
  184. mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
  185. mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
  186. mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
  187. mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
  188. mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
  189. mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
  190. mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
  191. mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
  192. mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
  193. mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
  194. mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
  195. mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
  196. mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
  197. mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
  198. mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
  199. mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
  200. mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
  201. mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
  202. mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
  203. mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
  204. mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
  205. mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
  206. mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
  207. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
  208. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
  209. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
  210. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
  211. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
  212. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
  213. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
  214. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
  215. mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
  216. mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  217. mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
  218. mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  219. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
  220. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
  221. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
  222. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
  223. mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
  224. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
  225. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
  226. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
  227. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
  228. mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
  229. mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  230. mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
  231. mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  232. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
  233. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
  234. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
  235. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
  236. mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
  237. mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
  238. mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
  239. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
  240. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
  241. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
  242. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
  243. mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
  244. mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
  245. mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
  246. mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
  247. mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
  248. mapproxy/test/schemas/xml.xsd +287 -0
  249. mapproxy/test/system/__init__.py +98 -0
  250. mapproxy/test/system/fixture/arcgis.yaml +57 -0
  251. mapproxy/test/system/fixture/auth.yaml +70 -0
  252. mapproxy/test/system/fixture/cache.mbtiles +0 -0
  253. mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
  254. mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
  255. mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
  256. mapproxy/test/system/fixture/cache_coverage.yaml +84 -0
  257. mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
  258. mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
  259. mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
  260. mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
  261. mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
  262. mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
  263. mapproxy/test/system/fixture/cache_s3.yaml +58 -0
  264. mapproxy/test/system/fixture/cache_source.yaml +81 -0
  265. mapproxy/test/system/fixture/combined_sources.yaml +130 -0
  266. mapproxy/test/system/fixture/coverage.yaml +77 -0
  267. mapproxy/test/system/fixture/demo.yaml +135 -0
  268. mapproxy/test/system/fixture/dimension.yaml +59 -0
  269. mapproxy/test/system/fixture/disable_storage.yaml +25 -0
  270. mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
  271. mapproxy/test/system/fixture/formats.yaml +72 -0
  272. mapproxy/test/system/fixture/inspire.yaml +101 -0
  273. mapproxy/test/system/fixture/inspire_full.yaml +124 -0
  274. mapproxy/test/system/fixture/kml_layer.yaml +66 -0
  275. mapproxy/test/system/fixture/layer.yaml +260 -0
  276. mapproxy/test/system/fixture/layergroups.yaml +57 -0
  277. mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
  278. mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
  279. mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
  280. mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
  281. mapproxy/test/system/fixture/mapserver.yaml +23 -0
  282. mapproxy/test/system/fixture/minimal_cgi.py +16 -0
  283. mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
  284. mapproxy/test/system/fixture/multi_cache_layers.yaml +111 -0
  285. mapproxy/test/system/fixture/multiapp1.yaml +20 -0
  286. mapproxy/test/system/fixture/multiapp2.yaml +19 -0
  287. mapproxy/test/system/fixture/renderd_client.yaml +55 -0
  288. mapproxy/test/system/fixture/scalehints.yaml +70 -0
  289. mapproxy/test/system/fixture/seed.yaml +94 -0
  290. mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
  291. mapproxy/test/system/fixture/seed_old.yaml +12 -0
  292. mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
  293. mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
  294. mapproxy/test/system/fixture/seedonly.yaml +51 -0
  295. mapproxy/test/system/fixture/sld.yaml +35 -0
  296. mapproxy/test/system/fixture/source_errors.yaml +84 -0
  297. mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
  298. mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
  299. mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
  300. mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
  301. mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
  302. mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
  303. mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
  304. mapproxy/test/system/fixture/util_grids.yaml +29 -0
  305. mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
  306. mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
  307. mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
  308. mapproxy/test/system/fixture/watermark.yaml +50 -0
  309. mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
  310. mapproxy/test/system/fixture/wms_versions.yaml +38 -0
  311. mapproxy/test/system/fixture/wmts.yaml +134 -0
  312. mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
  313. mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
  314. mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
  315. mapproxy/test/system/test_arcgis.py +156 -0
  316. mapproxy/test/system/test_auth.py +1133 -0
  317. mapproxy/test/system/test_behind_proxy.py +75 -0
  318. mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
  319. mapproxy/test/system/test_cache_azureblob.py +127 -0
  320. mapproxy/test/system/test_cache_band_merge.py +103 -0
  321. mapproxy/test/system/test_cache_coverage.py +168 -0
  322. mapproxy/test/system/test_cache_geopackage.py +144 -0
  323. mapproxy/test/system/test_cache_grid_names.py +89 -0
  324. mapproxy/test/system/test_cache_mbtiles.py +85 -0
  325. mapproxy/test/system/test_cache_s3.py +115 -0
  326. mapproxy/test/system/test_cache_source.py +146 -0
  327. mapproxy/test/system/test_combined_sources.py +335 -0
  328. mapproxy/test/system/test_coverage.py +140 -0
  329. mapproxy/test/system/test_decorate_img.py +214 -0
  330. mapproxy/test/system/test_demo.py +56 -0
  331. mapproxy/test/system/test_demo_with_extra_service.py +57 -0
  332. mapproxy/test/system/test_dimensions.py +279 -0
  333. mapproxy/test/system/test_disable_storage.py +42 -0
  334. mapproxy/test/system/test_formats.py +219 -0
  335. mapproxy/test/system/test_inspire_vs.py +173 -0
  336. mapproxy/test/system/test_kml.py +264 -0
  337. mapproxy/test/system/test_layergroups.py +160 -0
  338. mapproxy/test/system/test_legendgraphic.py +308 -0
  339. mapproxy/test/system/test_mapnik.py +162 -0
  340. mapproxy/test/system/test_mapserver.py +83 -0
  341. mapproxy/test/system/test_mixed_mode_format.py +201 -0
  342. mapproxy/test/system/test_multi_cache_layers.py +169 -0
  343. mapproxy/test/system/test_multiapp.py +92 -0
  344. mapproxy/test/system/test_refresh.py +206 -0
  345. mapproxy/test/system/test_renderd_client.py +304 -0
  346. mapproxy/test/system/test_response_headers.py +54 -0
  347. mapproxy/test/system/test_scalehints.py +140 -0
  348. mapproxy/test/system/test_seed.py +425 -0
  349. mapproxy/test/system/test_seed_only.py +93 -0
  350. mapproxy/test/system/test_sld.py +120 -0
  351. mapproxy/test/system/test_source_errors.py +377 -0
  352. mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
  353. mapproxy/test/system/test_tms.py +277 -0
  354. mapproxy/test/system/test_tms_origin.py +46 -0
  355. mapproxy/test/system/test_util_conf.py +434 -0
  356. mapproxy/test/system/test_util_export.py +210 -0
  357. mapproxy/test/system/test_util_grids.py +88 -0
  358. mapproxy/test/system/test_util_wms_capabilities.py +182 -0
  359. mapproxy/test/system/test_watermark.py +91 -0
  360. mapproxy/test/system/test_wms.py +1616 -0
  361. mapproxy/test/system/test_wms_srs_extent.py +165 -0
  362. mapproxy/test/system/test_wms_version.py +85 -0
  363. mapproxy/test/system/test_wmsc.py +116 -0
  364. mapproxy/test/system/test_wmts.py +334 -0
  365. mapproxy/test/system/test_wmts_dimensions.py +206 -0
  366. mapproxy/test/system/test_wmts_restful.py +198 -0
  367. mapproxy/test/system/test_xslt_featureinfo.py +423 -0
  368. mapproxy/test/test_http_helper.py +217 -0
  369. mapproxy/test/unit/__init__.py +0 -0
  370. mapproxy/test/unit/epsg +2 -0
  371. mapproxy/test/unit/polygons/polygons.dbf +0 -0
  372. mapproxy/test/unit/polygons/polygons.shp +0 -0
  373. mapproxy/test/unit/polygons/polygons.shx +0 -0
  374. mapproxy/test/unit/test_async.py +242 -0
  375. mapproxy/test/unit/test_auth.py +430 -0
  376. mapproxy/test/unit/test_cache.py +1356 -0
  377. mapproxy/test/unit/test_cache_azureblob.py +97 -0
  378. mapproxy/test/unit/test_cache_compact.py +324 -0
  379. mapproxy/test/unit/test_cache_couchdb.py +118 -0
  380. mapproxy/test/unit/test_cache_geopackage.py +256 -0
  381. mapproxy/test/unit/test_cache_redis.py +123 -0
  382. mapproxy/test/unit/test_cache_riak.py +80 -0
  383. mapproxy/test/unit/test_cache_s3.py +93 -0
  384. mapproxy/test/unit/test_cache_tile.py +477 -0
  385. mapproxy/test/unit/test_client.py +488 -0
  386. mapproxy/test/unit/test_client_arcgis.py +74 -0
  387. mapproxy/test/unit/test_client_cgi.py +140 -0
  388. mapproxy/test/unit/test_collections.py +116 -0
  389. mapproxy/test/unit/test_concat_legends.py +37 -0
  390. mapproxy/test/unit/test_conf_loader.py +1267 -0
  391. mapproxy/test/unit/test_conf_validator.py +427 -0
  392. mapproxy/test/unit/test_config.py +118 -0
  393. mapproxy/test/unit/test_decorate_img.py +185 -0
  394. mapproxy/test/unit/test_exceptions.py +270 -0
  395. mapproxy/test/unit/test_featureinfo.py +313 -0
  396. mapproxy/test/unit/test_file_lock_load.py +49 -0
  397. mapproxy/test/unit/test_geom.py +512 -0
  398. mapproxy/test/unit/test_grid.py +1279 -0
  399. mapproxy/test/unit/test_image.py +1051 -0
  400. mapproxy/test/unit/test_image_mask.py +181 -0
  401. mapproxy/test/unit/test_image_messages.py +209 -0
  402. mapproxy/test/unit/test_image_options.py +160 -0
  403. mapproxy/test/unit/test_isodate.py +118 -0
  404. mapproxy/test/unit/test_multiapp.py +163 -0
  405. mapproxy/test/unit/test_ogr_reader.py +51 -0
  406. mapproxy/test/unit/test_request.py +745 -0
  407. mapproxy/test/unit/test_request_wmts.py +178 -0
  408. mapproxy/test/unit/test_response.py +78 -0
  409. mapproxy/test/unit/test_seed.py +365 -0
  410. mapproxy/test/unit/test_seed_cachelock.py +91 -0
  411. mapproxy/test/unit/test_srs.py +215 -0
  412. mapproxy/test/unit/test_tiled_source.py +122 -0
  413. mapproxy/test/unit/test_tilefilter.py +31 -0
  414. mapproxy/test/unit/test_times.py +25 -0
  415. mapproxy/test/unit/test_timeutils.py +50 -0
  416. mapproxy/test/unit/test_util_conf_utils.py +75 -0
  417. mapproxy/test/unit/test_utils.py +476 -0
  418. mapproxy/test/unit/test_wms_capabilities.py +44 -0
  419. mapproxy/test/unit/test_wms_layer.py +113 -0
  420. mapproxy/test/unit/test_yaml.py +68 -0
  421. mapproxy/tilefilter.py +61 -0
  422. mapproxy/util/__init__.py +0 -0
  423. mapproxy/util/async_.py +229 -0
  424. mapproxy/util/collections.py +134 -0
  425. mapproxy/util/coverage.py +337 -0
  426. mapproxy/util/ext/__init__.py +14 -0
  427. mapproxy/util/ext/dictspec/__init__.py +1 -0
  428. mapproxy/util/ext/dictspec/spec.py +131 -0
  429. mapproxy/util/ext/dictspec/test/__init__.py +0 -0
  430. mapproxy/util/ext/dictspec/test/test_validator.py +278 -0
  431. mapproxy/util/ext/dictspec/validator.py +194 -0
  432. mapproxy/util/ext/local.py +198 -0
  433. mapproxy/util/ext/lockfile.py +140 -0
  434. mapproxy/util/ext/odict.py +321 -0
  435. mapproxy/util/ext/serving.py +491 -0
  436. mapproxy/util/ext/tempita/__init__.py +1093 -0
  437. mapproxy/util/ext/tempita/_looper.py +163 -0
  438. mapproxy/util/ext/tempita/string_utils.py +24 -0
  439. mapproxy/util/ext/wmsparse/__init__.py +3 -0
  440. mapproxy/util/ext/wmsparse/duration.py +600 -0
  441. mapproxy/util/ext/wmsparse/parse.py +307 -0
  442. mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
  443. mapproxy/util/ext/wmsparse/test/test_parse.py +111 -0
  444. mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
  445. mapproxy/util/ext/wmsparse/test/wms-example-111.xml +90 -0
  446. mapproxy/util/ext/wmsparse/test/wms-example-130.xml +120 -0
  447. mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
  448. mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
  449. mapproxy/util/ext/wmsparse/util.py +189 -0
  450. mapproxy/util/fs.py +164 -0
  451. mapproxy/util/geom.py +307 -0
  452. mapproxy/util/lib.py +117 -0
  453. mapproxy/util/lock.py +171 -0
  454. mapproxy/util/ogr.py +247 -0
  455. mapproxy/util/py.py +75 -0
  456. mapproxy/util/times.py +78 -0
  457. mapproxy/util/yaml.py +58 -0
  458. mapproxy/version.py +33 -0
  459. mapproxy/wsgiapp.py +167 -0
@@ -0,0 +1,1616 @@
1
+ # This file is part of the MapProxy project.
2
+ # Copyright (C) 2010 Omniscale <http://omniscale.de>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from __future__ import print_function, division
17
+
18
+ import re
19
+ import sys
20
+ import functools
21
+
22
+ from io import BytesIO
23
+
24
+ import pytest
25
+
26
+ from mapproxy.image import ImageSource
27
+ from mapproxy.srs import SRS
28
+ from mapproxy.compat.image import Image
29
+ from mapproxy.request.wms import (
30
+ WMS100MapRequest,
31
+ WMS111MapRequest,
32
+ WMS130MapRequest,
33
+ WMS111FeatureInfoRequest,
34
+ WMS111CapabilitiesRequest,
35
+ WMS130CapabilitiesRequest,
36
+ WMS100CapabilitiesRequest,
37
+ WMS100FeatureInfoRequest,
38
+ WMS130FeatureInfoRequest,
39
+ WMS110MapRequest,
40
+ WMS110FeatureInfoRequest,
41
+ WMS110CapabilitiesRequest,
42
+ wms_request,
43
+ )
44
+ from mapproxy.test.image import is_jpeg, is_png, tmp_image, create_tmp_image
45
+ from mapproxy.test.unit.test_image import assert_geotiff_tags
46
+ from mapproxy.test.http import mock_httpd
47
+ from mapproxy.test.helper import validate_with_dtd, validate_with_xsd
48
+ from mapproxy.test.system import SysTest
49
+
50
+
51
+ @pytest.fixture(scope="module")
52
+ def config_file():
53
+ return "layer.yaml"
54
+
55
+
56
+ class TestBase(SysTest):
57
+
58
+ def test_invalid_url(self, app):
59
+ app.get("/invalid?fop", status=404)
60
+
61
+
62
+ class TestCoverageWMS(SysTest):
63
+ config_file = "layer.yaml"
64
+
65
+ def test_unknown_version_110(self, app):
66
+ resp = app.get(
67
+ "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities"
68
+ "&VERSION=1.1.0"
69
+ )
70
+ assert is_110_capa(resp.lxml)
71
+
72
+ def test_unknown_version_113(self, app):
73
+ resp = app.get(
74
+ "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities"
75
+ "&VERSION=1.1.3"
76
+ )
77
+ assert is_111_capa(resp.lxml)
78
+
79
+ def test_unknown_version_090(self, app):
80
+ resp = app.get(
81
+ "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities"
82
+ "&WMTVER=0.9.0"
83
+ )
84
+ assert is_100_capa(resp.lxml)
85
+
86
+ def test_unknown_version_200(self, app):
87
+ resp = app.get(
88
+ "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities"
89
+ "&VERSION=2.0.0"
90
+ )
91
+ assert is_130_capa(resp.lxml)
92
+
93
+
94
+ def bbox_srs_from_boundingbox(bbox_elem):
95
+ return [
96
+ float(bbox_elem.attrib["minx"]),
97
+ float(bbox_elem.attrib["miny"]),
98
+ float(bbox_elem.attrib["maxx"]),
99
+ float(bbox_elem.attrib["maxy"]),
100
+ ]
101
+
102
+
103
+ class TestWMS111(SysTest):
104
+ config_file = "layer.yaml"
105
+
106
+ def setup_method(self):
107
+ # WMSTest.setup_method(self)
108
+ self.common_req = WMS111MapRequest(
109
+ url="/service?", param=dict(service="WMS", version="1.1.1")
110
+ )
111
+ self.common_map_req = WMS111MapRequest(
112
+ url="/service?",
113
+ param=dict(
114
+ service="WMS",
115
+ version="1.1.1",
116
+ bbox="-180,0,0,80",
117
+ width="200",
118
+ height="200",
119
+ layers="wms_cache",
120
+ srs="EPSG:4326",
121
+ format="image/png",
122
+ styles="",
123
+ request="GetMap",
124
+ ),
125
+ )
126
+ self.common_fi_req = WMS111FeatureInfoRequest(
127
+ url="/service?",
128
+ param=dict(
129
+ x="10",
130
+ y="20",
131
+ width="200",
132
+ height="200",
133
+ layers="wms_cache",
134
+ format="image/png",
135
+ query_layers="wms_cache",
136
+ styles="",
137
+ bbox="1000,400,2000,1400",
138
+ srs="EPSG:900913",
139
+ ),
140
+ )
141
+
142
+ def test_invalid_request_type(self, app):
143
+ req = str(self.common_map_req).replace("GetMap", "invalid")
144
+ resp = app.get(req)
145
+ is_111_exception(resp.lxml, "unknown WMS request type 'invalid'")
146
+
147
+ def test_endpoints(self, app):
148
+ for endpoint in ("service", "ows", "wms"):
149
+ req = WMS111CapabilitiesRequest(
150
+ url="/%s?" % endpoint
151
+ ).copy_with_request_params(self.common_req)
152
+ resp = app.get(req)
153
+ assert resp.content_type == "application/vnd.ogc.wms_xml"
154
+ xml = resp.lxml
155
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd")
156
+
157
+ def test_wms_capabilities(self, app):
158
+ req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params(
159
+ self.common_req
160
+ )
161
+ resp = app.get(req)
162
+ assert resp.content_type == "application/vnd.ogc.wms_xml"
163
+ xml = resp.lxml
164
+ assert (
165
+ xml.xpath(
166
+ "//GetMap//OnlineResource/@xlink:href",
167
+ namespaces=dict(xlink="http://www.w3.org/1999/xlink"),
168
+ )[0] ==
169
+ "http://localhost/service?"
170
+ )
171
+
172
+ # test for MetadataURL
173
+ assert (
174
+ xml.xpath(
175
+ "//Layer/MetadataURL/OnlineResource/@xlink:href",
176
+ namespaces=dict(xlink="http://www.w3.org/1999/xlink"),
177
+ )[0] ==
178
+ "http://some.url/"
179
+ )
180
+ assert xml.xpath("//Layer/MetadataURL/@type")[0] == "TC211"
181
+
182
+ layer_names = set(xml.xpath("//Layer/Layer/Name/text()"))
183
+ expected_names = set(
184
+ [
185
+ "direct_fwd_params",
186
+ "direct",
187
+ "wms_cache",
188
+ "wms_cache_100",
189
+ "wms_cache_130",
190
+ "wms_cache_transparent",
191
+ "wms_merge",
192
+ "tms_cache",
193
+ "tms_fi_cache",
194
+ "wms_cache_multi",
195
+ "wms_cache_link_single",
196
+ "wms_cache_110",
197
+ "watermark_cache",
198
+ "wms_managed_cookies_cache",
199
+ ]
200
+ )
201
+ assert layer_names == expected_names
202
+ assert set(xml.xpath("//Layer/Layer[3]/Abstract/text()")) == set(["Some abstract"])
203
+
204
+ bboxs = xml.xpath("//Layer/Layer[1]/BoundingBox")
205
+ bboxs = dict((e.attrib["SRS"], e) for e in bboxs)
206
+ assert_almost_equal_bbox(
207
+ bbox_srs_from_boundingbox(bboxs["EPSG:3857"]),
208
+ [-20037508.3428, -15538711.0963, 18924313.4349, 15538711.0963],
209
+ )
210
+ assert_almost_equal_bbox(
211
+ bbox_srs_from_boundingbox(bboxs["EPSG:4326"]), [-180.0, -70.0, 170.0, 80.0]
212
+ )
213
+
214
+ bbox_srs = xml.xpath("//Layer/Layer/BoundingBox")
215
+ bbox_srs = set(e.attrib["SRS"] for e in bbox_srs)
216
+ # we have a coverage in EPSG:4258, but it is not in wms.srs (#288)
217
+ assert "EPSG:4258" not in bbox_srs
218
+
219
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd")
220
+
221
+ def test_invalid_layer(self, app):
222
+ self.common_map_req.params["layers"] = "invalid"
223
+ resp = app.get(self.common_map_req)
224
+ assert resp.content_type == "application/vnd.ogc.se_xml"
225
+ is_111_exception(resp.lxml, "unknown layer: invalid", "LayerNotDefined")
226
+
227
+ def test_invalid_layer_img_exception(self, app):
228
+ self.common_map_req.params["layers"] = "invalid"
229
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
230
+ resp = app.get(self.common_map_req)
231
+ assert resp.content_type == "image/png"
232
+ assert is_png(BytesIO(resp.body))
233
+
234
+ def test_invalid_format(self, app):
235
+ self.common_map_req.params["format"] = "image/ascii"
236
+ resp = app.get(self.common_map_req)
237
+ assert resp.content_type == "application/vnd.ogc.se_xml"
238
+ is_111_exception(
239
+ resp.lxml, "unsupported image format: image/ascii", "InvalidFormat"
240
+ )
241
+
242
+ def test_invalid_format_img_exception(self, app):
243
+ self.common_map_req.params["format"] = "image/ascii"
244
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
245
+ resp = app.get(self.common_map_req)
246
+ assert resp.content_type == "image/png"
247
+ assert is_png(BytesIO(resp.body))
248
+
249
+ def test_invalid_format_options_img_exception(self, app):
250
+ self.common_map_req.params["format"] = "image/png; mode=12bit"
251
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
252
+ resp = app.get(self.common_map_req)
253
+ assert resp.content_type == "image/png"
254
+ assert is_png(BytesIO(resp.body))
255
+
256
+ def test_missing_format_img_exception(self, app):
257
+ del self.common_map_req.params["format"]
258
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
259
+ resp = app.get(self.common_map_req)
260
+ assert resp.content_type == "image/png"
261
+ assert is_png(BytesIO(resp.body))
262
+
263
+ def test_invalid_srs(self, app):
264
+ self.common_map_req.params["srs"] = "EPSG:1234"
265
+ resp = app.get(self.common_map_req)
266
+ assert resp.content_type == "application/vnd.ogc.se_xml"
267
+ is_111_exception(resp.lxml, "unsupported srs: EPSG:1234", "InvalidSRS")
268
+
269
+ def test_get_map_unknown_style(self, app):
270
+ self.common_map_req.params["styles"] = "unknown"
271
+ resp = app.get(self.common_map_req)
272
+ assert resp.content_type == "application/vnd.ogc.se_xml"
273
+ is_111_exception(resp.lxml, "unsupported styles: unknown", "StyleNotDefined")
274
+
275
+ def test_get_map_too_large(self, app):
276
+ self.common_map_req.params.size = (5000, 5000)
277
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
278
+ resp = app.get(self.common_map_req)
279
+ # is xml, even if inimage was requested
280
+ assert resp.content_type == "application/vnd.ogc.se_xml"
281
+ is_111_exception(resp.lxml, "image size too large")
282
+
283
+ def test_get_map_default_style(self, app, fixture_cache_data):
284
+ self.common_map_req.params["styles"] = "default"
285
+ resp = app.get(self.common_map_req)
286
+ assert resp.content_type == "image/png"
287
+ data = BytesIO(resp.body)
288
+ assert is_png(data)
289
+ assert Image.open(data).mode == "RGB"
290
+
291
+ def test_get_map_png(self, app, fixture_cache_data):
292
+ resp = app.get(self.common_map_req)
293
+ assert "Cache-Control" not in resp.headers
294
+ assert resp.content_type == "image/png"
295
+ data = BytesIO(resp.body)
296
+ assert is_png(data)
297
+ assert Image.open(data).mode == "RGB"
298
+
299
+ def test_get_map_float_size(self, app, fixture_cache_data):
300
+ self.common_map_req.params['width'] = '200.0'
301
+ resp = app.get(self.common_map_req)
302
+ assert "Cache-Control" not in resp.headers
303
+ assert resp.content_type == "image/png"
304
+ data = BytesIO(resp.body)
305
+ assert is_png(data)
306
+ assert Image.open(data).mode == "RGB"
307
+
308
+ def test_get_map_png8_custom_format(self, app, fixture_cache_data):
309
+ self.common_map_req.params["layers"] = "wms_cache"
310
+ self.common_map_req.params["format"] = "image/png; mode=8bit"
311
+ resp = app.get(self.common_map_req)
312
+ assert resp.headers["Content-type"] == "image/png; mode=8bit"
313
+ data = BytesIO(resp.body)
314
+ assert is_png(data)
315
+ img = Image.open(data)
316
+ assert img.mode == "P"
317
+
318
+ def test_get_map_png_transparent_non_transparent_data(
319
+ self, app, fixture_cache_data
320
+ ):
321
+ self.common_map_req.params["transparent"] = "True"
322
+ resp = app.get(self.common_map_req)
323
+ assert resp.content_type == "image/png"
324
+ data = BytesIO(resp.body)
325
+ assert is_png(data)
326
+ img = Image.open(data)
327
+ assert img.mode == "RGB"
328
+
329
+ def test_get_map_png_transparent(self, app, fixture_cache_data):
330
+ self.common_map_req.params["layers"] = "wms_cache_transparent"
331
+ self.common_map_req.params["transparent"] = "True"
332
+ resp = app.get(self.common_map_req)
333
+ assert resp.content_type == "image/png"
334
+ data = BytesIO(resp.body)
335
+ assert is_png(data)
336
+ assert Image.open(data).mode == "RGBA"
337
+
338
+ def test_get_map_png_w_default_bgcolor(self, app, fixture_cache_data):
339
+ self.common_map_req.params["layers"] = "wms_cache_transparent"
340
+ resp = app.get(self.common_map_req)
341
+ assert resp.content_type == "image/png"
342
+ data = BytesIO(resp.body)
343
+ assert is_png(data)
344
+ img = Image.open(data)
345
+ assert img.mode == "RGB"
346
+ assert img.getcolors()[0][1] == (255, 255, 255)
347
+
348
+ def test_get_map_png_w_bgcolor(self, app, fixture_cache_data):
349
+ self.common_map_req.params["layers"] = "wms_cache_transparent"
350
+ self.common_map_req.params["bgcolor"] = "0xff00a0"
351
+ resp = app.get(self.common_map_req)
352
+ assert resp.content_type == "image/png"
353
+ data = BytesIO(resp.body)
354
+ assert is_png(data)
355
+ img = Image.open(data)
356
+ assert img.mode == "RGB"
357
+ assert sorted(img.getcolors())[-1][1] == (255, 0, 160)
358
+
359
+ def test_get_map_jpeg(self, app, fixture_cache_data):
360
+ self.common_map_req.params["format"] = "image/jpeg"
361
+ resp = app.get(self.common_map_req)
362
+ assert resp.content_type == "image/jpeg"
363
+ assert is_jpeg(BytesIO(resp.body))
364
+
365
+ def test_get_map_geotiff(self, app, fixture_cache_data):
366
+ self.common_map_req.params["format"] = "image/tiff"
367
+ resp = app.get(self.common_map_req)
368
+ assert resp.content_type == "image/tiff"
369
+ img = ImageSource(BytesIO(resp.body)).as_image()
370
+ assert_geotiff_tags(img, (-180, 80), (180/200.0, 80/200.0), 4326, False)
371
+
372
+ def test_get_map_xml_exception(self, app):
373
+ self.common_map_req.params["bbox"] = "0,0,90,90"
374
+ resp = app.get(self.common_map_req)
375
+ assert resp.content_type == "application/vnd.ogc.se_xml"
376
+ xml = resp.lxml
377
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
378
+ assert "No response from URL" in xml.xpath("//ServiceException/text()")[0]
379
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
380
+
381
+ def test_direct_layer_error(self, app):
382
+ self.common_map_req.params["layers"] = "direct"
383
+ resp = app.get(self.common_map_req)
384
+ assert resp.content_type == "application/vnd.ogc.se_xml"
385
+ xml = resp.lxml
386
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
387
+ # TODO hide error
388
+ # assert 'unable to get map for layers: direct' in \
389
+ # xml.xpath('//ServiceException/text()')[0]
390
+ assert "No response from URL" in xml.xpath("//ServiceException/text()")[0]
391
+
392
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
393
+
394
+ def test_direct_layer_non_image_response(self, app):
395
+ self.common_map_req.params["layers"] = "direct"
396
+ expected_req = (
397
+ {
398
+ "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng"
399
+ "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles="
400
+ "&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0"
401
+ "&WIDTH=200"
402
+ },
403
+ {"body": b"notanimage", "headers": {"content-type": "image/jpeg"}},
404
+ )
405
+ with mock_httpd(("localhost", 42423), [expected_req]):
406
+ resp = app.get(self.common_map_req)
407
+ assert resp.content_type == "application/vnd.ogc.se_xml"
408
+ xml = resp.lxml
409
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
410
+ assert (
411
+ "error while processing image file"
412
+ in xml.xpath("//ServiceException/text()")[0]
413
+ )
414
+
415
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
416
+
417
+ def test_get_map_non_image_response(self, app, cache_dir):
418
+ expected_req = (
419
+ {
420
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
421
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
422
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
423
+ "&WIDTH=256"
424
+ },
425
+ {"body": b"notanimage", "headers": {"content-type": "image/jpeg"}},
426
+ )
427
+ with mock_httpd(
428
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
429
+ ):
430
+ self.common_map_req.params["bbox"] = "0,0,180,90"
431
+ resp = app.get(self.common_map_req)
432
+ assert resp.content_type == "application/vnd.ogc.se_xml"
433
+
434
+ xml = resp.lxml
435
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
436
+ assert (
437
+ "unable to transform image: cannot identify image file"
438
+ in xml.xpath("//ServiceException/text()")[0]
439
+ )
440
+
441
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
442
+
443
+ assert cache_dir.join(
444
+ "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg"
445
+ ).check()
446
+
447
+ @pytest.mark.flaky(reruns=5, reruns_delay=2)
448
+ def test_get_map(self, app, base_dir, cache_dir):
449
+ # check global tile lock directory
450
+ tiles_lock_dir = base_dir.join("wmscachetilelockdir")
451
+ # make sure global tile_lock_dir was ot created by other tests
452
+ if tiles_lock_dir.check():
453
+ tiles_lock_dir.remove()
454
+
455
+ with tmp_image((256, 256), format="jpeg") as img:
456
+ expected_req = (
457
+ {
458
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
459
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
460
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
461
+ "&WIDTH=256"
462
+ },
463
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
464
+ )
465
+ with mock_httpd(
466
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
467
+ ):
468
+ self.common_map_req.params["bbox"] = "0,0,180,90"
469
+ resp = app.get(self.common_map_req)
470
+ assert 35000 < int(resp.headers["Content-length"]) < 75000
471
+ assert resp.content_type == "image/png"
472
+
473
+ # check custom tile_lock_dir
474
+ assert cache_dir.join(
475
+ "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg"
476
+ ).check()
477
+ assert tiles_lock_dir.check()
478
+
479
+ def test_get_map_direct_fwd_params_layer(self, app):
480
+ img = create_tmp_image((200, 200), format="png")
481
+ expected_req = (
482
+ {
483
+ "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng"
484
+ "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles="
485
+ "&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0"
486
+ "&WIDTH=200&TIME=20041012"
487
+ },
488
+ {"body": img},
489
+ )
490
+ with mock_httpd(
491
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
492
+ ):
493
+ self.common_map_req.params["layers"] = "direct_fwd_params"
494
+ self.common_map_req.params["time"] = "20041012"
495
+ resp = app.get(self.common_map_req)
496
+ assert resp.content_type == "image/png"
497
+
498
+ def test_get_map_use_direct_from_level(self, app):
499
+ with tmp_image((200, 200), format="png") as img:
500
+ expected_req = (
501
+ {
502
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
503
+ "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles="
504
+ "&VERSION=1.1.1&BBOX=5.0,-10.0,6.0,-9.0"
505
+ "&WIDTH=200"
506
+ },
507
+ {"body": img.read(), "headers": {"content-type": "image/png"}},
508
+ )
509
+ with mock_httpd(
510
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
511
+ ):
512
+ self.common_map_req.params["bbox"] = "5,-10,6,-9"
513
+ resp = app.get(self.common_map_req)
514
+ img.seek(0)
515
+ assert resp.body == img.read()
516
+ is_png(img)
517
+ assert resp.content_type == "image/png"
518
+
519
+ def test_get_map_use_direct_from_level_with_transform(self, app):
520
+ with tmp_image((200, 200), format="png") as img:
521
+ expected_req = (
522
+ {
523
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
524
+ "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A900913&styles="
525
+ "&VERSION=1.1.1&BBOX=908822.944624,7004479.85652,920282.144964,7014491.63726"
526
+ "&WIDTH=229"
527
+ },
528
+ {"body": img.read(), "headers": {"content-type": "image/png"}},
529
+ )
530
+ with mock_httpd(
531
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
532
+ ):
533
+ self.common_map_req.params[
534
+ "bbox"
535
+ ] = "444122.311736,5885498.04243,450943.508884,5891425.10484"
536
+ self.common_map_req.params["srs"] = "EPSG:25832"
537
+ resp = app.get(self.common_map_req)
538
+ img.seek(0)
539
+ assert resp.body != img.read()
540
+ is_png(img)
541
+ assert resp.content_type == "image/png"
542
+
543
+ def test_get_map_invalid_bbox(self, app):
544
+ # min x larger than max x
545
+ url = (
546
+ """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=7,2,-9,10&SRS=EPSG:4326&WIDTH=164&HEIGHT=388&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" # noqa
547
+ )
548
+ resp = app.get(url)
549
+ is_111_exception(resp.lxml, "invalid bbox 7,2,-9,10")
550
+
551
+ def test_get_map_invalid_bbox2(self, app):
552
+ # broken bbox for the requested srs
553
+ url = (
554
+ """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=-72988843.697212,-255661507.634227,142741550.188860,255661507.634227&SRS=EPSG:25833&WIDTH=164&HEIGHT=388&LAYERS=wms_cache_100&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" # noqa
555
+ )
556
+ resp = app.get(url)
557
+ # result depends on proj version
558
+ is_111_exception(
559
+ resp.lxml,
560
+ re_msg="Request too large or invalid BBOX.|Could not transform BBOX: Invalid result.",
561
+ )
562
+
563
+ def test_get_map_broken_bbox(self, app):
564
+ url = (
565
+ """/service?VERSION=1.1.11&REQUEST=GetMap&SRS=EPSG:31468&BBOX=-20000855.0573254,2847125.18913603,-19329367.42767611,4239924.78564583&WIDTH=130&HEIGHT=62&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" # noqa
566
+ )
567
+ resp = app.get(url)
568
+ is_111_exception(resp.lxml, "Could not transform BBOX: Invalid result.")
569
+
570
+ def test_get_map100(self, app, base_dir, cache_dir):
571
+ # check global tile lock directory
572
+ tiles_lock_dir = base_dir.join("defaulttilelockdir")
573
+ # make sure global tile_lock_dir was ot created by other tests
574
+ if tiles_lock_dir.check():
575
+ tiles_lock_dir.remove()
576
+
577
+ # request_format tiff, cache format jpeg, wms request in png
578
+ with tmp_image((256, 256), format="tiff") as img:
579
+ expected_req = (
580
+ {
581
+ "path": r"/service?LAYERs=foo,bar&FORMAT=TIFF"
582
+ "&REQUEST=map&HEIGHT=256&SRS=EPSG%3A900913&styles="
583
+ "&WMTVER=1.0.0&BBOX=0.0,0.0,20037508.3428,20037508.3428"
584
+ "&WIDTH=256"
585
+ },
586
+ {"body": img.read(), "headers": {"content-type": "image/tiff"}},
587
+ )
588
+ with mock_httpd(
589
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
590
+ ):
591
+ self.common_map_req.params["bbox"] = "0,0,180,90"
592
+ self.common_map_req.params["layers"] = "wms_cache_100"
593
+ resp = app.get(self.common_map_req)
594
+ assert resp.content_type == "image/png"
595
+
596
+ # check global tile lock directory was created
597
+ assert tiles_lock_dir.check()
598
+
599
+ assert cache_dir.join(
600
+ "wms_cache_100_EPSG900913/01/000/000/001/000/000/001.jpeg"
601
+ ).check()
602
+
603
+ def test_get_map130(self, app, cache_dir):
604
+ with tmp_image((256, 256), format="jpeg") as img:
605
+ expected_req = (
606
+ {
607
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
608
+ "&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A900913&styles="
609
+ "&VERSION=1.3.0&BBOX=0.0,0.0,20037508.3428,20037508.3428"
610
+ "&WIDTH=256"
611
+ },
612
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
613
+ )
614
+ with mock_httpd(
615
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
616
+ ):
617
+ self.common_map_req.params["bbox"] = "0,0,180,90"
618
+ self.common_map_req.params["layers"] = "wms_cache_130"
619
+ resp = app.get(self.common_map_req)
620
+ assert resp.content_type == "image/png"
621
+ assert cache_dir.join(
622
+ "wms_cache_130_EPSG900913/01/000/000/001/000/000/001.jpeg"
623
+ ).check()
624
+
625
+ def test_get_map130_axis_order(self, app, cache_dir):
626
+ with tmp_image((256, 256), format="jpeg") as img:
627
+ img = img.read()
628
+ expected_reqs = [
629
+ (
630
+ {
631
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
632
+ "&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A4326&styles="
633
+ "&VERSION=1.3.0&BBOX=0.0,90.0,90.0,180.0"
634
+ "&WIDTH=256"
635
+ },
636
+ {"body": img, "headers": {"content-type": "image/jpeg"}},
637
+ )
638
+ ]
639
+ with mock_httpd(("localhost", 42423), expected_reqs):
640
+ self.common_map_req.params["bbox"] = "90,0,180,90"
641
+ self.common_map_req.params["layers"] = "wms_cache_multi"
642
+ resp = app.get(self.common_map_req)
643
+ assert resp.content_type == "image/png"
644
+ assert cache_dir.join(
645
+ "wms_cache_multi_EPSG4326/02/000/000/003/000/000/001.jpeg"
646
+ ).check()
647
+
648
+ def test_get_featureinfo(self, app):
649
+ expected_req = (
650
+ {
651
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
652
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
653
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
654
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20&feature_count=100"
655
+ },
656
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
657
+ )
658
+ with mock_httpd(("localhost", 42423), [expected_req]):
659
+ self.common_fi_req.params["feature_count"] = 100
660
+ resp = app.get(self.common_fi_req)
661
+ assert resp.content_type == "text/plain"
662
+ assert resp.body == b"info"
663
+
664
+ def test_get_featureinfo_coverage(self, app):
665
+ self.common_fi_req.params["bbox"] = "-180.0,-90.0,180.0,90.0"
666
+ self.common_fi_req.params["srs"] = "EPSG:4326"
667
+ self.common_fi_req.params["width"] = "400"
668
+ self.common_fi_req.params["height"] = "200"
669
+ self.common_fi_req.params["x"] = 395 # outside of coverage
670
+ self.common_fi_req.params["y"] = 50
671
+ self.common_fi_req.params["layers"] = 'tms_fi_cache'
672
+ self.common_fi_req.params["query_layers"] = 'tms_fi_cache'
673
+
674
+ resp = app.get(self.common_fi_req)
675
+ assert resp.body == b""
676
+ assert resp.content_type == "text/plain"
677
+
678
+ expected_req = (
679
+ {
680
+ "path": r"/service?LAYERs=fi&SERVICE=WMS&FORMAT=image%2Fpng"
681
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A4326"
682
+ "&VERSION=1.1.1&BBOX=-180.0,-90.0,180.0,90.0&styles="
683
+ "&WIDTH=400&QUERY_LAYERS=fi&X=380&Y=50"
684
+ },
685
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
686
+ )
687
+ with mock_httpd(("localhost", 42423), [expected_req]):
688
+ self.common_fi_req.params["x"] = 380 # inside of coverage
689
+
690
+ resp = app.get(self.common_fi_req)
691
+ assert resp.body == b"info"
692
+ assert resp.content_type == "text/plain"
693
+
694
+ def test_get_featureinfo_float(self, app):
695
+ expected_req = (
696
+ {
697
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
698
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
699
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
700
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10.123&Y=20.567&feature_count=100"
701
+ },
702
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
703
+ )
704
+ with mock_httpd(("localhost", 42423), [expected_req]):
705
+ self.common_fi_req.params["feature_count"] = 100
706
+ self.common_fi_req.params["x"] = 10.123
707
+ self.common_fi_req.params["y"] = 20.567
708
+ resp = app.get(self.common_fi_req)
709
+ assert resp.content_type == "text/plain"
710
+ assert resp.body == b"info"
711
+
712
+ def test_get_featureinfo_transformed(self, app):
713
+ expected_req = (
714
+ {
715
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
716
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
717
+ "&BBOX=1172272.30156,7196018.03449,1189711.04571,7213496.99738"
718
+ "&styles=&VERSION=1.1.1&feature_count=100"
719
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=14&Y=20"
720
+ },
721
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
722
+ )
723
+
724
+ # out fi point at x=10,y=20
725
+ p_25832 = (
726
+ 600000 + 10 * (610000 - 600000) / 200,
727
+ 6010000 - 20 * (6010000 - 6000000) / 200,
728
+ )
729
+ # the transformed fi point at x=14,y=20
730
+ p_900913 = (
731
+ 1172272.30156 + 14 * (1189711.04571 - 1172272.30156) / 200,
732
+ 7213496.99738 - 20 * (7213496.99738 - 7196018.03449) / 200,
733
+ )
734
+
735
+ # are they the same?
736
+ # check with tolerance: pixel resolution is ~50 and x/y position is rounded to pizel
737
+ assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[0] - p_900913[0]) < 50
738
+ assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[1] - p_900913[1]) < 50
739
+
740
+ with mock_httpd(
741
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
742
+ ):
743
+ self.common_fi_req.params["bbox"] = "600000,6000000,610000,6010000"
744
+ self.common_fi_req.params["srs"] = "EPSG:25832"
745
+ self.common_fi_req.params.pos = 10, 20
746
+ self.common_fi_req.params["feature_count"] = 100
747
+ resp = app.get(self.common_fi_req)
748
+ assert resp.content_type == "text/plain"
749
+ assert resp.body == b"info"
750
+
751
+ def test_get_featureinfo_info_format(self, app):
752
+ expected_req = (
753
+ {
754
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
755
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
756
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
757
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20"
758
+ "&info_format=text%2Fhtml"
759
+ },
760
+ {"body": b"info", "headers": {"content-type": "text/html"}},
761
+ )
762
+ with mock_httpd(("localhost", 42423), [expected_req]):
763
+ self.common_fi_req.params["info_format"] = "text/html"
764
+ resp = app.get(self.common_fi_req)
765
+ assert resp.content_type == "text/html"
766
+ assert resp.body == b"<html><body><p>info</p></body></html>"
767
+
768
+ def test_get_featureinfo_130(self, app):
769
+ expected_req = (
770
+ {
771
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
772
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913"
773
+ "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles="
774
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20"
775
+ },
776
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
777
+ )
778
+ with mock_httpd(("localhost", 42423), [expected_req]):
779
+ self.common_fi_req.params["layers"] = "wms_cache_130"
780
+ self.common_fi_req.params["query_layers"] = "wms_cache_130"
781
+ resp = app.get(self.common_fi_req)
782
+ assert resp.content_type == "text/plain"
783
+ assert resp.body == b"info"
784
+
785
+ def test_get_featureinfo_missing_params(self, app):
786
+ expected_req = (
787
+ {
788
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
789
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
790
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
791
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20"
792
+ },
793
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
794
+ )
795
+ with mock_httpd(("localhost", 42423), [expected_req]):
796
+ del self.common_fi_req.params["format"]
797
+ del self.common_fi_req.params["styles"]
798
+ resp = app.get(self.common_fi_req)
799
+ assert resp.content_type == "text/plain"
800
+ assert resp.body == b"info"
801
+
802
+ def test_get_featureinfo_missing_params_strict(self, app):
803
+ request_parser = app.app.handlers["service"].services["wms"].request_parser
804
+ try:
805
+ app.app.handlers["service"].services[
806
+ "wms"
807
+ ].request_parser = functools.partial(wms_request, strict=True)
808
+
809
+ del self.common_fi_req.params["format"]
810
+ del self.common_fi_req.params["styles"]
811
+ resp = app.get(self.common_fi_req)
812
+ xml = resp.lxml
813
+ assert "missing parameters" in xml.xpath("//ServiceException/text()")[0]
814
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
815
+ finally:
816
+ app.app.handlers["service"].services["wms"].request_parser = request_parser
817
+ app.app.handlers["service"].request_parser = request_parser
818
+
819
+ def test_get_featureinfo_not_queryable(self, app):
820
+ self.common_fi_req.params["query_layers"] = "tms_cache"
821
+ self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
822
+ resp = app.get(self.common_fi_req)
823
+ assert resp.content_type == "application/vnd.ogc.se_xml"
824
+ xml = resp.lxml
825
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
826
+ assert "tms_cache is not queryable" in xml.xpath("//ServiceException/text()")[0]
827
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
828
+
829
+
830
+ class TestWMS110(SysTest):
831
+ config_file = "layer.yaml"
832
+
833
+ def setup_method(self):
834
+ # WMSTest.setup_method(self)
835
+ self.common_req = WMS110MapRequest(
836
+ url="/service?", param=dict(service="WMS", version="1.1.0")
837
+ )
838
+ self.common_map_req = WMS110MapRequest(
839
+ url="/service?",
840
+ param=dict(
841
+ service="WMS",
842
+ version="1.1.0",
843
+ bbox="-180,0,0,80",
844
+ width="200",
845
+ height="200",
846
+ layers="wms_cache",
847
+ srs="EPSG:4326",
848
+ format="image/png",
849
+ styles="",
850
+ request="GetMap",
851
+ ),
852
+ )
853
+ self.common_fi_req = WMS110FeatureInfoRequest(
854
+ url="/service?",
855
+ param=dict(
856
+ x="10",
857
+ y="20",
858
+ width="200",
859
+ height="200",
860
+ layers="wms_cache",
861
+ format="image/png",
862
+ query_layers="wms_cache_110",
863
+ styles="",
864
+ bbox="1000,400,2000,1400",
865
+ srs="EPSG:900913",
866
+ ),
867
+ )
868
+
869
+ def test_wms_capabilities(self, app):
870
+ req = WMS110CapabilitiesRequest(url="/service?").copy_with_request_params(
871
+ self.common_req
872
+ )
873
+ resp = app.get(req)
874
+ assert resp.content_type == "application/vnd.ogc.wms_xml"
875
+ xml = resp.lxml
876
+ assert (
877
+ xml.xpath(
878
+ "//GetMap//OnlineResource/@xlink:href",
879
+ namespaces=dict(xlink="http://www.w3.org/1999/xlink"),
880
+ )[0] ==
881
+ "http://localhost/service?"
882
+ )
883
+
884
+ llbox = xml.xpath("//Capability/Layer/LatLonBoundingBox")[0]
885
+ # some clients don't like 90deg north/south
886
+ assert float(llbox.attrib["miny"]) == pytest.approx(-70.0)
887
+ assert float(llbox.attrib["maxy"]) == pytest.approx(89.999999)
888
+ assert float(llbox.attrib["minx"]) == pytest.approx(-180.0)
889
+ assert float(llbox.attrib["maxx"]) == pytest.approx(180.0)
890
+
891
+ layer_names = set(xml.xpath("//Layer/Layer/Name/text()"))
892
+ expected_names = set(
893
+ [
894
+ "direct_fwd_params",
895
+ "direct",
896
+ "wms_cache",
897
+ "wms_cache_100",
898
+ "wms_cache_130",
899
+ "wms_cache_transparent",
900
+ "wms_merge",
901
+ "tms_cache",
902
+ "tms_fi_cache",
903
+ "wms_cache_multi",
904
+ "wms_cache_link_single",
905
+ "wms_cache_110",
906
+ "watermark_cache",
907
+ "wms_managed_cookies_cache",
908
+ ]
909
+ )
910
+ assert layer_names == expected_names
911
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.0/capabilities_1_1_0.dtd")
912
+
913
+ def test_invalid_layer(self, app):
914
+ self.common_map_req.params["layers"] = "invalid"
915
+ resp = app.get(self.common_map_req)
916
+ assert resp.content_type == "application/vnd.ogc.se_xml"
917
+ xml = resp.lxml
918
+ assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
919
+ assert (
920
+ xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] ==
921
+ "LayerNotDefined"
922
+ )
923
+ assert xml.xpath("//ServiceException/text()")[0] == "unknown layer: invalid"
924
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd")
925
+
926
+ def test_invalid_format(self, app):
927
+ self.common_map_req.params["format"] = "image/ascii"
928
+ resp = app.get(self.common_map_req)
929
+ assert resp.content_type == "application/vnd.ogc.se_xml"
930
+ xml = resp.lxml
931
+ assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
932
+ assert (
933
+ xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] ==
934
+ "InvalidFormat"
935
+ )
936
+ assert (
937
+ xml.xpath("//ServiceException/text()")[0] ==
938
+ "unsupported image format: image/ascii"
939
+ )
940
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd")
941
+
942
+ def test_invalid_format_img_exception(self, app):
943
+ self.common_map_req.params["format"] = "image/ascii"
944
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
945
+ resp = app.get(self.common_map_req)
946
+ assert resp.content_type == "image/png"
947
+ assert is_png(BytesIO(resp.body))
948
+
949
+ def test_missing_format_img_exception(self, app):
950
+ del self.common_map_req.params["format"]
951
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
952
+ resp = app.get(self.common_map_req)
953
+ assert resp.content_type == "image/png"
954
+ assert is_png(BytesIO(resp.body))
955
+
956
+ def test_invalid_srs(self, app):
957
+ self.common_map_req.params["srs"] = "EPSG:1234"
958
+ resp = app.get(self.common_map_req)
959
+ assert resp.content_type == "application/vnd.ogc.se_xml"
960
+ xml = resp.lxml
961
+ assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0"
962
+ assert (
963
+ xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == "InvalidSRS"
964
+ )
965
+ assert xml.xpath("//ServiceException/text()")[0] == "unsupported srs: EPSG:1234"
966
+ assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd")
967
+
968
+ def test_get_map_png(self, app, fixture_cache_data):
969
+ resp = app.get(self.common_map_req)
970
+ assert resp.content_type == "image/png"
971
+ data = BytesIO(resp.body)
972
+ assert is_png(data)
973
+ assert Image.open(data).mode == "RGB"
974
+
975
+ def test_get_map_jpeg(self, app, fixture_cache_data):
976
+ self.common_map_req.params["format"] = "image/jpeg"
977
+ resp = app.get(self.common_map_req)
978
+ assert resp.content_type == "image/jpeg"
979
+ assert is_jpeg(BytesIO(resp.body))
980
+
981
+ def test_get_map_xml_exception(self, app):
982
+ self.common_map_req.params["bbox"] = "0,0,90,90"
983
+ resp = app.get(self.common_map_req)
984
+ assert resp.content_type == "application/vnd.ogc.se_xml"
985
+ xml = resp.lxml
986
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
987
+ assert "No response from URL" in xml.xpath("//ServiceException/text()")[0]
988
+ assert validate_with_dtd(xml, "wms/1.1.0/exception_1_1_0.dtd")
989
+
990
+ @pytest.mark.flaky(reruns=5, reruns_delay=2)
991
+ def test_get_map(self, app, cache_dir):
992
+ with tmp_image((256, 256), format="jpeg") as img:
993
+ expected_req = (
994
+ {
995
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
996
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
997
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
998
+ "&WIDTH=256"
999
+ },
1000
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
1001
+ )
1002
+ with mock_httpd(
1003
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
1004
+ ):
1005
+ self.common_map_req.params["bbox"] = "0,0,180,90"
1006
+ resp = app.get(self.common_map_req)
1007
+ assert 35000 < int(resp.headers["Content-length"]) < 75000
1008
+ assert resp.content_type == "image/png"
1009
+
1010
+ assert cache_dir.join(
1011
+ "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg"
1012
+ ).check()
1013
+
1014
+ def test_get_map_110(self, app, cache_dir):
1015
+ with tmp_image((256, 256), format="jpeg") as img:
1016
+ expected_req = (
1017
+ {
1018
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
1019
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
1020
+ "&VERSION=1.1.0&BBOX=0.0,0.0,20037508.3428,20037508.3428"
1021
+ "&WIDTH=256"
1022
+ },
1023
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
1024
+ )
1025
+ with mock_httpd(
1026
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
1027
+ ):
1028
+ self.common_map_req.params["bbox"] = "0,0,180,90"
1029
+ self.common_map_req.params["layers"] = "wms_cache_110"
1030
+ resp = app.get(self.common_map_req)
1031
+ assert 35000 < int(resp.headers["Content-length"]) < 75000
1032
+ assert resp.content_type == "image/png"
1033
+
1034
+ assert cache_dir.join(
1035
+ "wms_cache_110_EPSG900913/01/000/000/001/000/000/001.jpeg"
1036
+ ).check()
1037
+
1038
+ def test_get_featureinfo(self, app):
1039
+ expected_req = (
1040
+ {
1041
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
1042
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
1043
+ "&VERSION=1.1.0&BBOX=1000.0,400.0,2000.0,1400.0&styles="
1044
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20"
1045
+ },
1046
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
1047
+ )
1048
+ with mock_httpd(("localhost", 42423), [expected_req]):
1049
+ resp = app.get(self.common_fi_req)
1050
+ assert resp.content_type == "text/plain"
1051
+ assert resp.body == b"info"
1052
+
1053
+ def test_get_featureinfo_not_queryable(self, app):
1054
+ self.common_fi_req.params["query_layers"] = "tms_cache"
1055
+ self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
1056
+ resp = app.get(self.common_fi_req)
1057
+ assert resp.content_type == "application/vnd.ogc.se_xml"
1058
+ xml = resp.lxml
1059
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == []
1060
+ assert "tms_cache is not queryable" in xml.xpath("//ServiceException/text()")[0]
1061
+ assert validate_with_dtd(xml, "wms/1.1.0/exception_1_1_0.dtd")
1062
+
1063
+ def test_managed_cookies(self, app):
1064
+ def assert_no_cookie(req_handler):
1065
+ return 'Cookie' not in req_handler.headers
1066
+
1067
+ def assert_cookie(req_handler):
1068
+ assert 'Cookie' in req_handler.headers
1069
+ cookie_name, cookie_val = req_handler.headers['Cookie'].split(';')[0].split('=')
1070
+ assert cookie_name == 'testcookie'
1071
+ assert cookie_val == '42'
1072
+ return True
1073
+
1074
+ url = (r"/service?LAYERs=layer1&SERVICE=WMS&FORMAT=image%2Fpng"
1075
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
1076
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
1077
+ "&WIDTH=200&QUERY_LAYERS=layer1&X=10&Y=20")
1078
+ # First response has a Set-Cookie => with managed_cookies=True, mapproxy should send the
1079
+ # cookie in the second request
1080
+ expected_requests = [
1081
+ (
1082
+ {'path': url, 'req_assert_function': assert_no_cookie},
1083
+ {'body': b'nothing', 'headers': {'Set-Cookie': "testcookie=42"}}
1084
+ ),
1085
+ (
1086
+ {'path': url, 'req_assert_function': assert_cookie},
1087
+ {'body': b'nothing'}
1088
+ )
1089
+ ]
1090
+ with mock_httpd(("localhost", 42423), expected_requests):
1091
+ self.common_fi_req.params["layers"] = "wms_managed_cookies_cache"
1092
+ self.common_fi_req.params["query_layers"] = "wms_managed_cookies_cache"
1093
+ resp = app.get(self.common_fi_req)
1094
+ assert resp.body == b"nothing"
1095
+ resp = app.get(self.common_fi_req)
1096
+ assert resp.body == b"nothing"
1097
+
1098
+
1099
+ class TestWMS100(SysTest):
1100
+ config_file = "layer.yaml"
1101
+
1102
+ def setup_method(self):
1103
+ self.common_req = WMS100MapRequest(url="/service?", param=dict(wmtver="1.0.0"))
1104
+ self.common_map_req = WMS100MapRequest(
1105
+ url="/service?",
1106
+ param=dict(
1107
+ wmtver="1.0.0",
1108
+ bbox="-180,0,0,80",
1109
+ width="200",
1110
+ height="200",
1111
+ layers="wms_cache",
1112
+ srs="EPSG:4326",
1113
+ format="PNG",
1114
+ styles="",
1115
+ request="GetMap",
1116
+ ),
1117
+ )
1118
+ self.common_fi_req = WMS100FeatureInfoRequest(
1119
+ url="/service?",
1120
+ param=dict(
1121
+ x="10",
1122
+ y="20",
1123
+ width="200",
1124
+ height="200",
1125
+ layers="wms_cache_100",
1126
+ format="PNG",
1127
+ query_layers="wms_cache_100",
1128
+ styles="",
1129
+ bbox="1000,400,2000,1400",
1130
+ srs="EPSG:900913",
1131
+ ),
1132
+ )
1133
+
1134
+ def test_wms_capabilities(self, app):
1135
+ req = WMS100CapabilitiesRequest(url="/service?").copy_with_request_params(
1136
+ self.common_req
1137
+ )
1138
+ resp = app.get(req)
1139
+ assert resp.content_type == "text/xml"
1140
+ xml = resp.lxml
1141
+ assert (
1142
+ xml.xpath("/WMT_MS_Capabilities/Service/Title/text()")[0] ==
1143
+ u"MapProxy test fixture \u2603"
1144
+ )
1145
+ layer_names = set(xml.xpath("//Layer/Layer/Name/text()"))
1146
+ expected_names = set(
1147
+ [
1148
+ "direct_fwd_params",
1149
+ "direct",
1150
+ "wms_cache",
1151
+ "wms_cache_100",
1152
+ "wms_cache_130",
1153
+ "wms_cache_transparent",
1154
+ "wms_merge",
1155
+ "tms_cache",
1156
+ "tms_fi_cache",
1157
+ "wms_cache_multi",
1158
+ "wms_cache_link_single",
1159
+ "wms_cache_110",
1160
+ "watermark_cache",
1161
+ "wms_managed_cookies_cache",
1162
+ ]
1163
+ )
1164
+ assert layer_names == expected_names
1165
+ # TODO srs
1166
+ assert validate_with_dtd(xml, dtd_name="wms/1.0.0/capabilities_1_0_0.dtd")
1167
+
1168
+ def test_invalid_layer(self, app):
1169
+ self.common_map_req.params["layers"] = "invalid"
1170
+ resp = app.get(self.common_map_req)
1171
+ assert resp.content_type == "text/xml"
1172
+ xml = resp.lxml
1173
+ assert xml.xpath("/WMTException/@version")[0] == "1.0.0"
1174
+ assert xml.xpath("//WMTException/text()")[0].strip() == "unknown layer: invalid"
1175
+
1176
+ def test_invalid_format(self, app):
1177
+ self.common_map_req.params["format"] = "image/ascii"
1178
+ resp = app.get(self.common_map_req)
1179
+ assert resp.content_type == "text/xml"
1180
+ xml = resp.lxml
1181
+ assert xml.xpath("/WMTException/@version")[0] == "1.0.0"
1182
+ assert (
1183
+ xml.xpath("//WMTException/text()")[0].strip() ==
1184
+ "unsupported image format: ASCII"
1185
+ )
1186
+
1187
+ def test_invalid_format_img_exception(self, app):
1188
+ self.common_map_req.params["format"] = "image/ascii"
1189
+ self.common_map_req.params["exceptions"] = "INIMAGE"
1190
+ resp = app.get(self.common_map_req)
1191
+ assert resp.content_type == "image/png"
1192
+ assert is_png(BytesIO(resp.body))
1193
+
1194
+ def test_missing_format_img_exception(self, app):
1195
+ del self.common_map_req.params["format"]
1196
+ self.common_map_req.params["exceptions"] = "INIMAGE"
1197
+ resp = app.get(self.common_map_req)
1198
+ assert resp.content_type == "image/png"
1199
+ assert is_png(BytesIO(resp.body))
1200
+
1201
+ def test_invalid_srs(self, app):
1202
+ self.common_map_req.params["srs"] = "EPSG:1234"
1203
+ print(self.common_map_req.complete_url)
1204
+ resp = app.get(self.common_map_req.complete_url)
1205
+ xml = resp.lxml
1206
+ assert xml.xpath("//WMTException/text()")[0].strip() == "unsupported srs: EPSG:1234"
1207
+
1208
+ def test_get_map_png(self, app, fixture_cache_data):
1209
+ resp = app.get(self.common_map_req)
1210
+ assert resp.content_type == "image/png"
1211
+ data = BytesIO(resp.body)
1212
+ assert is_png(data)
1213
+ assert Image.open(data).mode == "RGB"
1214
+
1215
+ def test_get_map_png_transparent_paletted(
1216
+ self, app, base_config, fixture_cache_data
1217
+ ):
1218
+ try:
1219
+ base_config.image.paletted = True
1220
+ self.common_map_req.params["transparent"] = "True"
1221
+ resp = app.get(self.common_map_req)
1222
+ assert resp.content_type == "image/png"
1223
+ data = BytesIO(resp.body)
1224
+ assert is_png(data)
1225
+ assert Image.open(data).mode == "P"
1226
+ finally:
1227
+ base_config.image.paletted = False
1228
+
1229
+ def test_get_map_jpeg(self, app, fixture_cache_data):
1230
+ self.common_map_req.params["format"] = "image/jpeg"
1231
+ resp = app.get(self.common_map_req)
1232
+ assert resp.content_type == "image/jpeg"
1233
+ assert is_jpeg(BytesIO(resp.body))
1234
+
1235
+ def test_get_map_xml_exception(self, app):
1236
+ self.common_map_req.params["bbox"] = "0,0,90,90"
1237
+ resp = app.get(self.common_map_req)
1238
+ xml = resp.lxml
1239
+ assert "No response from URL" in xml.xpath("//WMTException/text()")[0]
1240
+
1241
+ @pytest.mark.flaky(reruns=5, reruns_delay=2)
1242
+ def test_get_map(self, app, cache_dir):
1243
+ with tmp_image((256, 256), format="jpeg") as img:
1244
+ expected_req = (
1245
+ {
1246
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
1247
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
1248
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
1249
+ "&WIDTH=256"
1250
+ },
1251
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
1252
+ )
1253
+ with mock_httpd(
1254
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
1255
+ ):
1256
+ self.common_map_req.params["bbox"] = "0,0,180,90"
1257
+ resp = app.get(self.common_map_req)
1258
+ assert resp.content_type == "image/png"
1259
+ assert cache_dir.join(
1260
+ "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg"
1261
+ ).check()
1262
+
1263
+ def test_get_featureinfo(self, app):
1264
+ expected_req = (
1265
+ {
1266
+ "path": r"/service?LAYERs=foo,bar&FORMAT=image%2FPNG" # TODO should be PNG only
1267
+ "&REQUEST=feature_info&HEIGHT=200&SRS=EPSG%3A900913"
1268
+ "&WMTVER=1.0.0&BBOX=1000.0,400.0,2000.0,1400.0&styles="
1269
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20"
1270
+ },
1271
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
1272
+ )
1273
+ with mock_httpd(("localhost", 42423), [expected_req]):
1274
+ resp = app.get(self.common_fi_req)
1275
+ assert resp.content_type == "text/plain"
1276
+ assert resp.body == b"info"
1277
+
1278
+ def test_get_featureinfo_not_queryable(self, app):
1279
+ self.common_fi_req.params["query_layers"] = "tms_cache"
1280
+ self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml"
1281
+ resp = app.get(self.common_fi_req)
1282
+ assert resp.content_type == "text/xml"
1283
+ xml = resp.lxml
1284
+ assert "tms_cache is not queryable" in xml.xpath("//WMTException/text()")[0]
1285
+
1286
+
1287
+ ns130 = {
1288
+ "wms": "http://www.opengis.net/wms",
1289
+ "ogc": "http://www.opengis.net/ogc",
1290
+ "sld": "http://www.opengis.net/sld",
1291
+ "xlink": "http://www.w3.org/1999/xlink",
1292
+ }
1293
+
1294
+
1295
+ def assert_xpath(xml, xpath, expected, namespaces=None):
1296
+ assert xml.xpath(xpath, namespaces=namespaces)[0] == expected
1297
+
1298
+
1299
+ assert_xpath_wms130 = functools.partial(assert_xpath, namespaces=ns130)
1300
+
1301
+
1302
+ class TestWMS130(SysTest):
1303
+ config_file = "layer.yaml"
1304
+
1305
+ def setup_method(self):
1306
+ self.common_req = WMS130MapRequest(
1307
+ url="/service?", param=dict(service="WMS", version="1.3.0")
1308
+ )
1309
+ self.common_map_req = WMS130MapRequest(
1310
+ url="/service?",
1311
+ param=dict(
1312
+ service="WMS",
1313
+ version="1.3.0",
1314
+ bbox="0,-180,80,0",
1315
+ width="200",
1316
+ height="200",
1317
+ layers="wms_cache",
1318
+ crs="EPSG:4326",
1319
+ format="image/png",
1320
+ styles="",
1321
+ request="GetMap",
1322
+ ),
1323
+ )
1324
+ self.common_fi_req = WMS130FeatureInfoRequest(
1325
+ url="/service?",
1326
+ param=dict(
1327
+ i="10",
1328
+ j="20",
1329
+ width="200",
1330
+ height="200",
1331
+ layers="wms_cache_130",
1332
+ format="image/png",
1333
+ query_layers="wms_cache_130",
1334
+ styles="",
1335
+ bbox="1000,400,2000,1400",
1336
+ crs="EPSG:900913",
1337
+ ),
1338
+ )
1339
+
1340
+ def test_wms_capabilities(self, app):
1341
+ req = WMS130CapabilitiesRequest(url="/service?").copy_with_request_params(
1342
+ self.common_req
1343
+ )
1344
+ resp = app.get(req)
1345
+ assert resp.content_type == "text/xml"
1346
+ xml = resp.lxml
1347
+ assert_xpath_wms130(
1348
+ xml,
1349
+ "/wms:WMS_Capabilities/wms:Service/wms:Title/text()",
1350
+ u"MapProxy test fixture \u2603",
1351
+ )
1352
+
1353
+ # test for extended layer metadata
1354
+ assert_xpath_wms130(
1355
+ xml,
1356
+ "/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Layer/wms:Attribution/wms:Title/text()",
1357
+ u"My attribution title",
1358
+ )
1359
+
1360
+ layer_names = set(
1361
+ xml.xpath("//wms:Layer/wms:Layer/wms:Name/text()", namespaces=ns130)
1362
+ )
1363
+ expected_names = set(
1364
+ [
1365
+ "direct_fwd_params",
1366
+ "direct",
1367
+ "wms_cache",
1368
+ "wms_cache_100",
1369
+ "wms_cache_130",
1370
+ "wms_cache_transparent",
1371
+ "wms_merge",
1372
+ "tms_cache",
1373
+ "tms_fi_cache",
1374
+ "wms_cache_multi",
1375
+ "wms_cache_link_single",
1376
+ "wms_cache_110",
1377
+ "watermark_cache",
1378
+ "wms_managed_cookies_cache",
1379
+ ]
1380
+ )
1381
+ assert layer_names == expected_names
1382
+ assert is_130_capa(xml)
1383
+
1384
+ def test_invalid_layer(self, app):
1385
+ self.common_map_req.params["layers"] = "invalid"
1386
+ resp = app.get(self.common_map_req)
1387
+ assert resp.content_type == "text/xml"
1388
+ xml = resp.lxml
1389
+ assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
1390
+ assert_xpath_wms130(
1391
+ xml,
1392
+ "/ogc:ServiceExceptionReport/ogc:ServiceException/@code",
1393
+ "LayerNotDefined",
1394
+ )
1395
+ assert_xpath_wms130(xml, "//ogc:ServiceException/text()", "unknown layer: invalid")
1396
+ assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd")
1397
+
1398
+ def test_invalid_format(self, app):
1399
+ self.common_map_req.params["format"] = "image/ascii"
1400
+ resp = app.get(self.common_map_req)
1401
+ assert resp.content_type == "text/xml"
1402
+ xml = resp.lxml
1403
+ assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0")
1404
+ assert_xpath_wms130(
1405
+ xml,
1406
+ "/ogc:ServiceExceptionReport/ogc:ServiceException/@code",
1407
+ "InvalidFormat",
1408
+ )
1409
+ assert_xpath_wms130(
1410
+ xml,
1411
+ "//ogc:ServiceException/text()",
1412
+ "unsupported image format: image/ascii",
1413
+ )
1414
+ assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd")
1415
+
1416
+ def test_invalid_format_img_exception(self, app):
1417
+ self.common_map_req.params["format"] = "image/ascii"
1418
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
1419
+ resp = app.get(self.common_map_req)
1420
+ assert resp.content_type == "image/png"
1421
+ assert is_png(BytesIO(resp.body))
1422
+
1423
+ def test_missing_format_img_exception(self, app):
1424
+ del self.common_map_req.params["format"]
1425
+ self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage"
1426
+ resp = app.get(self.common_map_req)
1427
+ assert resp.content_type == "image/png"
1428
+ assert is_png(BytesIO(resp.body))
1429
+
1430
+ def test_invalid_srs(self, app):
1431
+ self.common_map_req.params["srs"] = "EPSG:1234"
1432
+ self.common_map_req.params["exceptions"] = "text/xml"
1433
+
1434
+ resp = app.get(self.common_map_req)
1435
+ assert resp.content_type == "text/xml"
1436
+ xml = resp.lxml
1437
+ assert_xpath_wms130(
1438
+ xml, "/ogc:ServiceExceptionReport/ogc:ServiceException/@code", "InvalidCRS"
1439
+ )
1440
+ assert_xpath_wms130(
1441
+ xml, "//ogc:ServiceException/text()", "unsupported crs: EPSG:1234"
1442
+ )
1443
+ assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd")
1444
+
1445
+ def test_get_map_png(self, app, fixture_cache_data):
1446
+ resp = app.get(self.common_map_req)
1447
+ assert resp.content_type == "image/png"
1448
+ data = BytesIO(resp.body)
1449
+ assert is_png(data)
1450
+ assert Image.open(data).mode == "RGB"
1451
+
1452
+ def test_get_map_jpeg(self, app, fixture_cache_data):
1453
+ self.common_map_req.params["format"] = "image/jpeg"
1454
+ resp = app.get(self.common_map_req)
1455
+ assert resp.content_type == "image/jpeg"
1456
+ assert is_jpeg(BytesIO(resp.body))
1457
+
1458
+ def test_get_map_xml_exception(self, app):
1459
+ self.common_map_req.params["bbox"] = "0,0,90,90"
1460
+ resp = app.get(self.common_map_req)
1461
+ assert resp.content_type == "text/xml"
1462
+ xml = resp.lxml
1463
+ assert (
1464
+ xml.xpath(
1465
+ "/ogc:ServiceExceptionReport/ogc:ServiceException/@code",
1466
+ namespaces=ns130,
1467
+ ) ==
1468
+ []
1469
+ )
1470
+ assert (
1471
+ "No response from URL"
1472
+ in xml.xpath("//ogc:ServiceException/text()", namespaces=ns130)[0]
1473
+ )
1474
+ assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd")
1475
+
1476
+ @pytest.mark.flaky(reruns=5, reruns_delay=2)
1477
+ def test_get_map(self, app, cache_dir):
1478
+ with tmp_image((256, 256), format="jpeg") as img:
1479
+ expected_req = (
1480
+ {
1481
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
1482
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
1483
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
1484
+ "&WIDTH=256"
1485
+ },
1486
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
1487
+ )
1488
+ with mock_httpd(
1489
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
1490
+ ):
1491
+ self.common_map_req.params["bbox"] = "0,0,180,90" # internal axis-order
1492
+ resp = app.get(self.common_map_req)
1493
+ assert resp.content_type == "image/png"
1494
+
1495
+ assert cache_dir.join(
1496
+ "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg"
1497
+ ).check()
1498
+
1499
+ def test_get_featureinfo(self, app):
1500
+ expected_req = (
1501
+ {
1502
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
1503
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913"
1504
+ "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles="
1505
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20"
1506
+ },
1507
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
1508
+ )
1509
+ with mock_httpd(("localhost", 42423), [expected_req]):
1510
+ resp = app.get(self.common_fi_req)
1511
+ assert resp.content_type == "text/plain"
1512
+ assert resp.body == b"info"
1513
+
1514
+ def test_get_featureinfo_111(self, app):
1515
+ expected_req = (
1516
+ {
1517
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng"
1518
+ "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913"
1519
+ "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles="
1520
+ "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20"
1521
+ },
1522
+ {"body": b"info", "headers": {"content-type": "text/plain"}},
1523
+ )
1524
+ with mock_httpd(("localhost", 42423), [expected_req]):
1525
+ self.common_fi_req.params["layers"] = "wms_cache"
1526
+ self.common_fi_req.params["query_layers"] = "wms_cache"
1527
+ resp = app.get(self.common_fi_req)
1528
+ assert resp.content_type == "text/plain"
1529
+ assert resp.body == b"info"
1530
+
1531
+
1532
+ @pytest.mark.skipif(sys.platform == "win32", reason="not supported on Windows")
1533
+ class TestWMSLinkSingleColorImages(SysTest):
1534
+ config_file = "layer.yaml"
1535
+
1536
+ def setup_method(self):
1537
+ self.common_map_req = WMS111MapRequest(
1538
+ url="/service?",
1539
+ param=dict(
1540
+ service="WMS",
1541
+ version="1.1.1",
1542
+ bbox="-180,0,0,80",
1543
+ width="200",
1544
+ height="200",
1545
+ layers="wms_cache_link_single",
1546
+ srs="EPSG:4326",
1547
+ format="image/jpeg",
1548
+ styles="",
1549
+ request="GetMap",
1550
+ ),
1551
+ )
1552
+
1553
+ @pytest.mark.flaky(reruns=5, reruns_delay=2)
1554
+ def test_get_map(self, app, cache_dir):
1555
+ link_name = "wms_cache_link_single_EPSG900913/01/000/000/001/000/000/001.png"
1556
+ real_name = "wms_cache_link_single_EPSG900913/single_color_tiles/fe00a0.png"
1557
+ with tmp_image((256, 256), format="jpeg", color="#fe00a0") as img:
1558
+ expected_req = (
1559
+ {
1560
+ "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg"
1561
+ "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles="
1562
+ "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428"
1563
+ "&WIDTH=256"
1564
+ },
1565
+ {"body": img.read(), "headers": {"content-type": "image/jpeg"}},
1566
+ )
1567
+ with mock_httpd(
1568
+ ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True
1569
+ ):
1570
+ self.common_map_req.params["bbox"] = "0,0,180,90"
1571
+ resp = app.get(self.common_map_req)
1572
+ assert resp.content_type == "image/jpeg"
1573
+
1574
+ single_loc = cache_dir.join(real_name)
1575
+ tile_loc = cache_dir.join(link_name)
1576
+ assert single_loc.check()
1577
+ assert tile_loc.check(link=True)
1578
+
1579
+ self.common_map_req.params["format"] = "image/png"
1580
+ resp = app.get(self.common_map_req)
1581
+ assert resp.content_type == "image/png"
1582
+
1583
+
1584
+ def assert_almost_equal_bbox(bbox1, bbox2, rel=0.01):
1585
+ assert bbox1 == pytest.approx(bbox2, rel=rel)
1586
+
1587
+
1588
+ def is_100_capa(xml):
1589
+ return validate_with_dtd(xml, dtd_name="wms/1.0.0/capabilities_1_0_0.dtd")
1590
+
1591
+
1592
+ def is_110_capa(xml):
1593
+ return validate_with_dtd(xml, dtd_name="wms/1.1.0/capabilities_1_1_0.dtd")
1594
+
1595
+
1596
+ def is_111_exception(xml, msg=None, code=None, re_msg=None):
1597
+ assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.1"
1598
+ if msg:
1599
+ assert xml.xpath("//ServiceException/text()")[0] == msg
1600
+ if re_msg:
1601
+ exception_msg = xml.xpath("//ServiceException/text()")[0]
1602
+ assert re.findall(re_msg, exception_msg, re.I), "'%r' does not match '%s'" % (
1603
+ re_msg,
1604
+ exception_msg,
1605
+ )
1606
+ if code is not None:
1607
+ assert xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == code
1608
+ assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd")
1609
+
1610
+
1611
+ def is_111_capa(xml):
1612
+ return validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd")
1613
+
1614
+
1615
+ def is_130_capa(xml):
1616
+ return validate_with_xsd(xml, xsd_name="wms/1.3.0/capabilities_1_3_0.xsd")