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,1356 @@
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.config.coverage import load_coverage
37
+ from mapproxy.grid import TileGrid, resolution_range
38
+ from mapproxy.image import ImageSource, BlankImageSource
39
+ from mapproxy.image.opts import ImageOptions
40
+ from mapproxy.layer import (
41
+ BlankImage,
42
+ CacheMapLayer,
43
+ DirectMapLayer,
44
+ MapBBOXError,
45
+ MapExtent,
46
+ MapLayer,
47
+ MapQuery,
48
+ ResolutionConditional,
49
+ SRSConditional,
50
+ )
51
+ from mapproxy.request.wms import WMS111MapRequest
52
+ from mapproxy.source import InvalidSourceQuery, SourceError
53
+ from mapproxy.source.tile import TiledSource
54
+ from mapproxy.source.wms import WMSSource
55
+ from mapproxy.source.error import HTTPSourceErrorHandler
56
+ from mapproxy.srs import SRS, SupportedSRS, PreferredSrcSRS
57
+ from mapproxy.test.helper import TempFile
58
+ from mapproxy.test.http import assert_query_eq, wms_query_eq, query_eq, mock_httpd
59
+ from mapproxy.test.image import create_debug_img, is_png, tmp_image, create_tmp_image_buf
60
+ from mapproxy.util.coverage import BBOXCoverage, coverage
61
+
62
+
63
+ TEST_SERVER_ADDRESS = ('127.0.0.1', 56413)
64
+ GLOBAL_GEOGRAPHIC_EXTENT = MapExtent((-180, -90, 180, 90), SRS(4326))
65
+
66
+ tmp_lock_dir = None
67
+
68
+
69
+ def setup():
70
+ global tmp_lock_dir
71
+ tmp_lock_dir = tempfile.mkdtemp()
72
+
73
+
74
+ def teardown():
75
+ shutil.rmtree(tmp_lock_dir)
76
+
77
+
78
+ class counting_set(object):
79
+ def __init__(self, items):
80
+ self.data = defaultdict(int)
81
+ for item in items:
82
+ self.data[item] += 1
83
+
84
+ def add(self, item):
85
+ self.data[item] += 1
86
+
87
+ def __repr__(self):
88
+ return 'counting_set(%r)' % dict(self.data)
89
+
90
+ def __eq__(self, other):
91
+ return self.data == other.data
92
+
93
+
94
+ class MockTileClient(object):
95
+ def __init__(self):
96
+ self.requested_tiles = []
97
+
98
+ def get_tile(self, tile_coord, format=None):
99
+ self.requested_tiles.append(tile_coord)
100
+ return ImageSource(create_debug_img((256, 256)))
101
+
102
+
103
+ class TestTiledSourceGlobalGeodetic(object):
104
+ def setup_method(self):
105
+ self.grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
106
+ self.client = MockTileClient()
107
+ self.source = TiledSource(self.grid, self.client)
108
+
109
+ def test_match(self):
110
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326)))
111
+ self.source.get_map(MapQuery([0, -90, 180, 90], (256, 256), SRS(4326)))
112
+ assert self.client.requested_tiles == [(0, 0, 1), (1, 0, 1)]
113
+
114
+ def test_wrong_size(self):
115
+ with pytest.raises(InvalidSourceQuery):
116
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (512, 256), SRS(4326)))
117
+
118
+ def test_wrong_srs(self):
119
+ with pytest.raises(InvalidSourceQuery):
120
+ self.source.get_map(MapQuery([-180, -90, 0, 90], (512, 256), SRS(4326)))
121
+
122
+
123
+ class RecordFileCache(FileCache):
124
+ def __init__(self, *args, **kw):
125
+ super(RecordFileCache, self).__init__(*args, **kw)
126
+ self.stored_tiles = set()
127
+ self.loaded_tiles = counting_set([])
128
+ self.is_cached_call_count = 0
129
+
130
+ def store_tile(self, tile, dimensions=None):
131
+ assert tile.coord not in self.stored_tiles
132
+ self.stored_tiles.add(tile.coord)
133
+ if self.cache_dir != '/dev/null':
134
+ FileCache.store_tile(self, tile, dimensions=dimensions)
135
+
136
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
137
+ if tile.source:
138
+ # Do not record tiles with source as "loaded" as FileCache will
139
+ # return tile without checking/loading from filesystem.
140
+ return True
141
+ self.loaded_tiles.add(tile.coord)
142
+ return FileCache.load_tile(self, tile, with_metadata, dimensions=dimensions)
143
+
144
+ def is_cached(self, tile, dimensions=None):
145
+ self.is_cached_call_count += 1
146
+ return tile.coord in self.stored_tiles
147
+
148
+
149
+ def create_cached_tile(tile, cache, timestamp=None):
150
+ loc = cache.tile_location(tile, create_dir=True)
151
+ with open(loc, 'wb') as f:
152
+ f.write(b'foo')
153
+
154
+ if timestamp:
155
+ os.utime(loc, (timestamp, timestamp))
156
+
157
+
158
+ @pytest.fixture
159
+ def file_cache(tmpdir):
160
+ return FileCache(cache_dir=tmpdir.join('cache').strpath, file_ext='png')
161
+
162
+
163
+ @pytest.fixture
164
+ def tile_locker(tmpdir):
165
+ return TileLocker(tmpdir.join('lock').strpath, 10, "id")
166
+
167
+
168
+ @pytest.fixture
169
+ def mock_tile_client():
170
+ return MockTileClient()
171
+
172
+
173
+ @pytest.fixture
174
+ def mock_file_cache():
175
+ return RecordFileCache('/dev/null', 'png')
176
+
177
+
178
+ class TestTileManagerStaleTiles(object):
179
+
180
+ @pytest.fixture
181
+ def tile_mgr(self, file_cache, tile_locker):
182
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
183
+ client = MockTileClient()
184
+ source = TiledSource(grid, client)
185
+ tile_mgr = TileManager(grid, file_cache, [source], 'png', locker=tile_locker)
186
+ return tile_mgr
187
+
188
+ def test_is_stale_missing(self, tile_mgr):
189
+ assert not tile_mgr.is_stale(Tile((0, 0, 1)))
190
+
191
+ def test_is_stale_not_expired(self, tile_mgr, file_cache):
192
+ create_cached_tile(Tile((0, 0, 1)), file_cache)
193
+ assert not tile_mgr.is_stale(Tile((0, 0, 1)))
194
+
195
+ def test_is_stale_expired(self, tile_mgr, file_cache):
196
+ create_cached_tile(Tile((0, 0, 1)), file_cache, timestamp=time.time()-3600)
197
+ tile_mgr._expire_timestamp = time.time()
198
+ assert tile_mgr.is_stale(Tile((0, 0, 1)))
199
+
200
+
201
+ class TestTileManagerRemoveTiles(object):
202
+ @pytest.fixture
203
+ def tile_mgr(self, file_cache, tile_locker):
204
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
205
+ client = MockTileClient()
206
+ source = TiledSource(grid, client)
207
+ image_opts = ImageOptions(format='image/png')
208
+ return TileManager(grid, file_cache, [source], 'png',
209
+ image_opts=image_opts,
210
+ locker=tile_locker)
211
+
212
+ def test_remove_missing(self, tile_mgr):
213
+ tile_mgr.remove_tile_coords([(0, 0, 0), (0, 0, 1)])
214
+
215
+ def test_remove_existing(self, tile_mgr, file_cache):
216
+ create_cached_tile(Tile((0, 0, 1)), file_cache)
217
+ assert tile_mgr.is_cached(Tile((0, 0, 1)))
218
+ tile_mgr.remove_tile_coords([(0, 0, 0), (0, 0, 1)])
219
+ assert not tile_mgr.is_cached(Tile((0, 0, 1)))
220
+
221
+
222
+ class TestTileManagerTiledSource(object):
223
+ @pytest.fixture
224
+ def tile_mgr(self, tile_locker, mock_file_cache, mock_tile_client):
225
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
226
+ source = TiledSource(grid, mock_tile_client)
227
+ image_opts = ImageOptions(format='image/png')
228
+ return TileManager(grid, mock_file_cache, [source], 'png',
229
+ image_opts=image_opts,
230
+ locker=tile_locker,
231
+ )
232
+
233
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_tile_client):
234
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
235
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
236
+ assert sorted(mock_tile_client.requested_tiles) == [(0, 0, 1), (1, 0, 1)]
237
+
238
+
239
+ class TestTileManagerDifferentSourceGrid(object):
240
+ @pytest.fixture
241
+ def tile_mgr(self, mock_file_cache, mock_tile_client, tile_locker):
242
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
243
+ source_grid = TileGrid(SRS(4326), bbox=[0, -90, 180, 90])
244
+ source = TiledSource(source_grid, mock_tile_client)
245
+ image_opts = ImageOptions(format='image/png')
246
+ return TileManager(grid, mock_file_cache, [source], 'png',
247
+ image_opts=image_opts,
248
+ locker=tile_locker,
249
+ )
250
+
251
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_tile_client):
252
+ tile_mgr.creator().create_tiles([Tile((1, 0, 1))])
253
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
254
+ assert mock_tile_client.requested_tiles == [(0, 0, 0)]
255
+
256
+ def test_create_tiles_out_of_bounds(self, tile_mgr):
257
+ with pytest.raises(InvalidSourceQuery):
258
+ tile_mgr.creator().create_tiles([Tile((0, 0, 0))])
259
+
260
+
261
+ class MockSource(MapLayer):
262
+ def __init__(self, *args):
263
+ MapLayer.__init__(self, *args)
264
+ self.requested = []
265
+
266
+ def _image(self, size):
267
+ return create_debug_img(size)
268
+
269
+ def get_map(self, query):
270
+ self.requested.append((query.bbox, query.size, query.srs))
271
+ return ImageSource(self._image(query.size))
272
+
273
+
274
+ @pytest.fixture
275
+ def mock_source():
276
+ return MockSource()
277
+
278
+
279
+ class TestTileManagerSource(object):
280
+
281
+ @pytest.fixture
282
+ def tile_mgr(self, mock_file_cache, mock_source, tile_locker):
283
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
284
+ image_opts = ImageOptions(format='image/png')
285
+ return TileManager(grid, mock_file_cache, [mock_source], 'png',
286
+ image_opts=image_opts,
287
+ locker=tile_locker,
288
+ )
289
+
290
+ def test_create_tile(self, tile_mgr, mock_file_cache, mock_source):
291
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
292
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
293
+ assert sorted(mock_source.requested) == \
294
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326)),
295
+ ((0.0, -90.0, 180.0, 90.0), (256, 256), SRS(4326))]
296
+
297
+
298
+ class MockWMSClient(object):
299
+ def __init__(self):
300
+ self.requested = []
301
+
302
+ def retrieve(self, query, format):
303
+ self.requested.append((query.bbox, query.size, query.srs))
304
+ return create_debug_img(query.size)
305
+
306
+
307
+ @pytest.fixture
308
+ def mock_wms_client():
309
+ return MockWMSClient()
310
+
311
+
312
+ class TestTileManagerWMSSource(object):
313
+ @pytest.fixture
314
+ def tile_mgr(self, mock_file_cache, tile_locker, mock_wms_client):
315
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
316
+ source = WMSSource(mock_wms_client)
317
+ image_opts = ImageOptions(format='image/png')
318
+ return TileManager(grid, mock_file_cache, [source], 'png',
319
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
320
+ locker=tile_locker,
321
+ )
322
+
323
+ def test_same_lock_for_meta_tile(self, tile_mgr):
324
+ assert tile_mgr.lock(Tile((0, 0, 1))).lock_file == \
325
+ tile_mgr.lock(Tile((1, 0, 1))).lock_file
326
+
327
+ def test_locks_for_meta_tiles(self, tile_mgr):
328
+ assert tile_mgr.lock(Tile((0, 0, 2))).lock_file != \
329
+ tile_mgr.lock(Tile((2, 0, 2))).lock_file
330
+
331
+ def test_create_tile_first_level(self, tile_mgr, mock_file_cache, mock_wms_client):
332
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
333
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
334
+ assert mock_wms_client.requested == \
335
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
336
+
337
+ def test_create_tile(self, tile_mgr, mock_file_cache, mock_wms_client):
338
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
339
+ assert mock_file_cache.stored_tiles == \
340
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)])
341
+ assert sorted(mock_wms_client.requested) == \
342
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326))]
343
+
344
+ def test_create_tiles(self, tile_mgr, mock_file_cache, mock_wms_client):
345
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2)), Tile((2, 0, 2))])
346
+ assert mock_file_cache.stored_tiles == \
347
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
348
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
349
+ assert sorted(mock_wms_client.requested) == \
350
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326)),
351
+ ((0.0, -90.0, 180.0, 90.0), (512, 512), SRS(4326))]
352
+
353
+ def test_load_tile_coords(self, tile_mgr, mock_file_cache, mock_wms_client):
354
+ tiles = tile_mgr.load_tile_coords(((0, 0, 2), (2, 0, 2)))
355
+ assert tiles[0].coord == (0, 0, 2)
356
+ assert isinstance(tiles[0].source, ImageSource)
357
+ assert tiles[1].coord == (2, 0, 2)
358
+ assert isinstance(tiles[1].source, ImageSource)
359
+
360
+ assert mock_file_cache.stored_tiles == \
361
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
362
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
363
+ assert sorted(mock_wms_client.requested) == \
364
+ [((-180.0, -90.0, 0.0, 90.0), (512, 512), SRS(4326)),
365
+ ((0.0, -90.0, 180.0, 90.0), (512, 512), SRS(4326))]
366
+
367
+
368
+ class TestTileManagerWMSSourceConcurrent(TestTileManagerWMSSource):
369
+ @pytest.fixture
370
+ def tile_mgr(self, mock_file_cache, tile_locker, mock_wms_client):
371
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
372
+ source = WMSSource(mock_wms_client)
373
+ image_opts = ImageOptions(format='image/png')
374
+ return TileManager(grid, mock_file_cache, [source], 'png',
375
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
376
+ locker=tile_locker,
377
+ concurrent_tile_creators=2,
378
+ )
379
+
380
+
381
+ class TestTileManagerWMSSourceMinimalMetaRequests(object):
382
+ @pytest.fixture
383
+ def tile_mgr(self, mock_file_cache, mock_wms_client, tile_locker):
384
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
385
+ source = WMSSource(mock_wms_client)
386
+ return TileManager(grid, mock_file_cache, [source], 'png',
387
+ meta_size=[2, 2], meta_buffer=10, minimize_meta_requests=True,
388
+ locker=tile_locker,
389
+ )
390
+
391
+ def test_create_tile_single(self, tile_mgr, mock_file_cache, mock_wms_client):
392
+ # not enabled for single tile requests
393
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
394
+ assert mock_file_cache.stored_tiles == \
395
+ set([(0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)])
396
+ assert sorted(mock_wms_client.requested) == \
397
+ [((-180.0, -90.0, 3.515625, 90.0), (522, 512), SRS(4326))]
398
+
399
+ def test_create_tile_multiple(self, tile_mgr, mock_file_cache, mock_wms_client):
400
+ tile_mgr.creator().create_tiles([Tile((4, 0, 3)), Tile((4, 1, 3)), Tile((4, 2, 3))])
401
+ assert mock_file_cache.stored_tiles == \
402
+ set([(4, 0, 3), (4, 1, 3), (4, 2, 3)])
403
+ assert sorted(mock_wms_client.requested) == \
404
+ [((-1.7578125, -90, 46.7578125, 46.7578125), (276, 778), SRS(4326))]
405
+
406
+ def test_create_tile_multiple_fragmented(self, tile_mgr, mock_file_cache, mock_wms_client):
407
+ tile_mgr.creator().create_tiles([Tile((4, 0, 3)), Tile((5, 2, 3))])
408
+ assert mock_file_cache.stored_tiles == \
409
+ set([(4, 0, 3), (4, 1, 3), (4, 2, 3), (5, 0, 3), (5, 1, 3), (5, 2, 3)])
410
+ assert sorted(mock_wms_client.requested) == \
411
+ [((-1.7578125, -90, 91.7578125, 46.7578125), (532, 778), SRS(4326))]
412
+
413
+
414
+ class SlowMockSource(MockSource):
415
+ supports_meta_tiles = True
416
+
417
+ def get_map(self, query):
418
+ time.sleep(0.1)
419
+ return MockSource.get_map(self, query)
420
+
421
+
422
+ class TestTileManagerLocking(object):
423
+ @pytest.fixture
424
+ def slow_source(self):
425
+ return SlowMockSource()
426
+
427
+ @pytest.fixture
428
+ def file_cache(self, tmpdir):
429
+ return RecordFileCache(tmpdir.strpath, 'png')
430
+
431
+ @pytest.fixture
432
+ def tile_mgr(self, file_cache, slow_source, tile_locker):
433
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
434
+ image_opts = ImageOptions(format='image/png')
435
+ return TileManager(grid, file_cache, [slow_source], 'png',
436
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
437
+ locker=tile_locker,
438
+ )
439
+
440
+ def test_get_single(self, tile_mgr, file_cache, slow_source):
441
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
442
+ assert file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
443
+ assert slow_source.requested == \
444
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
445
+
446
+ def test_concurrent(self, tile_mgr, file_cache, slow_source):
447
+ def do_it():
448
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
449
+
450
+ threads = [threading.Thread(target=do_it) for _ in range(3)]
451
+ [t.start() for t in threads]
452
+ [t.join() for t in threads]
453
+
454
+ assert file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
455
+ assert file_cache.loaded_tiles == counting_set([(0, 0, 1), (1, 0, 1), (0, 0, 1), (1, 0, 1)])
456
+ assert slow_source.requested == \
457
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
458
+
459
+ assert os.path.exists(file_cache.tile_location(Tile((0, 0, 1))))
460
+
461
+
462
+ class TestTileManagerMultipleSources(object):
463
+ @pytest.fixture
464
+ def source_base(self):
465
+ return MockSource()
466
+
467
+ @pytest.fixture
468
+ def source_overlay(self):
469
+ return MockSource()
470
+
471
+ @pytest.fixture
472
+ def tile_mgr(self, mock_file_cache, tile_locker, source_base, source_overlay):
473
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
474
+ image_opts = ImageOptions(format='image/png')
475
+ return TileManager(grid, mock_file_cache,
476
+ [source_base, source_overlay], 'png',
477
+ image_opts=image_opts,
478
+ locker=tile_locker,
479
+ )
480
+
481
+ def test_get_single(self, tile_mgr, mock_file_cache, source_base, source_overlay):
482
+ tile_mgr.creator().create_tiles([Tile((0, 0, 1))])
483
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1)])
484
+ assert source_base.requested == \
485
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326))]
486
+ assert source_overlay.requested == \
487
+ [((-180.0, -90.0, 0.0, 90.0), (256, 256), SRS(4326))]
488
+
489
+
490
+ class SolidColorMockSource(MockSource):
491
+ def __init__(self, color='#ff0000'):
492
+ MockSource.__init__(self)
493
+ self.color = color
494
+
495
+ def _image(self, size):
496
+ return Image.new('RGB', size, self.color)
497
+
498
+
499
+ class TestTileManagerMultipleSourcesWithMetaTiles(object):
500
+ @pytest.fixture
501
+ def source_base(self):
502
+ src = SolidColorMockSource(color='#ff0000')
503
+ src.supports_meta_tiles = True
504
+ return src
505
+
506
+ @pytest.fixture
507
+ def source_overlay(self):
508
+ src = MockSource()
509
+ src.supports_meta_tiles = True
510
+ return src
511
+
512
+ @pytest.fixture
513
+ def tile_mgr(self, mock_file_cache, tile_locker, source_base, source_overlay):
514
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
515
+ image_opts = ImageOptions(format='image/png')
516
+ return TileManager(grid, mock_file_cache,
517
+ [source_base, source_overlay], 'png',
518
+ image_opts=image_opts,
519
+ meta_size=[2, 2], meta_buffer=0,
520
+ locker=tile_locker,
521
+ )
522
+
523
+ def test_merged_tiles(self, tile_mgr, mock_file_cache, source_base, source_overlay):
524
+ tiles = tile_mgr.creator().create_tiles([Tile((0, 0, 1)), Tile((1, 0, 1))])
525
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
526
+ assert source_base.requested == \
527
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
528
+ assert source_overlay.requested == \
529
+ [((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326))]
530
+
531
+ hist = tiles[0].source.as_image().histogram()
532
+ # lots of red (base), but not everything (overlay)
533
+ assert 55000 < hist[255] < 60000 # red = 0xff
534
+ assert 55000 < hist[256] # green = 0x00
535
+ assert 55000 < hist[512] # blue = 0x00
536
+
537
+ def test_sources_with_mixed_support_for_meta_tiles(self, mock_file_cache, source_base, source_overlay, tile_locker):
538
+ source_base.supports_meta_tiles = False
539
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
540
+ with pytest.raises(ValueError):
541
+ TileManager(grid, file_cache,
542
+ [source_base, source_overlay], 'png',
543
+ meta_size=[2, 2], meta_buffer=0,
544
+ locker=tile_locker)
545
+
546
+ def test_sources_with_no_support_for_meta_tiles(self, mock_file_cache, source_base, source_overlay, tile_locker):
547
+ source_base.supports_meta_tiles = False
548
+ source_overlay.supports_meta_tiles = False
549
+
550
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
551
+ tile_mgr = TileManager(grid, mock_file_cache,
552
+ [source_base, source_overlay], 'png',
553
+ meta_size=[2, 2], meta_buffer=0,
554
+ locker=tile_locker)
555
+
556
+ assert tile_mgr.meta_grid is None
557
+
558
+
559
+ class TestTileManagerBulkMetaTiles(object):
560
+ @pytest.fixture
561
+ def source_base(self):
562
+ src = SolidColorMockSource(color='#ff0000')
563
+ src.supports_meta_tiles = False
564
+ return src
565
+
566
+ @pytest.fixture
567
+ def source_overlay(self):
568
+ src = MockSource()
569
+ src.supports_meta_tiles = False
570
+ return src
571
+
572
+ @pytest.fixture
573
+ def tile_mgr(self, mock_file_cache, source_base, source_overlay, tile_locker):
574
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
575
+ return TileManager(grid, mock_file_cache,
576
+ [source_base, source_overlay], 'png',
577
+ meta_size=[2, 2], meta_buffer=0,
578
+ locker=tile_locker,
579
+ bulk_meta_tiles=True,
580
+ )
581
+
582
+ def test_bulk_get(self, tile_mgr, mock_file_cache, source_base, source_overlay):
583
+ tiles = tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
584
+ assert len(tiles) == 2*2
585
+ assert mock_file_cache.stored_tiles == set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)])
586
+ for requested in [source_base.requested, source_overlay.requested]:
587
+ assert set(requested) == set([
588
+ ((-180.0, 0.0, -90.0, 90.0), (256, 256), SRS(4326)),
589
+ ((-90.0, 0.0, 0.0, 90.0), (256, 256), SRS(4326)),
590
+ ((-180.0, -90.0, -90.0, 0.0), (256, 256), SRS(4326)),
591
+ ((-90.0, -90.0, 0.0, 0.0), (256, 256), SRS(4326)),
592
+ ])
593
+
594
+ def test_bulk_get_error(self, tile_mgr, source_base):
595
+ tile_mgr.sources = [source_base, ErrorSource()]
596
+ try:
597
+ tile_mgr.creator().create_tiles([Tile((0, 0, 2))])
598
+ except Exception as ex:
599
+ assert ex.args[0] == "source error"
600
+
601
+ def test_bulk_get_multiple_meta_tiles(self, tile_mgr, mock_file_cache):
602
+ tiles = tile_mgr.creator().create_tiles([Tile((1, 0, 2)), Tile((2, 0, 2))])
603
+ assert len(tiles) == 2*2*2
604
+ assert mock_file_cache.stored_tiles, set([
605
+ (0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
606
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2),
607
+ ])
608
+
609
+
610
+ class TestTileManagerBulkMetaTilesConcurrent(TestTileManagerBulkMetaTiles):
611
+ @pytest.fixture
612
+ def tile_mgr(self, mock_file_cache, source_base, source_overlay, tile_locker):
613
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
614
+ return TileManager(
615
+ grid, mock_file_cache,
616
+ [source_base, source_overlay], 'png',
617
+ meta_size=[2, 2], meta_buffer=0,
618
+ locker=tile_locker,
619
+ bulk_meta_tiles=True,
620
+ concurrent_tile_creators=2,
621
+ )
622
+
623
+
624
+ class ErrorSource(MapLayer):
625
+ def __init__(self, *args):
626
+ MapLayer.__init__(self, *args)
627
+ self.requested = []
628
+
629
+ def get_map(self, query):
630
+ self.requested.append((query.bbox, query.size, query.srs))
631
+ raise Exception("source error")
632
+
633
+
634
+ default_image_opts = ImageOptions(resampling='bicubic')
635
+
636
+
637
+ class TestCacheMapLayer(object):
638
+ @pytest.fixture
639
+ def layer(self, mock_file_cache, mock_wms_client, tile_locker):
640
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
641
+ source = WMSSource(mock_wms_client)
642
+ image_opts = ImageOptions(resampling='nearest')
643
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
644
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
645
+ locker=tile_locker)
646
+ return CacheMapLayer(tile_mgr, image_opts=default_image_opts)
647
+
648
+ def test_get_map_small(self, layer, mock_file_cache):
649
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
650
+ assert mock_file_cache.stored_tiles == set([(0, 0, 1), (1, 0, 1)])
651
+ assert result.size == (300, 150)
652
+
653
+ def test_get_map_large(self, layer, mock_file_cache):
654
+ # gets next resolution layer
655
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (600, 300), SRS(4326), 'png'))
656
+ assert mock_file_cache.stored_tiles == \
657
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
658
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
659
+ assert result.size == (600, 300)
660
+
661
+ def test_transformed(self, layer, mock_file_cache):
662
+ result = layer.get_map(MapQuery(
663
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
664
+ SRS(900913), 'png'))
665
+ assert mock_file_cache.stored_tiles == \
666
+ set([(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2),
667
+ (2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)])
668
+ assert result.size == (500, 500)
669
+
670
+ def test_single_tile_match(self, layer, mock_file_cache):
671
+ result = layer.get_map(MapQuery(
672
+ (0.001, 0, 90, 90), (256, 256), SRS(4326), 'png', tiled_only=True))
673
+ assert mock_file_cache.stored_tiles == \
674
+ set([(3, 0, 2), (2, 0, 2), (3, 1, 2), (2, 1, 2)])
675
+ assert result.size == (256, 256)
676
+
677
+ def test_single_tile_no_match(self, layer):
678
+ with pytest.raises(MapBBOXError):
679
+ layer.get_map(
680
+ MapQuery((0.1, 0, 90, 90), (256, 256),
681
+ SRS(4326), 'png', tiled_only=True)
682
+ )
683
+
684
+ def test_get_map_with_res_range(self, mock_file_cache, mock_wms_client, tile_locker):
685
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
686
+ res_range = resolution_range(1000, 10)
687
+ source = WMSSource(mock_wms_client, res_range=res_range)
688
+ image_opts = ImageOptions(resampling='nearest')
689
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
690
+ meta_size=[2, 2], meta_buffer=0, image_opts=image_opts,
691
+ locker=tile_locker)
692
+ layer = CacheMapLayer(tile_mgr, image_opts=default_image_opts)
693
+
694
+ with pytest.raises(BlankImage):
695
+ result = layer.get_map(MapQuery(
696
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
697
+ SRS(900913), 'png'))
698
+ assert mock_file_cache.stored_tiles == set()
699
+
700
+ result = layer.get_map(MapQuery(
701
+ (0, 0, 10000, 10000), (50, 50),
702
+ SRS(900913), 'png'))
703
+ assert mock_file_cache.stored_tiles == \
704
+ set([(512, 257, 10), (513, 256, 10), (512, 256, 10), (513, 257, 10)])
705
+ assert result.size == (50, 50)
706
+
707
+
708
+ class TestCacheMapLayerWithExtent(object):
709
+ @pytest.fixture
710
+ def source(self, mock_wms_client):
711
+ return WMSSource(mock_wms_client)
712
+
713
+ @pytest.fixture
714
+ def layer(self, mock_file_cache, source, tile_locker):
715
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
716
+ image_opts = ImageOptions(resampling='nearest', format='png')
717
+ tile_mgr = TileManager(grid, mock_file_cache, [source], 'png',
718
+ meta_size=[1, 1], meta_buffer=0, image_opts=image_opts,
719
+ locker=tile_locker)
720
+ layer = CacheMapLayer(tile_mgr, image_opts=default_image_opts)
721
+ layer.extent = BBOXCoverage([0, 0, 90, 45], SRS(4326)).extent
722
+ return layer
723
+
724
+ def test_get_outside_extent(self, layer):
725
+ with pytest.raises(BlankImage):
726
+ layer.get_map(MapQuery((-180, -90, 0, 0), (300, 150), SRS(4326), 'png'))
727
+
728
+ def test_get_map_small(self, layer, mock_file_cache, mock_wms_client):
729
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
730
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
731
+ # source requests one tile (no meta-tiling configured)
732
+ assert mock_wms_client.requested == [((0.0, -90.0, 180.0, 90.0), (256, 256), SRS('EPSG:4326'))]
733
+ assert result.size == (300, 150)
734
+
735
+ def test_get_map_small_with_source_extent(self, source, layer, mock_file_cache, mock_wms_client):
736
+ source.extent = BBOXCoverage([0, 0, 90, 45], SRS(4326)).extent
737
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
738
+ assert mock_file_cache.stored_tiles == set([(1, 0, 1)])
739
+ # source requests one tile (no meta-tiling configured) limited to source.extent
740
+ assert mock_wms_client.requested == [((0, 0, 90, 45), (128, 64), (SRS(4326)))]
741
+ assert result.size == (300, 150)
742
+
743
+
744
+ class TestDirectMapLayer(object):
745
+ @pytest.fixture
746
+ def layer(self, mock_wms_client):
747
+ source = WMSSource(mock_wms_client)
748
+ return DirectMapLayer(source, GLOBAL_GEOGRAPHIC_EXTENT)
749
+
750
+ def test_get_map(self, layer, mock_wms_client):
751
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
752
+ assert mock_wms_client.requested == [((-180, -90, 180, 90), (300, 150), SRS(4326))]
753
+ assert result.size == (300, 150)
754
+
755
+ def test_get_map_mercator(self, layer, mock_wms_client):
756
+ result = layer.get_map(MapQuery(
757
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
758
+ SRS(900913), 'png'))
759
+ assert mock_wms_client.requested == \
760
+ [((-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
761
+ SRS(900913))]
762
+ assert result.size == (500, 500)
763
+
764
+
765
+ class TestDirectMapLayerWithSupportedSRS(object):
766
+ @pytest.fixture
767
+ def layer(self, mock_wms_client):
768
+ source = WMSSource(mock_wms_client)
769
+ return DirectMapLayer(source, GLOBAL_GEOGRAPHIC_EXTENT)
770
+
771
+ def test_get_map(self, layer, mock_wms_client):
772
+ result = layer.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326), 'png'))
773
+ assert mock_wms_client.requested == [((-180, -90, 180, 90), (300, 150), SRS(4326))]
774
+ assert result.size == (300, 150)
775
+
776
+ def test_get_map_mercator(self, layer, mock_wms_client):
777
+ result = layer.get_map(MapQuery(
778
+ (-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
779
+ SRS(900913), 'png'))
780
+ assert mock_wms_client.requested == \
781
+ [((-20037508.34, -20037508.34, 20037508.34, 20037508.34), (500, 500),
782
+ SRS(900913))]
783
+ assert result.size == (500, 500)
784
+
785
+
786
+ class MockHTTPClient(object):
787
+ def __init__(self):
788
+ self.requested = []
789
+
790
+ def open(self, url, data=None):
791
+ self.requested.append(url)
792
+ w = int(re.search(r'width=(\d+)', url, re.IGNORECASE).group(1))
793
+ h = int(re.search(r'height=(\d+)', url, re.IGNORECASE).group(1))
794
+ format = re.search(r'format=image(/|%2F)(\w+)', url, re.IGNORECASE).group(2)
795
+ transparent = re.search(r'transparent=(\w+)', url, re.IGNORECASE)
796
+ transparent = True if transparent and transparent.group(1).lower() == 'true' else False
797
+ result = BytesIO()
798
+ create_debug_img((int(w), int(h)), transparent).save(result, format=format)
799
+ result.seek(0)
800
+ result.headers = {'Content-type': 'image/'+format}
801
+ return result
802
+
803
+
804
+ @pytest.fixture
805
+ def mock_http_client():
806
+ return MockHTTPClient()
807
+
808
+
809
+ class TestWMSSourceTransform(object):
810
+ @pytest.fixture
811
+ def source(self, mock_http_client):
812
+ req_template = WMS111MapRequest(url='http://localhost/service?', param={
813
+ 'format': 'image/png', 'layers': 'foo'
814
+ })
815
+ client = WMSClient(req_template, http_client=mock_http_client)
816
+ return WMSSource(client, supported_srs=SupportedSRS([SRS(4326)]),
817
+ image_opts=ImageOptions(resampling='bilinear'))
818
+
819
+ def test_get_map(self, source, mock_http_client):
820
+ source.get_map(MapQuery((-180, -90, 180, 90), (300, 150), SRS(4326)))
821
+ assert query_eq(mock_http_client.requested[0], "http://localhost/service?"
822
+ "layers=foo&width=300&version=1.1.1&bbox=-180,-90,180,90&service=WMS"
823
+ "&format=image%2Fpng&styles=&srs=EPSG%3A4326&request=GetMap&height=150")
824
+
825
+ def test_get_map_transformed(self, source, mock_http_client):
826
+ source.get_map(MapQuery(
827
+ (556597, 4865942, 1669792, 7361866), (300, 150), SRS(900913)))
828
+ assert wms_query_eq(mock_http_client.requested[0], "http://localhost/service?"
829
+ "layers=foo&width=300&version=1.1.1"
830
+ "&bbox=4.99999592195,39.9999980766,14.999996749,54.9999994175&service=WMS"
831
+ "&format=image%2Fpng&styles=&srs=EPSG%3A4326&request=GetMap&height=450")
832
+
833
+
834
+ class TestWMSSourceWithClient(object):
835
+
836
+ @pytest.fixture
837
+ def req_template(self):
838
+ return WMS111MapRequest(
839
+ url='http://%s:%d/service?' % TEST_SERVER_ADDRESS,
840
+ param={'format': 'image/png', 'layers': 'foo'},
841
+ )
842
+
843
+ @pytest.fixture
844
+ def client(self, req_template):
845
+ return WMSClient(req_template)
846
+
847
+ @pytest.fixture
848
+ def source(self, client):
849
+ return WMSSource(client)
850
+
851
+ def test_get_map(self, source):
852
+ with tmp_image((512, 512)) as img:
853
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
854
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles='
855
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512'},
856
+ {'body': img.read(), 'headers': {'content-type': 'image/png'}})
857
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
858
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
859
+ result = source.get_map(q)
860
+ assert isinstance(result, ImageSource)
861
+ assert result.size == (512, 512)
862
+ assert is_png(result.as_buffer(seekable=True))
863
+ assert result.as_image().size == (512, 512)
864
+
865
+ def test_get_map_non_image_content_type(self, source):
866
+ with tmp_image((512, 512)) as img:
867
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
868
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles='
869
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512'},
870
+ {'body': img.read(), 'headers': {'content-type': 'text/plain'}})
871
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
872
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
873
+ try:
874
+ source.get_map(q)
875
+ except SourceError as e:
876
+ assert 'no image returned' in e.args[0]
877
+ else:
878
+ assert False, 'no SourceError raised'
879
+
880
+ def test_basic_auth(self, req_template, client, source):
881
+ http_client = HTTPClient(req_template.url, username='foo', password='bar@')
882
+ client.http_client = http_client
883
+
884
+ def assert_auth(req_handler):
885
+ assert 'Authorization' in req_handler.headers
886
+ auth_data = req_handler.headers['Authorization'].split()[1]
887
+ auth_data = base64.b64decode(auth_data.encode('utf-8')).decode('utf-8')
888
+ assert auth_data == 'foo:bar@'
889
+ return True
890
+ expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
891
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
892
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES=',
893
+ 'require_basic_auth': True,
894
+ 'req_assert_function': assert_auth},
895
+ {'body': b'no image', 'headers': {'content-type': 'image/png'}})
896
+ with mock_httpd(TEST_SERVER_ADDRESS, [expected_req]):
897
+ q = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
898
+ source.get_map(q)
899
+
900
+ def test_http_error_handler(self, client):
901
+ error_handler = HTTPSourceErrorHandler()
902
+ error_handler.add_handler(500, (255, 0, 0), cacheable=True)
903
+ error_handler.add_handler(400, (0, 0, 0), cacheable=False)
904
+ source = WMSSource(client, error_handler=error_handler)
905
+ expected_req = [
906
+ (
907
+ {
908
+ 'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
909
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
910
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES='
911
+ },
912
+ {
913
+ 'body': b'error',
914
+ 'status': 500,
915
+ 'headers': {'content-type': 'text/plain'},
916
+ },
917
+ ),
918
+ (
919
+ {
920
+ 'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
921
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
922
+ '&VERSION=1.1.1&BBOX=0.0,10.0,10.0,20.0&WIDTH=512&STYLES='
923
+ },
924
+ {
925
+ 'body': b'error',
926
+ 'status': 400,
927
+ 'headers': {'content-type': 'text/plain'},
928
+ },
929
+ ),
930
+ ]
931
+ with mock_httpd(TEST_SERVER_ADDRESS, expected_req):
932
+ query = MapQuery((0.0, 10.0, 10.0, 20.0), (512, 512), SRS(4326))
933
+ resp = source.get_map(query)
934
+ assert resp.cacheable
935
+ assert resp.as_image().getcolors() == [((512 * 512), (255, 0, 0))]
936
+
937
+ resp = source.get_map(query)
938
+ assert not resp.cacheable
939
+ assert resp.as_image().getcolors() == [((512 * 512), (0, 0, 0))]
940
+
941
+
942
+ TESTSERVER_URL = 'http://%s:%d' % TEST_SERVER_ADDRESS
943
+
944
+
945
+ class TestWMSSource(object):
946
+
947
+ @pytest.fixture
948
+ def source(self, mock_http_client):
949
+ req = WMS111MapRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers': 'foo'})
950
+ wms = WMSClient(req, http_client=mock_http_client)
951
+ return WMSSource(wms, supported_srs=SupportedSRS([SRS(4326)]),
952
+ image_opts=ImageOptions(resampling='bilinear'))
953
+
954
+ def test_request(self, source, mock_http_client):
955
+ req = MapQuery((-180.0, -90.0, 180.0, 90.0), (512, 256), SRS(4326), 'png')
956
+ source.get_map(req)
957
+ assert len(mock_http_client.requested) == 1
958
+ assert_query_eq(mock_http_client.requested[0],
959
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
960
+ '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326'
961
+ '&VERSION=1.1.1&BBOX=-180.0,-90.0,180.0,90.0&WIDTH=512&STYLES=')
962
+
963
+ def test_transformed_request(self, source, mock_http_client):
964
+ req = MapQuery((-200000, -200000, 200000, 200000), (512, 512), SRS(900913), 'png')
965
+ resp = source.get_map(req)
966
+ assert len(mock_http_client.requested) == 1
967
+
968
+ assert wms_query_eq(mock_http_client.requested[0],
969
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
970
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
971
+ '&VERSION=1.1.1&WIDTH=512&STYLES='
972
+ '&BBOX=-1.79663056824,-1.7963362121,1.79663056824,1.7963362121')
973
+ img = resp.as_image()
974
+ assert img.mode in ('P', 'RGB')
975
+
976
+ def test_transformed_request_transparent(self, mock_http_client):
977
+ req = WMS111MapRequest(url=TESTSERVER_URL + '/service?map=foo',
978
+ param={'layers': 'foo', 'transparent': 'true'})
979
+ wms = WMSClient(req, http_client=mock_http_client)
980
+ source = WMSSource(wms, supported_srs=SupportedSRS([SRS(4326)]),
981
+ image_opts=ImageOptions(resampling='bilinear'))
982
+
983
+ req = MapQuery((-200000, -200000, 200000, 200000), (512, 512), SRS(900913), 'png')
984
+ resp = source.get_map(req)
985
+ assert len(mock_http_client.requested) == 1
986
+
987
+ assert wms_query_eq(mock_http_client.requested[0],
988
+ TESTSERVER_URL+'/service?map=foo&LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng'
989
+ '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326'
990
+ '&VERSION=1.1.1&WIDTH=512&STYLES=&transparent=true'
991
+ '&BBOX=-1.79663056824,-1.7963362121,1.79663056824,1.7963362121')
992
+ img = resp.as_image()
993
+ assert img.mode in ('P', 'RGBA')
994
+ img = img.convert('RGBA')
995
+ assert img.getpixel((5, 5))[3] == 0
996
+
997
+
998
+ class MockLayer(object):
999
+ def __init__(self):
1000
+ self.requested = []
1001
+
1002
+ def get_map(self, query):
1003
+ self.requested.append((query.bbox, query.size, query.srs))
1004
+
1005
+
1006
+ @pytest.mark.parametrize('case,map_query,low_requested', [
1007
+ ['low', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(3857)), True],
1008
+ ['high', MapQuery((0, 0, 100, 100), (100, 100), SRS(3857)), False],
1009
+ ['match', MapQuery((0, 0, 10, 10), (100, 100), SRS(3857)), False],
1010
+ ['low_transform', MapQuery((0, 0, 0.1, 0.1), (100, 100), SRS(4326)), True],
1011
+ ['high_transform', MapQuery((0, 0, 0.005, 0.005), (100, 100), SRS(4326)), False],
1012
+ ])
1013
+ def test_resolution_conditional_layers(case, map_query, low_requested):
1014
+ low = MockLayer()
1015
+ high = MockLayer()
1016
+ layer = ResolutionConditional(low, high, 10, SRS(3857),
1017
+ GLOBAL_GEOGRAPHIC_EXTENT)
1018
+
1019
+ layer.get_map(map_query)
1020
+ assert bool(low.requested) == low_requested
1021
+ assert bool(high.requested) != low_requested
1022
+
1023
+
1024
+ def test_srs_conditional_layers():
1025
+ l4326 = MockLayer()
1026
+ l3857 = MockLayer()
1027
+ l25832 = MockLayer()
1028
+ preferred = PreferredSrcSRS()
1029
+ preferred.add(SRS(31467), [SRS(25832), SRS(3857)])
1030
+ layer = SRSConditional([
1031
+ (l4326, SRS(4326)),
1032
+ (l3857, SRS(3857)),
1033
+ (l25832, SRS(25832)),
1034
+ ], GLOBAL_GEOGRAPHIC_EXTENT, preferred_srs=preferred,
1035
+ )
1036
+
1037
+ # srs match
1038
+ assert layer._select_layer(SRS(4326)) == l4326
1039
+ assert layer._select_layer(SRS(3857)) == l3857
1040
+ assert layer._select_layer(SRS(25832)) == l25832
1041
+ # type match (projected)
1042
+ assert layer._select_layer(SRS(31466)) == l3857
1043
+ assert layer._select_layer(SRS(32633)) == l3857
1044
+ assert layer._select_layer(SRS(4258)) == l4326
1045
+ # preferred
1046
+ assert layer._select_layer(SRS(31467)) == l25832
1047
+
1048
+
1049
+ @pytest.mark.parametrize('case,map_query,is_direct,is_l3857,is_l4326', [
1050
+ ['high_3857', MapQuery((0, 0, 100, 100), (100, 100), SRS(900913)), True, False, False],
1051
+ ['high_4326', MapQuery((0, 0, 0.0001, 0.0001), (100, 100), SRS(4326)), True, False, False],
1052
+ ['low_4326', MapQuery((0, 0, 10, 10), (100, 100), SRS(4326)), False, False, True],
1053
+ ['low_3857', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(31467)), False, True, False],
1054
+ ['low_projected', MapQuery((0, 0, 10000, 10000), (100, 100), SRS(31467)), False, True, False],
1055
+ ])
1056
+ def test_neasted_conditional_layers(case, map_query, is_direct, is_l3857, is_l4326):
1057
+ direct = MockLayer()
1058
+ l3857 = MockLayer()
1059
+ l4326 = MockLayer()
1060
+ layer = ResolutionConditional(
1061
+ SRSConditional([
1062
+ (l3857, SRS('EPSG:3857')),
1063
+ (l4326, SRS('EPSG:4326'))
1064
+ ], GLOBAL_GEOGRAPHIC_EXTENT),
1065
+ direct, 10, SRS(3857), GLOBAL_GEOGRAPHIC_EXTENT
1066
+ )
1067
+ layer.get_map(map_query)
1068
+ assert bool(direct.requested) == is_direct
1069
+ assert bool(l3857.requested) == is_l3857
1070
+ assert bool(l4326.requested) == is_l4326
1071
+
1072
+
1073
+ def is_blank(tiles):
1074
+ return all([t.source is None or isinstance(t.source, BlankImageSource) for t in tiles.tiles])
1075
+
1076
+
1077
+ class TestTileManagerEmptySources(object):
1078
+ def test_upscale(self, mock_file_cache, tile_locker):
1079
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90])
1080
+ image_opts = ImageOptions(format='image/png')
1081
+ tm = TileManager(
1082
+ grid, mock_file_cache, [], 'png',
1083
+ locker=tile_locker,
1084
+ image_opts=image_opts,
1085
+ )
1086
+
1087
+ assert is_blank(tm.load_tile_coords([(0, 0, 0)]))
1088
+ assert is_blank(tm.load_tile_coords([(3, 2, 5)]))
1089
+
1090
+ assert mock_file_cache.stored_tiles == set()
1091
+ assert mock_file_cache.loaded_tiles == counting_set([(0, 0, 0), (3, 2, 5)])
1092
+
1093
+
1094
+ class TestTileManagerRescaleTiles(object):
1095
+ @pytest.fixture
1096
+ def file_cache(self, tmpdir):
1097
+ return RecordFileCache(tmpdir.strpath, 'png')
1098
+
1099
+ @pytest.mark.parametrize("name,rescale_tiles,tiles,store,expected_load,output", [
1100
+ (
1101
+ "no-scale: missing tile, no rescale",
1102
+ 0, [(0, 0, 0)], [], [(0, 0, 0)], "blank",
1103
+ ),
1104
+ (
1105
+ "downscale: missing tile, 1 level rescale with None tiles",
1106
+ 1, [(0, 0, 0)], [], [(0, 0, 0), None, None, (0, 0, 1), (1, 0, 1)], "blank",
1107
+ ),
1108
+ (
1109
+ "downscale: missing tile, 1 level rescale",
1110
+ 1, [(1, 2, 4)], [], [(1, 2, 4), (2, 4, 5), (3, 4, 5), (2, 5, 5), (3, 5, 5)], "blank",
1111
+ ),
1112
+ (
1113
+ "downscale: missing tile, 2 level rescale",
1114
+ 2, [(1, 2, 4)], [], [
1115
+ (1, 2, 4),
1116
+ (2, 4, 5), (3, 4, 5), (2, 5, 5), (3, 5, 5),
1117
+ (4, 8, 6), (5, 8, 6), (6, 8, 6), (7, 8, 6),
1118
+ (4, 9, 6), (5, 9, 6), (6, 9, 6), (7, 9, 6),
1119
+ (4, 10, 6), (5, 10, 6), (6, 10, 6), (7, 10, 6),
1120
+ (4, 11, 6), (5, 11, 6), (6, 11, 6), (7, 11, 6),
1121
+ ], "blank",
1122
+ ),
1123
+ (
1124
+ "downscale: exact tile cached",
1125
+ 1, [(0, 0, 1)], [(0, 0, 1)], [(0, 0, 1)], "full",
1126
+ ),
1127
+ (
1128
+ "downscale: next level tiles partially cached",
1129
+ 1, [(0, 0, 1)], [(0, 0, 2)], [(0, 0, 1), (0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)], "partial",
1130
+ ),
1131
+ (
1132
+ "downscale: next level tiles fully cached",
1133
+ 1, [(0, 0, 1)], [(0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)],
1134
+ [(0, 0, 1), (0, 0, 2), (0, 1, 2), (1, 0, 2), (1, 1, 2)], "full",
1135
+ ),
1136
+ (
1137
+ "upscale: missing tile level 1 rescale",
1138
+ -1, [(16, 8, 5)], [], [(16, 8, 5), (8, 4, 4)], "blank",
1139
+ ),
1140
+ (
1141
+ "upscale: missing tile level 1 rescale, odd coords",
1142
+ -1, [(15, 7, 5)], [], [(15, 7, 5), (7, 3, 4)], "blank",
1143
+ ),
1144
+ (
1145
+ "upscale: missing tile level 1 rescale, multiple tiles",
1146
+ -1, [(15, 6, 5), (16, 6, 5), (15, 7, 5), (16, 7, 5)], [],
1147
+ [(15, 6, 5), (16, 6, 5), (15, 7, 5), (16, 7, 5), (7, 3, 4), (8, 3, 4)], "blank",
1148
+ ),
1149
+ (
1150
+ "upscale: tile in level 2",
1151
+ -3, [(15, 7, 5)], [(3, 1, 3)], [(15, 7, 5), (7, 3, 4), (3, 1, 3)], "full",
1152
+ ),
1153
+ (
1154
+ "upscale: missing tile level 99 rescale",
1155
+ -99, [(16, 8, 5)], [], [(16, 8, 5), (8, 4, 4), (4, 2, 3), (2, 1, 2), (1, 0, 1), (0, 0, 0)], "blank",
1156
+ ),
1157
+ (
1158
+ "upscale: unregular grid, partial match",
1159
+ -2, [(201, 101, 10)], [(78, 40, 8), (79, 40, 8), (79, 39, 8)], [
1160
+ (201, 101, 10), (100, 50, 9),
1161
+ # check all four tiles above 100/50/9
1162
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1163
+ ], "partial",
1164
+ ),
1165
+ (
1166
+ "upscale: unregular grid, multiple tiles, partial match",
1167
+ -2, [(200, 100, 10), (201, 100, 10), (200, 101, 10), (201, 101, 10)],
1168
+ [(78, 40, 8), (79, 40, 8), (79, 39, 8)], [
1169
+ (200, 100, 10), (201, 100, 10), (200, 101, 10), (201, 101, 10),
1170
+ (100, 50, 9),
1171
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1172
+ ], "partial",
1173
+ ),
1174
+ (
1175
+ "upscale: unregular grid",
1176
+ -3, [(200, 100, 10)], [], [
1177
+ (200, 100, 10), (100, 50, 9),
1178
+ # check all four tiles above 100/50/9
1179
+ (78, 40, 8), (79, 40, 8), (78, 39, 8), (79, 39, 8),
1180
+ # check tiles above level 8 for each tile individually.
1181
+ # this is due to the recursive nature of our rescaling algorithm
1182
+ (49, 24, 7),
1183
+ (50, 24, 7),
1184
+ (49, 25, 7),
1185
+ (50, 25, 7),
1186
+ (49, 26, 7),
1187
+ (50, 26, 7),
1188
+ ],
1189
+ "blank",
1190
+ ),
1191
+ ])
1192
+ def test_scaled_tiles(self, name, file_cache, tile_locker, rescale_tiles, tiles, store, expected_load, output):
1193
+ res = [
1194
+ 1.40625, # 0
1195
+ 0.703125, # 1
1196
+ 0.3515625, # 2
1197
+ 0.17578125, # 3
1198
+ 0.087890625, # 4
1199
+ 0.0439453125, # 5
1200
+ 0.02197265625, # 6
1201
+ 0.010986328125, # 7
1202
+ 0.007, # 8 additional resolution to test unregular grids
1203
+ 0.0054931640625, # 9
1204
+ 0.00274658203125, # 10
1205
+ ]
1206
+ grid = TileGrid(SRS(4326), origin='sw', bbox=[-180, -90, 180, 90], res=res)
1207
+ image_opts = ImageOptions(format='image/png', resampling='nearest')
1208
+ tm = TileManager(
1209
+ grid, file_cache, [], 'png',
1210
+ locker=tile_locker,
1211
+ image_opts=image_opts,
1212
+ rescale_tiles=rescale_tiles,
1213
+ )
1214
+
1215
+ if store:
1216
+ colors = set()
1217
+ if output == "partial":
1218
+ colors.add((255, 255, 255))
1219
+ for i, t in enumerate(store):
1220
+ color = (150+i*35, 5+i*35, 5+i*35)
1221
+ colors.add(color)
1222
+ tile = Tile(t, ImageSource(create_tmp_image_buf((256, 256), color=color)))
1223
+ file_cache.store_tile(tile)
1224
+
1225
+ loaded_tiles = tm.load_tile_coords(tiles)
1226
+ assert not is_blank(loaded_tiles)
1227
+ assert len(loaded_tiles) == len(tiles)
1228
+ got_colors = set()
1229
+ for t in loaded_tiles:
1230
+ got_colors.update([c for _, c in t.source.as_image().getcolors()])
1231
+ assert got_colors == colors
1232
+ else:
1233
+ loaded_tiles = tm.load_tile_coords(tiles)
1234
+ assert is_blank(loaded_tiles) == (output == "blank")
1235
+ assert len(loaded_tiles.tiles) == len(tiles)
1236
+
1237
+ assert file_cache.stored_tiles == set(store)
1238
+ assert file_cache.loaded_tiles == counting_set(expected_load)
1239
+ assert file_cache.is_cached_call_count == 0
1240
+
1241
+
1242
+ class TileCacheTestBase(object):
1243
+ cache = None # set by subclasses
1244
+
1245
+ def setup_method(self):
1246
+ self.cache_dir = tempfile.mkdtemp()
1247
+
1248
+ def teardown_method(self):
1249
+ if hasattr(self.cache, 'cleanup'):
1250
+ self.cache.cleanup()
1251
+ if hasattr(self, 'cache_dir') and os.path.exists(self.cache_dir):
1252
+ shutil.rmtree(self.cache_dir)
1253
+
1254
+
1255
+ class TestTileManagerCacheBboxCoverage(TileCacheTestBase):
1256
+ def setup_method(self):
1257
+ TileCacheTestBase.setup_method(self)
1258
+ self.cache = RecordFileCache(self.cache_dir, 'png', coverage=coverage([-50, -50, 50, 50], SRS(4326)))
1259
+
1260
+ def test_load_tiles_in_coverage(self, tile_locker):
1261
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
1262
+ image_opts = ImageOptions(format='image/png')
1263
+ tm = TileManager(
1264
+ grid, self.cache, [], 'png',
1265
+ locker=tile_locker,
1266
+ image_opts=image_opts,
1267
+ )
1268
+
1269
+ coords = [(2, 1, 2), (36, 7, 6), (18082, 6028, 15)]
1270
+ collection = tm.load_tile_coords(coords)
1271
+
1272
+ # Check that tiles inside of coverage loaded
1273
+ assert all(coord in collection for coord in coords)
1274
+ assert all(t.coord is not None for t in collection)
1275
+
1276
+ all(t.coord in self.cache.stored_tiles for t in collection)
1277
+ assert self.cache.loaded_tiles == counting_set(coords)
1278
+ assert self.cache.is_cached_call_count == 0
1279
+
1280
+ def test_empty_tiles_outside_coverage(self, tile_locker):
1281
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
1282
+ image_opts = ImageOptions(format='image/png')
1283
+ tm = TileManager(
1284
+ grid, self.cache, [], 'png',
1285
+ locker=tile_locker,
1286
+ image_opts=image_opts,
1287
+ )
1288
+
1289
+ coords = [(0, 1, 1), (33, 5, 5), (19449, 3638, 15)]
1290
+ collection = tm.load_tile_coords(coords)
1291
+
1292
+ # Check that tiles did not load
1293
+ assert collection.blank
1294
+ assert all(t.coord is None for t in collection)
1295
+
1296
+ assert self.cache.stored_tiles == set([])
1297
+ assert self.cache.loaded_tiles == counting_set([None for _ in coords])
1298
+
1299
+
1300
+ # From: https://www.kaggle.com/datasets/chapagain/country-state-geo-location
1301
+ boundary_geojson = (
1302
+ b"""
1303
+ {"type":"FeatureCollection","features":[
1304
+ {"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-155.68817,18.91619],[-155.93665,19.05939],[-155.90806,19.33888],[-156.07347,19.70294],[-156.02368,19.81422],[-155.85008,19.97729],[-155.91907,20.17395],[-155.86108,20.26721],[-155.78505,20.2487],[-155.40214,20.07975],[-155.22452,19.99302],[-155.06226,19.8591],[-154.80741,19.50871],[-154.83147,19.45328],[-155.22217,19.23972],[-155.54211,19.08348]]],[[[-156.07926,20.64397],[-156.41445,20.57241],[-156.58673,20.783],[-156.70167,20.8643],[-156.71055,20.92676],[-156.61258,21.01249],[-156.25711,20.91745],[-155.99566,20.76404],[-156.07926,20.64397]]],[[[-156.75824,21.17684],[-156.78933,21.06873],[-157.32521,21.09777],[-157.25027,21.21958],[-156.75824,21.17684]]],[[[-157.65283,21.32217],[-157.70703,21.26442],[-157.7786,21.27729],[-158.12667,21.31244],[-158.2538,21.53919],[-158.29265,21.57912],[-158.0252,21.71696],[-157.94161,21.65272],[-157.65283,21.32217]]],[[[-159.34512,21.982],[-159.46372,21.88299],[-159.80051,22.06533],[-159.74877,22.1382],[-159.5962,22.23618],[-159.36569,22.21494],[-159.34512,21.982]]],[[[-94.81758,49.38905],[-94.64,48.84],[-94.32914,48.67074],[-93.63087,48.60926],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-89.272917,48.019808],[-88.378114,48.302918],[-87.439793,47.94],[-86.461991,47.553338],[-85.652363,47.220219],[-84.87608,46.900083],[-84.779238,46.637102],[-84.543749,46.538684],[-84.6049,46.4396],[-84.3367,46.40877],[-84.14212,46.512226],[-84.091851,46.275419],[-83.890765,46.116927],[-83.616131,46.116927],[-83.469551,45.994686],[-83.592851,45.816894],[-82.550925,45.347517],[-82.337763,44.44],[-82.137642,43.571088],[-82.43,42.98],[-82.9,42.43],[-83.12,42.08],[-83.142,41.975681],[-83.02981,41.832796],[-82.690089,41.675105],[-82.439278,41.675105],[-81.277747,42.209026],[-80.247448,42.3662],[-78.939362,42.863611],[-78.92,42.965],[-79.01,43.27],[-79.171674,43.466339],[-78.72028,43.625089],[-77.737885,43.629056],[-76.820034,43.628784],[-76.5,44.018459],[-76.375,44.09631],[-75.31821,44.81645],[-74.867,45.00048],[-73.34783,45.00738],[-71.50506,45.0082],[-71.405,45.255],[-71.08482,45.30524],[-70.66,45.46],[-70.305,45.915],[-69.99997,46.69307],[-69.237216,47.447781],[-68.905,47.185],[-68.23444,47.35486],[-67.79046,47.06636],[-67.79134,45.70281],[-67.13741,45.13753],[-66.96466,44.8097],[-68.03252,44.3252],[-69.06,43.98],[-70.11617,43.68405],[-70.645476,43.090238],[-70.81489,42.8653],[-70.825,42.335],[-70.495,41.805],[-70.08,41.78],[-70.185,42.145],[-69.88497,41.92283],[-69.96503,41.63717],[-70.64,41.475],[-71.12039,41.49445],[-71.86,41.32],[-72.295,41.27],[-72.87643,41.22065],[-73.71,40.931102],[-72.24126,41.11948],[-71.945,40.93],[-73.345,40.63],[-73.982,40.628],[-73.952325,40.75075],[-74.25671,40.47351],[-73.96244,40.42763],[-74.17838,39.70926],[-74.90604,38.93954],[-74.98041,39.1964],[-75.20002,39.24845],[-75.52805,39.4985],[-75.32,38.96],[-75.071835,38.782032],[-75.05673,38.40412],[-75.37747,38.01551],[-75.94023,37.21689],[-76.03127,37.2566],[-75.72205,37.93705],[-76.23287,38.319215],[-76.35,39.15],[-76.542725,38.717615],[-76.32933,38.08326],[-76.989998,38.239992],[-76.30162,37.917945],[-76.25874,36.9664],[-75.9718,36.89726],[-75.86804,36.55125],[-75.72749,35.55074],[-76.36318,34.80854],[-77.397635,34.51201],[-78.05496,33.92547],[-78.55435,33.86133],[-79.06067,33.49395],[-79.20357,33.15839],[-80.301325,32.509355],[-80.86498,32.0333],[-81.33629,31.44049],[-81.49042,30.72999],[-81.31371,30.03552],[-80.98,29.18],[-80.535585,28.47213],[-80.53,28.04],[-80.056539,26.88],[-80.088015,26.205765],[-80.13156,25.816775],[-80.38103,25.20616],[-80.68,25.08],[-81.17213,25.20126],[-81.33,25.64],[-81.71,25.87],[-82.24,26.73],[-82.70515,27.49504],[-82.85526,27.88624],[-82.65,28.55],[-82.93,29.1],[-83.70959,29.93656],[-84.1,30.09],[-85.10882,29.63615],[-85.28784,29.68612],[-85.7731,30.15261],[-86.4,30.4],[-87.53036,30.27433],[-88.41782,30.3849],[-89.18049,30.31598],[-89.593831,30.159994],[-89.413735,29.89419],[-89.43,29.48864],[-89.21767,29.29108],[-89.40823,29.15961],[-89.77928,29.30714],[-90.15463,29.11743],[-90.880225,29.148535],[-91.626785,29.677],[-92.49906,29.5523],[-93.22637,29.78375],[-93.84842,29.71363],[-94.69,29.48],[-95.60026,28.73863],[-96.59404,28.30748],[-97.14,27.83],[-97.37,27.38],[-97.38,26.69],[-97.33,26.21],[-97.14,25.87],[-97.53,25.84],[-98.24,26.06],[-99.02,26.37],[-99.3,26.84],[-99.52,27.54],[-100.11,28.11],[-100.45584,28.69612],[-100.9576,29.38071],[-101.6624,29.7793],[-102.48,29.76],[-103.11,28.97],[-103.94,29.27],[-104.45697,29.57196],[-104.70575,30.12173],[-105.03737,30.64402],[-105.63159,31.08383],[-106.1429,31.39995],[-106.50759,31.75452],[-108.24,31.754854],[-108.24194,31.34222],[-109.035,31.34194],[-111.02361,31.33472],[-113.30498,32.03914],[-114.815,32.52528],[-114.72139,32.72083],[-115.99135,32.61239],[-117.12776,32.53534],[-117.295938,33.046225],[-117.944,33.621236],[-118.410602,33.740909],[-118.519895,34.027782],[-119.081,34.078],[-119.438841,34.348477],[-120.36778,34.44711],[-120.62286,34.60855],[-120.74433,35.15686],[-121.71457,36.16153],[-122.54747,37.55176],[-122.51201,37.78339],[-122.95319,38.11371],[-123.7272,38.95166],[-123.86517,39.76699],[-124.39807,40.3132],[-124.17886,41.14202],[-124.2137,41.99964],[-124.53284,42.76599],[-124.14214,43.70838],[-124.020535,44.615895],[-123.89893,45.52341],[-124.079635,46.86475],[-124.39567,47.72017],[-124.68721,48.184433],[-124.566101,48.379715],[-123.12,48.04],[-122.58736,47.096],[-122.34,47.36],[-122.5,48.18],[-122.84,49],[-120,49],[-117.03121,49],[-116.04818,49],[-113,49],[-110.05,49],[-107.05,49],[-104.04826,48.99986],[-100.65,49],[-97.22872,49.0007],[-95.15907,49],[-95.15609,49.38425],[-94.81758,49.38905]]],[[[-153.006314,57.115842],[-154.00509,56.734677],[-154.516403,56.992749],[-154.670993,57.461196],[-153.76278,57.816575],[-153.228729,57.968968],[-152.564791,57.901427],[-152.141147,57.591059],[-153.006314,57.115842]]],[[[-165.579164,59.909987],[-166.19277,59.754441],[-166.848337,59.941406],[-167.455277,60.213069],[-166.467792,60.38417],[-165.67443,60.293607],[-165.579164,59.909987]]],[[[-171.731657,63.782515],[-171.114434,63.592191],[-170.491112,63.694975],[-169.682505,63.431116],[-168.689439,63.297506],[-168.771941,63.188598],[-169.52944,62.976931],[-170.290556,63.194438],[-170.671386,63.375822],[-171.553063,63.317789],[-171.791111,63.405846],[-171.731657,63.782515]]],[[[-155.06779,71.147776],[-154.344165,70.696409],[-153.900006,70.889989],[-152.210006,70.829992],[-152.270002,70.600006],[-150.739992,70.430017],[-149.720003,70.53001],[-147.613362,70.214035],[-145.68999,70.12001],[-144.920011,69.989992],[-143.589446,70.152514],[-142.07251,69.851938],[-140.985988,69.711998],[-140.992499,66.000029],[-140.99777,60.306397],[-140.012998,60.276838],[-139.039,60.000007],[-138.34089,59.56211],[-137.4525,58.905],[-136.47972,59.46389],[-135.47583,59.78778],[-134.945,59.27056],[-134.27111,58.86111],[-133.355549,58.410285],[-132.73042,57.69289],[-131.70781,56.55212],[-130.00778,55.91583],[-129.979994,55.284998],[-130.53611,54.802753],[-131.085818,55.178906],[-131.967211,55.497776],[-132.250011,56.369996],[-133.539181,57.178887],[-134.078063,58.123068],[-135.038211,58.187715],[-136.628062,58.212209],[-137.800006,58.499995],[-139.867787,59.537762],[-140.825274,59.727517],[-142.574444,60.084447],[-143.958881,59.99918],[-145.925557,60.45861],[-147.114374,60.884656],[-148.224306,60.672989],[-148.018066,59.978329],[-148.570823,59.914173],[-149.727858,59.705658],[-150.608243,59.368211],[-151.716393,59.155821],[-151.859433,59.744984],[-151.409719,60.725803],[-150.346941,61.033588],[-150.621111,61.284425],[-151.895839,60.727198],[-152.57833,60.061657],[-154.019172,59.350279],[-153.287511,58.864728],[-154.232492,58.146374],[-155.307491,57.727795],[-156.308335,57.422774],[-156.556097,56.979985],[-158.117217,56.463608],[-158.433321,55.994154],[-159.603327,55.566686],[-160.28972,55.643581],[-161.223048,55.364735],[-162.237766,55.024187],[-163.069447,54.689737],[-164.785569,54.404173],[-164.942226,54.572225],[-163.84834,55.039431],[-162.870001,55.348043],[-161.804175,55.894986],[-160.563605,56.008055],[-160.07056,56.418055],[-158.684443,57.016675],[-158.461097,57.216921],[-157.72277,57.570001],[-157.550274,58.328326],[-157.041675,58.918885],[-158.194731,58.615802],[-158.517218,58.787781],[-159.058606,58.424186],[-159.711667,58.93139],[-159.981289,58.572549],[-160.355271,59.071123],[-161.355003,58.670838],[-161.968894,58.671665],[-162.054987,59.266925],[-161.874171,59.633621],[-162.518059,59.989724],[-163.818341,59.798056],[-164.662218,60.267484],[-165.346388,60.507496],[-165.350832,61.073895],[-166.121379,61.500019],[-165.734452,62.074997],[-164.919179,62.633076],[-164.562508,63.146378],[-163.753332,63.219449],[-163.067224,63.059459],[-162.260555,63.541936],[-161.53445,63.455817],[-160.772507,63.766108],[-160.958335,64.222799],[-161.518068,64.402788],[-160.777778,64.788604],[-161.391926,64.777235],[-162.45305,64.559445],[-162.757786,64.338605],[-163.546394,64.55916],[-164.96083,64.446945],[-166.425288,64.686672],[-166.845004,65.088896],[-168.11056,65.669997],[-166.705271,66.088318],[-164.47471,66.57666],[-163.652512,66.57666],[-163.788602,66.077207],[-161.677774,66.11612],[-162.489715,66.735565],[-163.719717,67.116395],[-164.430991,67.616338],[-165.390287,68.042772],[-166.764441,68.358877],[-166.204707,68.883031],[-164.430811,68.915535],[-163.168614,69.371115],[-162.930566,69.858062],[-161.908897,70.33333],[-160.934797,70.44769],[-159.039176,70.891642],[-158.119723,70.824721],[-156.580825,71.357764],[-155.06779,71.147776]]]]}}
1305
+ ]}
1306
+ """.strip()
1307
+ )
1308
+
1309
+
1310
+ class TestTileManagerCacheGeojsonCoverage(TileCacheTestBase):
1311
+ def setup_method(self):
1312
+ TileCacheTestBase.setup_method(self)
1313
+
1314
+ with TempFile() as tf:
1315
+ with open(tf, 'wb') as f:
1316
+ f.write(boundary_geojson)
1317
+ conf = {'datasource': tf, 'srs': 'EPSG:4326'}
1318
+ self.cache = RecordFileCache(self.cache_dir, 'png', coverage=load_coverage(conf))
1319
+
1320
+ def test_load_tiles_in_coverage(self, tile_locker):
1321
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
1322
+ image_opts = ImageOptions(format='image/png')
1323
+ tm = TileManager(
1324
+ grid, self.cache, [], 'png',
1325
+ locker=tile_locker,
1326
+ image_opts=image_opts,
1327
+ )
1328
+
1329
+ coords = [(3, 2, 4), (11, 9, 6), (17, 11, 7), (359, 325, 11), (2996, 2513, 14), (22923, 20919, 17)]
1330
+ collection = tm.load_tile_coords(coords)
1331
+
1332
+ # Check that tiles inside of coverage loaded
1333
+ assert all(coord in collection for coord in coords)
1334
+ assert all(t.coord is not None for t in collection)
1335
+
1336
+ all(t.coord in self.cache.stored_tiles for t in collection)
1337
+ assert self.cache.loaded_tiles == counting_set(coords)
1338
+
1339
+ def test_empty_tiles_outside_coverage(self, tile_locker):
1340
+ grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], origin='ul')
1341
+ image_opts = ImageOptions(format='image/png')
1342
+ tm = TileManager(
1343
+ grid, self.cache, [], 'png',
1344
+ locker=tile_locker,
1345
+ image_opts=image_opts,
1346
+ )
1347
+
1348
+ coords = [(3, 3, 4), (5, 3, 4), (8, 11, 6), (19, 11, 7), (38, 25, 8), (359, 328, 11), (22922, 20922, 17)]
1349
+ collection = tm.load_tile_coords(coords)
1350
+
1351
+ # Check that tiles did not load
1352
+ assert collection.blank
1353
+ assert all(t.coord is None for t in collection)
1354
+
1355
+ assert self.cache.stored_tiles == set([])
1356
+ assert self.cache.loaded_tiles == counting_set([None for _ in coords])