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,1193 @@
1
+ # This file is part of the MapProxy project.
2
+ # Copyright (C) 2010 Omniscale <http://omniscale.de>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ import base64
18
+ import os
19
+ import re
20
+ import shutil
21
+ import tempfile
22
+ import threading
23
+ import time
24
+
25
+ from io import BytesIO
26
+ from collections import defaultdict
27
+
28
+ import pytest
29
+
30
+ from mapproxy.cache.base import TileLocker
31
+ from mapproxy.cache.file import FileCache
32
+ from mapproxy.cache.tile import Tile, TileManager
33
+ from mapproxy.client.http import HTTPClient
34
+ from mapproxy.client.wms import WMSClient
35
+ from mapproxy.compat.image import Image
36
+ from mapproxy.grid import TileGrid, resolution_range
37
+ from mapproxy.image import ImageSource, BlankImageSource
38
+ from mapproxy.image.opts import ImageOptions
39
+ from mapproxy.layer import (
40
+ BlankImage,
41
+ CacheMapLayer,
42
+ DirectMapLayer,
43
+ MapBBOXError,
44
+ MapExtent,
45
+ MapLayer,
46
+ MapQuery,
47
+ ResolutionConditional,
48
+ SRSConditional,
49
+ )
50
+ from mapproxy.request.wms import WMS111MapRequest
51
+ from mapproxy.source import InvalidSourceQuery, SourceError
52
+ from mapproxy.source.tile import TiledSource
53
+ from mapproxy.source.wms import WMSSource
54
+ from mapproxy.source.error import HTTPSourceErrorHandler
55
+ from mapproxy.srs import SRS, SupportedSRS, PreferredSrcSRS
56
+ from mapproxy.test.http import assert_query_eq, wms_query_eq, query_eq, mock_httpd
57
+ from mapproxy.test.image import create_debug_img, is_png, tmp_image, create_tmp_image_buf
58
+ from mapproxy.util.coverage import BBOXCoverage
59
+
60
+
61
+ TEST_SERVER_ADDRESS = ('127.0.0.1', 56413)
62
+ GLOBAL_GEOGRAPHIC_EXTENT = MapExtent((-180, -90, 180, 90), SRS(4326))
63
+
64
+ tmp_lock_dir = None
65
+ def setup():
66
+ global tmp_lock_dir
67
+ tmp_lock_dir = tempfile.mkdtemp()
68
+
69
+ def teardown():
70
+ shutil.rmtree(tmp_lock_dir)
71
+
72
+ class counting_set(object):
73
+ def __init__(self, items):
74
+ self.data = defaultdict(int)
75
+ for item in items:
76
+ self.data[item] += 1
77
+ def add(self, item):
78
+ self.data[item] += 1
79
+
80
+ def __repr__(self):
81
+ return 'counting_set(%r)' % dict(self.data)
82
+
83
+ def __eq__(self, other):
84
+ return self.data == other.data
85
+
86
+ class MockTileClient(object):
87
+ def __init__(self):
88
+ self.requested_tiles = []
89
+
90
+ def get_tile(self, tile_coord, format=None):
91
+ self.requested_tiles.append(tile_coord)
92
+ return ImageSource(create_debug_img((256, 256)))
93
+
94
+ class TestTiledSourceGlobalGeodetic(object):
95
+ def setup(self):
96
+ self.grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
97
+ self.client = MockTileClient()
98
+ self.source = TiledSource(self.grid, self.client)
99
+ def test_match(self):
100
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326)))
101
+ self.source.get_map(MapQuery([0, -90, 180, 90], (256, 256), SRS(4326)))
102
+ assert self.client.requested_tiles == [(0, 0, 1), (1, 0, 1)]
103
+ def test_wrong_size(self):
104
+ with pytest.raises(InvalidSourceQuery):
105
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (512, 256), SRS(4326)))
106
+ def test_wrong_srs(self):
107
+ with pytest.raises(InvalidSourceQuery):
108
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (512, 256), SRS(4326)))
109
+
110
+ class RecordFileCache(FileCache):
111
+ def __init__(self, *args, **kw):
112
+ super(RecordFileCache, self).__init__(*args, **kw)
113
+ self.stored_tiles = set()
114
+ self.loaded_tiles = counting_set([])
115
+
116
+ def store_tile(self, tile,dimensions=None):
117
+ assert tile.coord not in self.stored_tiles
118
+ self.stored_tiles.add(tile.coord)
119
+ if self.cache_dir != '/dev/null':
120
+ FileCache.store_tile(self, tile,dimensions=dimensions)
121
+
122
+ def load_tile(self, tile, with_metadata=False,dimensions=None):
123
+ if tile.source:
124
+ # Do not record tiles with source as "loaded" as FileCache will
125
+ # return tile without checking/loading from filesystem.
126
+ return True
127
+ self.loaded_tiles.add(tile.coord)
128
+ return FileCache.load_tile(self, tile, with_metadata,dimensions=dimensions)
129
+
130
+ def is_cached(self, tile,dimensions=None):
131
+ return tile.coord in self.stored_tiles
132
+
133
+
134
+ def create_cached_tile(tile, cache, timestamp=None):
135
+ loc = cache.tile_location(tile, create_dir=True)
136
+ with open(loc, 'wb') as f:
137
+ f.write(b'foo')
138
+
139
+ if timestamp:
140
+ os.utime(loc, (timestamp, timestamp))
141
+
142
+
143
+ @pytest.fixture
144
+ def file_cache(tmpdir):
145
+ return FileCache(cache_dir=tmpdir.join('cache').strpath, file_ext='png')
146
+
147
+ @pytest.fixture
148
+ def tile_locker(tmpdir):
149
+ return TileLocker(tmpdir.join('lock').strpath, 10, "id")
150
+
151
+ @pytest.fixture
152
+ def mock_tile_client():
153
+ return MockTileClient()
154
+
155
+ @pytest.fixture
156
+ def mock_file_cache():
157
+ return RecordFileCache('/dev/null', 'png')
158
+
159
+
160
+ class TestTileManagerStaleTiles(object):
161
+
162
+ @pytest.fixture
163
+ def tile_mgr(self, file_cache, tile_locker):
164
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
165
+ client = MockTileClient()
166
+ source = TiledSource(grid, client)
167
+ tile_mgr = TileManager(grid, file_cache, [source], 'png', locker=tile_locker)
168
+ return tile_mgr
169
+
170
+ def test_is_stale_missing(self, tile_mgr):
171
+ assert not tile_mgr.is_stale(Tile((0, 0, 1)))
172
+
173
+ def test_is_stale_not_expired(self, tile_mgr, file_cache):
174
+ create_cached_tile(Tile((0, 0, 1)), file_cache)
175
+ assert not tile_mgr.is_stale(Tile((0, 0, 1)))
176
+
177
+ def test_is_stale_expired(self, tile_mgr, file_cache):
178
+ create_cached_tile(Tile((0, 0, 1)), file_cache, timestamp=time.time()-3600)
179
+ tile_mgr._expire_timestamp = time.time()
180
+ assert tile_mgr.is_stale(Tile((0, 0, 1)))
181
+
182
+
183
+ class TestTileManagerRemoveTiles(object):
184
+ @pytest.fixture
185
+ def tile_mgr(self, file_cache, tile_locker):
186
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
187
+ client = MockTileClient()
188
+ source = TiledSource(grid, client)
189
+ image_opts = ImageOptions(format='image/png')
190
+ return TileManager(grid, file_cache, [source], 'png',
191
+ image_opts=image_opts,
192
+ locker=tile_locker)
193
+
194
+ def test_remove_missing(self, tile_mgr):
195
+ tile_mgr.remove_tile_coords([(0, 0, 0), (0, 0, 1)])
196
+
197
+ def test_remove_existing(self, tile_mgr, file_cache):
198
+ create_cached_tile(Tile((0, 0, 1)), file_cache)
199
+ assert tile_mgr.is_cached(Tile((0, 0, 1)))
200
+ tile_mgr.remove_tile_coords([(0, 0, 0), (0, 0, 1)])
201
+ assert not tile_mgr.is_cached(Tile((0, 0, 1)))
202
+
203
+
204
+ class TestTileManagerTiledSource(object):
205
+ @pytest.fixture
206
+ def tile_mgr(self, tile_locker, mock_file_cache, mock_tile_client):
207
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
208
+ source = TiledSource(grid, mock_tile_client)
209
+ image_opts = ImageOptions(format='image/png')
210
+ return TileManager(grid, mock_file_cache, [source], 'png',
211
+ image_opts=image_opts,
212
+ locker=tile_locker,
213
+ )
214
+
215
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_tile_client):
216
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
217
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
218
+ assert sorted(mock_tile_client.requested_tiles) == [(0, 0, 1), (1, 0, 1)]
219
+
220
+ class TestTileManagerDifferentSourceGrid(object):
221
+ @pytest.fixture
222
+ def tile_mgr(self, mock_file_cache, mock_tile_client, tile_locker):
223
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
224
+ source_grid = TileGrid(SRS(4326), bbox=[0, -90, 180, 90])
225
+ source = TiledSource(source_grid, mock_tile_client)
226
+ image_opts = ImageOptions(format='image/png')
227
+ return TileManager(grid, mock_file_cache, [source], 'png',
228
+ image_opts=image_opts,
229
+ locker=tile_locker,
230
+ )
231
+
232
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_tile_client):
233
+ tile_mgr.creator().create_tiles([Tile((1, 0, 1))])
234
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
235
+ assert mock_tile_client.requested_tiles == [(0, 0, 0)]
236
+
237
+ def test_create_tiles_out_of_bounds(self, tile_mgr):
238
+ with pytest.raises(InvalidSourceQuery):
239
+ tile_mgr.creator().create_tiles([Tile((0, 0, 0))])
240
+
241
+ class MockSource(MapLayer):
242
+ def __init__(self, *args):
243
+ MapLayer.__init__(self, *args)
244
+ self.requested = []
245
+
246
+ def _image(self, size):
247
+ return create_debug_img(size)
248
+
249
+ def get_map(self, query):
250
+ self.requested.append((query.bbox, query.size, query.srs))
251
+ return ImageSource(self._image(query.size))
252
+
253
+ @pytest.fixture
254
+ def mock_source():
255
+ return MockSource()
256
+
257
+ class TestTileManagerSource(object):
258
+
259
+ @pytest.fixture
260
+ def tile_mgr(self, mock_file_cache, mock_source, tile_locker):
261
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
262
+ image_opts = ImageOptions(format='image/png')
263
+ return TileManager(grid, mock_file_cache, [mock_source], 'png',
264
+ image_opts=image_opts,
265
+ locker=tile_locker,
266
+ )
267
+
268
+ def test_create_tile(self, tile_mgr, mock_file_cache, mock_source):
269
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
270
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
271
+ assert sorted(mock_source.requested) == \
272
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326)),
273
+ ((0.0, -90.0, 180.0, 90.0), (256, 256), SRS(4326))]
274
+
275
+ class MockWMSClient(object):
276
+ def __init__(self):
277
+ self.requested = []
278
+
279
+ def retrieve(self, query, format):
280
+ self.requested.append((query.bbox, query.size, query.srs))
281
+ return create_debug_img(query.size)
282
+
283
+ @pytest.fixture
284
+ def mock_wms_client():
285
+ return MockWMSClient()
286
+
287
+ class TestTileManagerWMSSource(object):
288
+ @pytest.fixture
289
+ def tile_mgr(self, mock_file_cache, tile_locker, mock_wms_client):
290
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
291
+ source = WMSSource(mock_wms_client)
292
+ image_opts = ImageOptions(format='image/png')
293
+ return TileManager(grid, mock_file_cache, [source], 'png',
294
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
295
+ locker=tile_locker,
296
+ )
297
+
298
+ def test_same_lock_for_meta_tile(self, tile_mgr):
299
+ assert tile_mgr.lock(Tile((0, 0, 1))).lock_file == \
300
+ tile_mgr.lock(Tile((1, 0, 1))).lock_file
301
+ def test_locks_for_meta_tiles(self, tile_mgr):
302
+ assert tile_mgr.lock(Tile((0, 0, 2))).lock_file != \
303
+ tile_mgr.lock(Tile((2, 0, 2))).lock_file
304
+
305
+ def test_create_tile_first_level(self, tile_mgr, mock_file_cache, mock_wms_client):
306
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
307
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
308
+ assert mock_wms_client.requested == \
309
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
310
+
311
+ def test_create_tile(self, tile_mgr, mock_file_cache, mock_wms_client):
312
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
313
+ assert mock_file_cache.stored_tiles == \
314
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)])
315
+ assert sorted(mock_wms_client.requested) == \
316
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326))]
317
+
318
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_wms_client):
319
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2)), Tile((2, 0, 2))])
320
+ assert mock_file_cache.stored_tiles == \
321
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
322
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
323
+ assert sorted(mock_wms_client.requested) == \
324
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326)),
325
+ ((0.0, -90.0, 180.0, 90.0), (512, 512), SRS(4326))]
326
+
327
+ def test_load_tile_coords(self, tile_mgr, mock_file_cache, mock_wms_client):
328
+ tiles = tile_mgr.load_tile_coords(((0, 0, 2), (2, 0, 2)))
329
+ assert tiles[0].coord == (0, 0, 2)
330
+ assert isinstance(tiles[0].source, ImageSource)
331
+ assert tiles[1].coord == (2, 0, 2)
332
+ assert isinstance(tiles[1].source, ImageSource)
333
+
334
+ assert mock_file_cache.stored_tiles == \
335
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
336
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
337
+ assert sorted(mock_wms_client.requested) == \
338
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326)),
339
+ ((0.0, -90.0, 180.0, 90.0), (512, 512), SRS(4326))]
340
+
341
+
342
+ class TestTileManagerWMSSourceConcurrent(TestTileManagerWMSSource):
343
+ @pytest.fixture
344
+ def tile_mgr(self, mock_file_cache, tile_locker, mock_wms_client):
345
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
346
+ source = WMSSource(mock_wms_client)
347
+ image_opts = ImageOptions(format='image/png')
348
+ return TileManager(grid, mock_file_cache, [source], 'png',
349
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
350
+ locker=tile_locker,
351
+ concurrent_tile_creators=2,
352
+ )
353
+
354
+ class TestTileManagerWMSSourceMinimalMetaRequests(object):
355
+ @pytest.fixture
356
+ def tile_mgr(self, mock_file_cache, mock_wms_client, tile_locker):
357
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
358
+ source = WMSSource(mock_wms_client)
359
+ return TileManager(grid, mock_file_cache, [source], 'png',
360
+ meta_size=[2, 2], meta_buffer=10, minimize_meta_requests=True,
361
+ locker=tile_locker,
362
+ )
363
+
364
+ def test_create_tile_single(self, tile_mgr, mock_file_cache, mock_wms_client):
365
+ # not enabled for single tile requests
366
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
367
+ assert mock_file_cache.stored_tiles == \
368
+ set([(0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)])
369
+ assert sorted(mock_wms_client.requested) == \
370
+ [((-180.0, -90.0, 3.515625, 90.0), (522, 512), SRS(4326))]
371
+
372
+ def test_create_tile_multiple(self, tile_mgr, mock_file_cache, mock_wms_client):
373
+ tile_mgr.creator().create_tiles([Tile((4, 0, 3)), Tile((4, 1, 3)), Tile((4, 2, 3))])
374
+ assert mock_file_cache.stored_tiles == \
375
+ set([(4, 0, 3), (4, 1, 3), (4, 2, 3)])
376
+ assert sorted(mock_wms_client.requested) == \
377
+ [((-1.7578125, -90, 46.7578125, 46.7578125), (276, 778), SRS(4326))]
378
+
379
+ def test_create_tile_multiple_fragmented(self, tile_mgr, mock_file_cache, mock_wms_client):
380
+ tile_mgr.creator().create_tiles([Tile((4, 0, 3)), Tile((5, 2, 3))])
381
+ assert mock_file_cache.stored_tiles == \
382
+ set([(4, 0, 3), (4, 1, 3), (4, 2, 3), (5, 0, 3), (5, 1, 3), (5, 2, 3)])
383
+ assert sorted(mock_wms_client.requested) == \
384
+ [((-1.7578125, -90, 91.7578125, 46.7578125), (532, 778), SRS(4326))]
385
+
386
+ class SlowMockSource(MockSource):
387
+ supports_meta_tiles = True
388
+ def get_map(self, query):
389
+ time.sleep(0.1)
390
+ return MockSource.get_map(self, query)
391
+
392
+ class TestTileManagerLocking(object):
393
+ @pytest.fixture
394
+ def slow_source(self):
395
+ return SlowMockSource()
396
+ @pytest.fixture
397
+ def file_cache(self, tmpdir):
398
+ return RecordFileCache(tmpdir.strpath, 'png')
399
+
400
+ @pytest.fixture
401
+ def tile_mgr(self, file_cache, slow_source, tile_locker):
402
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
403
+ image_opts = ImageOptions(format='image/png')
404
+ return TileManager(grid, file_cache, [slow_source], 'png',
405
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
406
+ locker=tile_locker,
407
+ )
408
+
409
+ def test_get_single(self, tile_mgr, file_cache, slow_source):
410
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
411
+ assert file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
412
+ assert slow_source.requested == \
413
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
414
+
415
+ def test_concurrent(self, tile_mgr, file_cache, slow_source):
416
+ def do_it():
417
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
418
+
419
+ threads = [threading.Thread(target=do_it) for _ in range(3)]
420
+ [t.start() for t in threads]
421
+ [t.join() for t in threads]
422
+
423
+ assert file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
424
+ assert file_cache.loaded_tiles == counting_set([(0, 0, 1), (1, 0, 1), (0, 0, 1), (1, 0, 1)])
425
+ assert slow_source.requested == \
426
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
427
+
428
+ assert os.path.exists(file_cache.tile_location(Tile((0, 0, 1))))
429
+
430
+
431
+
432
+ class TestTileManagerMultipleSources(object):
433
+ @pytest.fixture
434
+ def source_base(self):
435
+ return MockSource()
436
+
437
+ @pytest.fixture
438
+ def source_overlay(self):
439
+ return MockSource()
440
+
441
+ @pytest.fixture
442
+ def tile_mgr(self, mock_file_cache, tile_locker, source_base, source_overlay):
443
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
444
+ image_opts = ImageOptions(format='image/png')
445
+ return TileManager(grid, mock_file_cache,
446
+ [source_base, source_overlay], 'png',
447
+ image_opts=image_opts,
448
+ locker=tile_locker,
449
+ )
450
+
451
+ def test_get_single(self, tile_mgr, mock_file_cache, source_base, source_overlay):
452
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1))])
453
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1)])
454
+ assert source_base.requested == \
455
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326))]
456
+ assert source_overlay.requested == \
457
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326))]
458
+
459
+ class SolidColorMockSource(MockSource):
460
+ def __init__(self, color='#ff0000'):
461
+ MockSource.__init__(self)
462
+ self.color = color
463
+ def _image(self, size):
464
+ return Image.new('RGB', size, self.color)
465
+
466
+ class TestTileManagerMultipleSourcesWithMetaTiles(object):
467
+ @pytest.fixture
468
+ def source_base(self):
469
+ src = SolidColorMockSource(color='#ff0000')
470
+ src.supports_meta_tiles = True
471
+ return src
472
+
473
+ @pytest.fixture
474
+ def source_overlay(self):
475
+ src = MockSource()
476
+ src.supports_meta_tiles = True
477
+ return src
478
+
479
+ @pytest.fixture
480
+ def tile_mgr(self, mock_file_cache, tile_locker, source_base, source_overlay):
481
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
482
+ image_opts = ImageOptions(format='image/png')
483
+ return TileManager(grid, mock_file_cache,
484
+ [source_base, source_overlay], 'png',
485
+ image_opts=image_opts,
486
+ meta_size=[2, 2], meta_buffer=0,
487
+ locker=tile_locker,
488
+ )
489
+
490
+ def test_merged_tiles(self, tile_mgr, mock_file_cache, source_base, source_overlay):
491
+ tiles = tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
492
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
493
+ assert source_base.requested == \
494
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
495
+ assert source_overlay.requested == \
496
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
497
+
498
+ hist = tiles[0].source.as_image().histogram()
499
+ # lots of red (base), but not everything (overlay)
500
+ assert 55000 < hist[255] < 60000 # red = 0xff
501
+ assert 55000 < hist[256] # green = 0x00
502
+ assert 55000 < hist[512] # blue = 0x00
503
+
504
+
505
+ def test_sources_with_mixed_support_for_meta_tiles(self, mock_file_cache, source_base, source_overlay, tile_locker):
506
+ source_base.supports_meta_tiles = False
507
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
508
+ with pytest.raises(ValueError):
509
+ TileManager(grid, file_cache,
510
+ [source_base, source_overlay], 'png',
511
+ meta_size=[2, 2], meta_buffer=0,
512
+ locker=tile_locker)
513
+
514
+ def test_sources_with_no_support_for_meta_tiles(self, mock_file_cache, source_base, source_overlay, tile_locker):
515
+ source_base.supports_meta_tiles = False
516
+ source_overlay.supports_meta_tiles = False
517
+
518
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
519
+ tile_mgr = TileManager(grid, mock_file_cache,
520
+ [source_base, source_overlay], 'png',
521
+ meta_size=[2, 2], meta_buffer=0,
522
+ locker=tile_locker)
523
+
524
+ assert tile_mgr.meta_grid is None
525
+
526
+
527
+ class TestTileManagerBulkMetaTiles(object):
528
+ @pytest.fixture
529
+ def source_base(self):
530
+ src = SolidColorMockSource(color='#ff0000')
531
+ src.supports_meta_tiles = False
532
+ return src
533
+
534
+ @pytest.fixture
535
+ def source_overlay(self):
536
+ src = MockSource()
537
+ src.supports_meta_tiles = False
538
+ return src
539
+
540
+ @pytest.fixture
541
+ def tile_mgr(self, mock_file_cache, source_base, source_overlay, tile_locker):
542
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
543
+ return TileManager(grid, mock_file_cache,
544
+ [source_base, source_overlay], 'png',
545
+ meta_size=[2, 2], meta_buffer=0,
546
+ locker=tile_locker,
547
+ bulk_meta_tiles=True,
548
+ )
549
+
550
+ def test_bulk_get(self, tile_mgr, mock_file_cache, source_base, source_overlay):
551
+ tiles = tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
552
+ assert len(tiles) == 2*2
553
+ assert mock_file_cache.stored_tiles == set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)])
554
+ for requested in [source_base.requested, source_overlay.requested]:
555
+ assert set(requested) == set([
556
+ ((-180.0, 0.0, -90.0, 90.0), (256, 256), SRS(4326)),
557
+ ((-90.0, 0.0, 0.0, 90.0), (256, 256), SRS(4326)),
558
+ ((-180.0, -90.0, -90.0, 0.0), (256, 256), SRS(4326)),
559
+ ((-90.0, -90.0, 0.0, 0.0), (256, 256), SRS(4326)),
560
+ ])
561
+
562
+ def test_bulk_get_error(self, tile_mgr, source_base):
563
+ tile_mgr.sources = [source_base, ErrorSource()]
564
+ try:
565
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
566
+ except Exception as ex:
567
+ assert ex.args[0] == "source error"
568
+
569
+ def test_bulk_get_multiple_meta_tiles(self, tile_mgr, mock_file_cache):
570
+ tiles = tile_mgr.creator().create_tiles([Tile((1, 0, 2)), Tile((2, 0, 2))])
571
+ assert len(tiles) == 2*2*2
572
+ assert mock_file_cache.stored_tiles, set([
573
+ (0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
574
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2),
575
+ ])
576
+
577
+ class TestTileManagerBulkMetaTilesConcurrent(TestTileManagerBulkMetaTiles):
578
+ @pytest.fixture
579
+ def tile_mgr(self, mock_file_cache, source_base, source_overlay, tile_locker):
580
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
581
+ return TileManager(
582
+ grid, mock_file_cache,
583
+ [source_base, source_overlay], 'png',
584
+ meta_size=[2, 2], meta_buffer=0,
585
+ locker=tile_locker,
586
+ bulk_meta_tiles=True,
587
+ concurrent_tile_creators=2,
588
+ )
589
+
590
+ class ErrorSource(MapLayer):
591
+ def __init__(self, *args):
592
+ MapLayer.__init__(self, *args)
593
+ self.requested = []
594
+
595
+ def get_map(self, query):
596
+ self.requested.append((query.bbox, query.size, query.srs))
597
+ raise Exception("source error")
598
+
599
+
600
+ default_image_opts = ImageOptions(resampling='bicubic')
601
+
602
+ class TestCacheMapLayer(object):
603
+ @pytest.fixture
604
+ def layer(self, mock_file_cache, mock_wms_client, tile_locker):
605
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
606
+ source = WMSSource(mock_wms_client)
607
+ image_opts = ImageOptions(resampling='nearest')
608
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
609
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
610
+ locker=tile_locker)
611
+ return CacheMapLayer(tile_mgr, image_opts=default_image_opts)
612
+
613
+ def test_get_map_small(self, layer, mock_file_cache):
614
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
615
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
616
+ assert result.size == (300, 150)
617
+
618
+ def test_get_map_large(self, layer, mock_file_cache):
619
+ # gets next resolution layer
620
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (600, 300), SRS(4326), 'png'))
621
+ assert mock_file_cache.stored_tiles == \
622
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
623
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
624
+ assert result.size == (600, 300)
625
+
626
+ def test_transformed(self, layer, mock_file_cache):
627
+ result = layer.get_map(MapQuery(
628
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
629
+ SRS(900913), 'png'))
630
+ assert mock_file_cache.stored_tiles == \
631
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
632
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
633
+ assert result.size == (500, 500)
634
+
635
+ def test_single_tile_match(self, layer, mock_file_cache):
636
+ result = layer.get_map(MapQuery(
637
+ (0.001, 0, 90, 90), (256, 256), SRS(4326), 'png', tiled_only=True))
638
+ assert mock_file_cache.stored_tiles == \
639
+ set([(3, 0, 2), (2, 0, 2), (3, 1, 2), (2, 1, 2)])
640
+ assert result.size == (256, 256)
641
+
642
+ def test_single_tile_no_match(self, layer):
643
+ with pytest.raises(MapBBOXError):
644
+ layer.get_map(
645
+ MapQuery((0.1, 0, 90, 90), (256, 256),
646
+ SRS(4326), 'png', tiled_only=True)
647
+ )
648
+
649
+ def test_get_map_with_res_range(self, mock_file_cache, mock_wms_client, tile_locker):
650
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
651
+ res_range = resolution_range(1000, 10)
652
+ source = WMSSource(mock_wms_client, res_range=res_range)
653
+ image_opts = ImageOptions(resampling='nearest')
654
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
655
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
656
+ locker=tile_locker)
657
+ layer = CacheMapLayer(tile_mgr, image_opts=default_image_opts)
658
+
659
+ with pytest.raises(BlankImage):
660
+ result = layer.get_map(MapQuery(
661
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
662
+ SRS(900913), 'png'))
663
+ assert mock_file_cache.stored_tiles == set()
664
+
665
+ result = layer.get_map(MapQuery(
666
+ (0, 0, 10000, 10000), (50, 50),
667
+ SRS(900913), 'png'))
668
+ assert mock_file_cache.stored_tiles == \
669
+ set([(512, 257, 10), (513, 256, 10), (512, 256, 10), (513, 257, 10)])
670
+ assert result.size == (50, 50)
671
+
672
+ class TestCacheMapLayerWithExtent(object):
673
+ @pytest.fixture
674
+ def source(self, mock_wms_client):
675
+ return WMSSource(mock_wms_client)
676
+
677
+ @pytest.fixture
678
+ def layer(self, mock_file_cache, source, tile_locker):
679
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
680
+ image_opts = ImageOptions(resampling='nearest', format='png')
681
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
682
+ meta_size=[1, 1], meta_buffer=0, image_opts=image_opts,
683
+ locker=tile_locker)
684
+ layer = CacheMapLayer(tile_mgr, image_opts=default_image_opts)
685
+ layer.extent = BBOXCoverage([0, 0, 90, 45], SRS(4326)).extent
686
+ return layer
687
+
688
+ def test_get_outside_extent(self, layer):
689
+ with pytest.raises(BlankImage):
690
+ layer.get_map(MapQuery((-180, -90, 0, 0), (300, 150), SRS(4326), 'png'))
691
+
692
+ def test_get_map_small(self, layer, mock_file_cache, mock_wms_client):
693
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
694
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
695
+ # source requests one tile (no meta-tiling configured)
696
+ assert mock_wms_client.requested == [((0.0, -90.0, 180.0, 90.0), (256, 256), SRS('EPSG:4326'))]
697
+ assert result.size == (300, 150)
698
+
699
+ def test_get_map_small_with_source_extent(self, source, layer, mock_file_cache, mock_wms_client):
700
+ source.extent = BBOXCoverage([0, 0, 90, 45], SRS(4326)).extent
701
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
702
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
703
+ # source requests one tile (no meta-tiling configured) limited to source.extent
704
+ assert mock_wms_client.requested == [((0, 0, 90, 45), (128, 64), (SRS(4326)))]
705
+ assert result.size == (300, 150)
706
+
707
+ class TestDirectMapLayer(object):
708
+ @pytest.fixture
709
+ def layer(self, mock_wms_client):
710
+ source = WMSSource(mock_wms_client)
711
+ return DirectMapLayer(source, GLOBAL_GEOGRAPHIC_EXTENT)
712
+
713
+ def test_get_map(self, layer, mock_wms_client):
714
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
715
+ assert mock_wms_client.requested == [((-180, -90, 180, 90), (300, 150), SRS(4326))]
716
+ assert result.size == (300, 150)
717
+
718
+ def test_get_map_mercator(self, layer, mock_wms_client):
719
+ result = layer.get_map(MapQuery(
720
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
721
+ SRS(900913), 'png'))
722
+ assert mock_wms_client.requested == \
723
+ [((-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
724
+ SRS(900913))]
725
+ assert result.size == (500, 500)
726
+
727
+ class TestDirectMapLayerWithSupportedSRS(object):
728
+ @pytest.fixture
729
+ def layer(self, mock_wms_client):
730
+ source = WMSSource(mock_wms_client)
731
+ return DirectMapLayer(source, GLOBAL_GEOGRAPHIC_EXTENT)
732
+
733
+ def test_get_map(self, layer, mock_wms_client):
734
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
735
+ assert mock_wms_client.requested == [((-180, -90, 180, 90), (300, 150), SRS(4326))]
736
+ assert result.size == (300, 150)
737
+
738
+ def test_get_map_mercator(self, layer, mock_wms_client):
739
+ result = layer.get_map(MapQuery(
740
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
741
+ SRS(900913), 'png'))
742
+ assert mock_wms_client.requested == \
743
+ [((-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
744
+ SRS(900913))]
745
+ assert result.size == (500, 500)
746
+
747
+
748
+ class MockHTTPClient(object):
749
+ def __init__(self):
750
+ self.requested = []
751
+
752
+ def open(self, url, data=None):
753
+ self.requested.append(url)
754
+ w = int(re.search(r'width=(\d+)', url, re.IGNORECASE).group(1))
755
+ h = int(re.search(r'height=(\d+)', url, re.IGNORECASE).group(1))
756
+ format = re.search(r'format=image(/|%2F)(\w+)', url, re.IGNORECASE).group(2)
757
+ transparent = re.search(r'transparent=(\w+)', url, re.IGNORECASE)
758
+ transparent = True if transparent and transparent.group(1).lower() == 'true' else False
759
+ result = BytesIO()
760
+ create_debug_img((int(w), int(h)), transparent).save(result, format=format)
761
+ result.seek(0)
762
+ result.headers = {'Content-type': 'image/'+format}
763
+ return result
764
+
765
+
766
+ @pytest.fixture
767
+ def mock_http_client():
768
+ return MockHTTPClient()
769
+
770
+ class TestWMSSourceTransform(object):
771
+ @pytest.fixture
772
+ def source(self, mock_http_client):
773
+ req_template = WMS111MapRequest(url='http://localhost/service?', param={
774
+ 'format': 'image/png', 'layers': 'foo'
775
+ })
776
+ client = WMSClient(req_template, http_client=mock_http_client)
777
+ return WMSSource(client, supported_srs=SupportedSRS([SRS(4326)]),
778
+ image_opts=ImageOptions(resampling='bilinear'))
779
+
780
+ def test_get_map(self, source, mock_http_client):
781
+ source.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326)))
782
+ assert query_eq(mock_http_client.requested[0], "http://localhost/service?"
783
+ "layers=foo&width=300&version=1.1.1&bbox=-180,-90,180,90&service=WMS"
784
+ "&format=image%2Fpng&styles=&srs=EPSG%3A4326&request=GetMap&height=150")
785
+
786
+ def test_get_map_transformed(self, source, mock_http_client):
787
+ source.get_map(MapQuery(
788
+ (556597, 4865942, 1669792, 7361866), (300, 150), SRS(900913)))
789
+ assert wms_query_eq(mock_http_client.requested[0], "http://localhost/service?"
790
+ "layers=foo&width=300&version=1.1.1"
791
+ "&bbox=4.99999592195,39.9999980766,14.999996749,54.9999994175&service=WMS"
792
+ "&format=image%2Fpng&styles=&srs=EPSG%3A4326&request=GetMap&height=450")
793
+
794
+
795
+ class TestWMSSourceWithClient(object):
796
+
797
+ @pytest.fixture
798
+ def req_template(self):
799
+ return WMS111MapRequest(
800
+ url='http://%s:%d/service?' % TEST_SERVER_ADDRESS,
801
+ param={'format': 'image/png', 'layers': 'foo'},
802
+ )
803
+
804
+ @pytest.fixture
805
+ def client(self, req_template):
806
+ return WMSClient(req_template)
807
+
808
+ @pytest.fixture
809
+ def source(self, client):
810
+ return WMSSource(client)
811
+
812
+ def test_get_map(self, source):
813
+ with tmp_image((512, 512)) as img:
814
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
815
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles='
816
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512'},
817
+ {'body': img.read(), 'headers': {'content-type': 'image/png'}})
818
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
819
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
820
+ result = source.get_map(q)
821
+ assert isinstance(result, ImageSource)
822
+ assert result.size == (512, 512)
823
+ assert is_png(result.as_buffer(seekable=True))
824
+ assert result.as_image().size == (512, 512)
825
+
826
+ def test_get_map_non_image_content_type(self, source):
827
+ with tmp_image((512, 512)) as img:
828
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
829
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles='
830
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512'},
831
+ {'body': img.read(), 'headers': {'content-type': 'text/plain'}})
832
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
833
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
834
+ try:
835
+ source.get_map(q)
836
+ except SourceError as e:
837
+ assert 'no image returned' in e.args[0]
838
+ else:
839
+ assert False, 'no SourceError raised'
840
+
841
+ def test_basic_auth(self, req_template, client, source):
842
+ http_client = HTTPClient(req_template.url, username='foo', password='bar@')
843
+ client.http_client = http_client
844
+ def assert_auth(req_handler):
845
+ assert 'Authorization' in req_handler.headers
846
+ auth_data = req_handler.headers['Authorization'].split()[1]
847
+ auth_data = base64.b64decode(auth_data.encode('utf-8')).decode('utf-8')
848
+ assert auth_data == 'foo:bar@'
849
+ return True
850
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
851
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
852
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES=',
853
+ 'require_basic_auth': True,
854
+ 'req_assert_function': assert_auth},
855
+ {'body': b'no image', 'headers': {'content-type': 'image/png'}})
856
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
857
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
858
+ source.get_map(q)
859
+
860
+ def test_http_error_handler(self, client):
861
+ error_handler = HTTPSourceErrorHandler()
862
+ error_handler.add_handler(500, (255, 0, 0), cacheable=True)
863
+ error_handler.add_handler(400, (0, 0, 0), cacheable=False)
864
+ source = WMSSource(client, error_handler=error_handler)
865
+ expected_req = [
866
+ (
867
+ {
868
+ 'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
869
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
870
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES='
871
+ },
872
+ {
873
+ 'body': b'error',
874
+ 'status': 500,
875
+ 'headers': {'content-type': 'text/plain'},
876
+ },
877
+ ),
878
+ (
879
+ {
880
+ 'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
881
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
882
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES='
883
+ },
884
+ {
885
+ 'body': b'error',
886
+ 'status': 400,
887
+ 'headers': {'content-type': 'text/plain'},
888
+ },
889
+ ),
890
+ ]
891
+ with mock_httpd(TEST_SERVER_ADDRESS, expected_req):
892
+ query = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
893
+ resp = source.get_map(query)
894
+ assert resp.cacheable
895
+ assert resp.as_image().getcolors() == [((512 * 512), (255, 0, 0))]
896
+
897
+ resp = source.get_map(query)
898
+ assert not resp.cacheable
899
+ assert resp.as_image().getcolors() == [((512 * 512), (0, 0, 0))]
900
+
901
+
902
+ TESTSERVER_URL = 'http://%s:%d' % TEST_SERVER_ADDRESS
903
+
904
+ class TestWMSSource(object):
905
+
906
+ @pytest.fixture
907
+ def source(self, mock_http_client):
908
+ req = WMS111MapRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers':'foo'})
909
+ wms = WMSClient(req, http_client=mock_http_client)
910
+ return WMSSource(wms, supported_srs=SupportedSRS([SRS(4326)]),
911
+ image_opts=ImageOptions(resampling='bilinear'))
912
+
913
+ def test_request(self, source, mock_http_client):
914
+ req = MapQuery((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326), 'png')
915
+ source.get_map(req)
916
+ assert len(mock_http_client.requested) == 1
917
+ assert_query_eq(mock_http_client.requested[0],
918
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
919
+ '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326'
920
+ '&VERSION=1.1.1&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=512&STYLES=')
921
+
922
+ def test_transformed_request(self, source, mock_http_client):
923
+ req = MapQuery((-200000, -200000, 200000, 200000), (512, 512), SRS(900913), 'png')
924
+ resp = source.get_map(req)
925
+ assert len(mock_http_client.requested) == 1
926
+
927
+ assert wms_query_eq(mock_http_client.requested[0],
928
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
929
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
930
+ '&VERSION=1.1.1&WIDTH=512&STYLES='
931
+ '&BBOX=-1.79663056824,-1.7963362121,1.79663056824,1.7963362121')
932
+ img = resp.as_image()
933
+ assert img.mode in ('P', 'RGB')
934
+
935
+ def test_transformed_request_transparent(self, mock_http_client):
936
+ req = WMS111MapRequest(url=TESTSERVER_URL + '/service?map=foo',
937
+ param={'layers':'foo', 'transparent': 'true'})
938
+ wms = WMSClient(req, http_client=mock_http_client)
939
+ source = WMSSource(wms, supported_srs=SupportedSRS([SRS(4326)]),
940
+ image_opts=ImageOptions(resampling='bilinear'))
941
+
942
+ req = MapQuery((-200000, -200000, 200000, 200000), (512, 512), SRS(900913), 'png')
943
+ resp = source.get_map(req)
944
+ assert len(mock_http_client.requested) == 1
945
+
946
+ assert wms_query_eq(mock_http_client.requested[0],
947
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
948
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
949
+ '&VERSION=1.1.1&WIDTH=512&STYLES=&transparent=true'
950
+ '&BBOX=-1.79663056824,-1.7963362121,1.79663056824,1.7963362121')
951
+ img = resp.as_image()
952
+ assert img.mode in ('P', 'RGBA')
953
+ img = img.convert('RGBA')
954
+ assert img.getpixel((5, 5))[3] == 0
955
+
956
+
957
+ class MockLayer(object):
958
+ def __init__(self):
959
+ self.requested = []
960
+ def get_map(self, query):
961
+ self.requested.append((query.bbox, query.size, query.srs))
962
+
963
+ @pytest.mark.parametrize('case,map_query,low_requested', [
964
+ ['low', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(3857)), True],
965
+ ['high', MapQuery((0, 0, 100, 100), (100, 100), SRS(3857)), False],
966
+ ['match', MapQuery((0, 0, 10, 10), (100, 100), SRS(3857)), False],
967
+ ['low_transform', MapQuery((0, 0, 0.1, 0.1), (100, 100), SRS(4326)), True],
968
+ ['high_transform', MapQuery((0, 0, 0.005, 0.005), (100, 100), SRS(4326)), False],
969
+ ])
970
+ def test_resolution_conditional_layers(case, map_query, low_requested):
971
+ low = MockLayer()
972
+ high = MockLayer()
973
+ layer = ResolutionConditional(low, high, 10, SRS(3857),
974
+ GLOBAL_GEOGRAPHIC_EXTENT)
975
+
976
+ layer.get_map(map_query)
977
+ assert bool(low.requested) == low_requested
978
+ assert bool(high.requested) != low_requested
979
+
980
+
981
+ def test_srs_conditional_layers():
982
+ l4326 = MockLayer()
983
+ l3857 = MockLayer()
984
+ l25832 = MockLayer()
985
+ preferred = PreferredSrcSRS()
986
+ preferred.add(SRS(31467), [SRS(25832), SRS(3857)])
987
+ layer = SRSConditional([
988
+ (l4326, SRS(4326)),
989
+ (l3857, SRS(3857)),
990
+ (l25832, SRS(25832)),
991
+ ], GLOBAL_GEOGRAPHIC_EXTENT, preferred_srs=preferred,
992
+ )
993
+
994
+ # srs match
995
+ assert layer._select_layer(SRS(4326)) == l4326
996
+ assert layer._select_layer(SRS(3857)) == l3857
997
+ assert layer._select_layer(SRS(25832)) == l25832
998
+ # type match (projected)
999
+ assert layer._select_layer(SRS(31466)) == l3857
1000
+ assert layer._select_layer(SRS(32633)) == l3857
1001
+ assert layer._select_layer(SRS(4258)) == l4326
1002
+ # preferred
1003
+ assert layer._select_layer(SRS(31467)) == l25832
1004
+
1005
+ @pytest.mark.parametrize('case,map_query,is_direct,is_l3857,is_l4326', [
1006
+ ['high_3857', MapQuery((0, 0, 100, 100), (100, 100), SRS(900913)), True, False, False],
1007
+ ['high_4326', MapQuery((0, 0, 0.0001, 0.0001), (100, 100), SRS(4326)), True, False, False],
1008
+ ['low_4326', MapQuery((0, 0, 10, 10), (100, 100), SRS(4326)), False, False, True],
1009
+ ['low_3857', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(31467)), False, True, False],
1010
+ ['low_projected', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(31467)), False, True, False],
1011
+ ])
1012
+ def test_neasted_conditional_layers(case, map_query, is_direct, is_l3857, is_l4326):
1013
+ direct = MockLayer()
1014
+ l3857 = MockLayer()
1015
+ l4326 = MockLayer()
1016
+ layer = ResolutionConditional(
1017
+ SRSConditional([
1018
+ (l3857, SRS('EPSG:3857')),
1019
+ (l4326, SRS('EPSG:4326'))
1020
+ ], GLOBAL_GEOGRAPHIC_EXTENT),
1021
+ direct, 10, SRS(3857), GLOBAL_GEOGRAPHIC_EXTENT
1022
+ )
1023
+ layer.get_map(map_query)
1024
+ assert bool(direct.requested) == is_direct
1025
+ assert bool(l3857.requested) == is_l3857
1026
+ assert bool(l4326.requested) == is_l4326
1027
+
1028
+
1029
+ def is_blank(tiles):
1030
+ return all([t.source is None or isinstance(t.source, BlankImageSource) for t in tiles.tiles])
1031
+
1032
+ class TestTileManagerEmptySources(object):
1033
+ def test_upscale(self, mock_file_cache, tile_locker):
1034
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
1035
+ image_opts = ImageOptions(format='image/png')
1036
+ tm = TileManager(
1037
+ grid, mock_file_cache, [], 'png',
1038
+ locker=tile_locker,
1039
+ image_opts=image_opts,
1040
+ )
1041
+
1042
+ assert is_blank(tm.load_tile_coords([(0, 0, 0)]))
1043
+ assert is_blank(tm.load_tile_coords([(3, 2, 5)]))
1044
+
1045
+ assert mock_file_cache.stored_tiles == set()
1046
+ assert mock_file_cache.loaded_tiles == counting_set([(0, 0, 0), (3, 2, 5)])
1047
+
1048
+ class TestTileManagerRescaleTiles(object):
1049
+ @pytest.fixture
1050
+ def file_cache(self, tmpdir):
1051
+ return RecordFileCache(tmpdir.strpath, 'png')
1052
+
1053
+ @pytest.mark.parametrize("name,rescale_tiles,tiles,store,expected_load,output", [
1054
+ (
1055
+ "no-scale: missing tile, no rescale",
1056
+ 0, [(0, 0, 0)], [], [(0, 0, 0)], "blank",
1057
+ ),
1058
+ (
1059
+ "downscale: missing tile, 1 level rescale with None tiles",
1060
+ 1, [(0, 0, 0)], [], [(0, 0, 0), None, None, (0, 0, 1), (1, 0, 1)], "blank",
1061
+ ),
1062
+ (
1063
+ "downscale: missing tile, 1 level rescale",
1064
+ 1, [(1, 2, 4)], [], [(1, 2, 4), (2, 4, 5), (3, 4, 5), (2, 5, 5), (3, 5, 5)], "blank",
1065
+ ),
1066
+ (
1067
+ "downscale: missing tile, 2 level rescale",
1068
+ 2, [(1, 2, 4)], [], [
1069
+ (1, 2, 4),
1070
+ (2, 4, 5), (3, 4, 5), (2, 5, 5), (3, 5, 5),
1071
+ (4, 8, 6), (5, 8, 6), (6, 8, 6), (7, 8, 6),
1072
+ (4, 9, 6), (5, 9, 6), (6, 9, 6), (7, 9, 6),
1073
+ (4, 10, 6), (5, 10, 6), (6, 10, 6), (7, 10, 6),
1074
+ (4, 11, 6), (5, 11, 6), (6, 11, 6), (7, 11, 6),
1075
+ ], "blank",
1076
+ ),
1077
+ (
1078
+ "downscale: exact tile cached",
1079
+ 1, [(0, 0, 1)], [(0, 0, 1)], [(0, 0, 1)], "full",
1080
+ ),
1081
+ (
1082
+ "downscale: next level tiles partially cached",
1083
+ 1, [(0, 0, 1)], [(0, 0, 2)], [(0, 0, 1), (0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)], "partial",
1084
+ ),
1085
+ (
1086
+ "downscale: next level tiles fully cached",
1087
+ 1, [(0, 0, 1)], [(0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)], [(0, 0, 1), (0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)], "full",
1088
+ ),
1089
+ (
1090
+ "upscale: missing tile level 1 rescale",
1091
+ -1, [(16, 8, 5)], [], [(16, 8, 5), (8, 4, 4)], "blank",
1092
+ ),
1093
+ (
1094
+ "upscale: missing tile level 1 rescale, odd coords",
1095
+ -1, [(15, 7, 5)], [], [(15, 7, 5), (7, 3, 4)], "blank",
1096
+ ),
1097
+ (
1098
+ "upscale: missing tile level 1 rescale, multiple tiles",
1099
+ -1, [(15, 6, 5), (16, 6, 5), (15, 7, 5), (16, 7, 5)], [],
1100
+ [(15, 6, 5), (16, 6, 5), (15, 7, 5), (16, 7, 5), (7, 3, 4), (8, 3, 4)], "blank",
1101
+ ),
1102
+ (
1103
+ "upscale: tile in level 2",
1104
+ -3, [(15, 7, 5)], [(3, 1, 3)], [(15, 7, 5), (7, 3, 4), (3, 1, 3)], "full",
1105
+ ),
1106
+ (
1107
+ "upscale: missing tile level 99 rescale",
1108
+ -99, [(16, 8, 5)], [], [(16, 8, 5), (8, 4, 4), (4, 2, 3), (2, 1, 2), (1, 0, 1), (0, 0, 0)], "blank",
1109
+ ),
1110
+ (
1111
+ "upscale: unregular grid, partial match",
1112
+ -2, [(201, 101, 10)], [(78, 40, 8), (79, 40, 8), (79, 39, 8)], [
1113
+ (201, 101, 10), (100, 50, 9),
1114
+ # check all four tiles above 100/50/9
1115
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1116
+ ], "partial",
1117
+ ),
1118
+ (
1119
+ "upscale: unregular grid, multiple tiles, partial match",
1120
+ -2, [(200, 100, 10), (201, 100, 10), (200, 101, 10), (201, 101, 10)],
1121
+ [(78, 40, 8), (79, 40, 8), (79, 39, 8)], [
1122
+ (200, 100, 10), (201, 100, 10), (200, 101, 10), (201, 101, 10),
1123
+ (100, 50, 9),
1124
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1125
+ ], "partial",
1126
+ ),
1127
+ (
1128
+ "upscale: unregular grid",
1129
+ -3, [(200, 100, 10)], [], [
1130
+ (200, 100, 10), (100, 50, 9),
1131
+ # check all four tiles above 100/50/9
1132
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1133
+ # check tiles above level 8 for each tile individually.
1134
+ # this is due to the recursive nature of our rescaling algorithm
1135
+ (49, 24, 7),
1136
+ (50, 24, 7),
1137
+ (49, 25, 7),
1138
+ (50, 25, 7),
1139
+ (49, 26, 7),
1140
+ (50, 26, 7),
1141
+ ],
1142
+ "blank",
1143
+ ),
1144
+ ])
1145
+ def test_scaled_tiles(self, name, file_cache, tile_locker, rescale_tiles, tiles, store, expected_load, output):
1146
+ res = [
1147
+ 1.40625, # 0
1148
+ 0.703125, # 1
1149
+ 0.3515625, # 2
1150
+ 0.17578125, # 3
1151
+ 0.087890625, # 4
1152
+ 0.0439453125, # 5
1153
+ 0.02197265625, # 6
1154
+ 0.010986328125, # 7
1155
+ 0.007, # 8 additional resolution to test unregular grids
1156
+ 0.0054931640625, # 9
1157
+ 0.00274658203125, # 10
1158
+ ]
1159
+ grid = TileGrid(SRS(4326), origin='sw', bbox=[-180, -90, 180, 90], res=res)
1160
+ image_opts = ImageOptions(format='image/png', resampling='nearest')
1161
+ tm = TileManager(
1162
+ grid, file_cache, [], 'png',
1163
+ locker=tile_locker,
1164
+ image_opts=image_opts,
1165
+ rescale_tiles=rescale_tiles,
1166
+ )
1167
+
1168
+ if store:
1169
+ colors = set()
1170
+ if output == "partial":
1171
+ colors.add((255, 255, 255))
1172
+ for i, t in enumerate(store):
1173
+ color = (150+i*35, 5+i*35, 5+i*35)
1174
+ colors.add(color)
1175
+ tile = Tile(t, ImageSource(create_tmp_image_buf((256, 256), color=color)))
1176
+ file_cache.store_tile(tile)
1177
+
1178
+ loaded_tiles = tm.load_tile_coords(tiles)
1179
+ assert not is_blank(loaded_tiles)
1180
+ assert len(loaded_tiles) == len(tiles)
1181
+ got_colors = set()
1182
+ for t in loaded_tiles:
1183
+ got_colors.update([c for _, c in t.source.as_image().getcolors()])
1184
+ assert got_colors == colors
1185
+ else:
1186
+ loaded_tiles = tm.load_tile_coords(tiles)
1187
+ assert is_blank(loaded_tiles) == (output == "blank")
1188
+ assert len(loaded_tiles.tiles) == len(tiles)
1189
+
1190
+ assert file_cache.stored_tiles == set(store)
1191
+ assert file_cache.loaded_tiles == counting_set(expected_load)
1192
+
1193
+