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
mapproxy/cache/tile.py ADDED
@@ -0,0 +1,736 @@
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
+ Tile caching (creation, caching and retrieval of tiles).
18
+
19
+ .. digraph:: Schematic Call Graph
20
+
21
+ ranksep = 0.1;
22
+ node [shape="box", height="0", width="0"]
23
+
24
+ cl [label="CacheMapLayer" href="<mapproxy.core.layer.CacheMapLayer>"]
25
+ tm [label="TileManager", href="<TileManager>"];
26
+ fc [label="FileCache", href="<FileCache>"];
27
+ s [label="Source", href="<mapproxy.core.source.Source>"];
28
+
29
+ {
30
+ cl -> tm [label="load_tile_coords"];
31
+ tm -> fc [label="load\\nstore\\nis_cached"];
32
+ tm -> s [label="get_map"]
33
+ }
34
+
35
+
36
+ """
37
+ from functools import partial
38
+ from contextlib import contextmanager
39
+ from mapproxy.grid import MetaGrid
40
+ from mapproxy.image import BlankImageSource
41
+ from mapproxy.image.mask import mask_image_source_from_coverage
42
+ from mapproxy.image.opts import ImageOptions
43
+ from mapproxy.image.merge import merge_images
44
+ from mapproxy.image.tile import TileSplitter, TiledImage
45
+ from mapproxy.layer import MapQuery, BlankImage
46
+ from mapproxy.source import SourceError, DummySource
47
+ from mapproxy.util import async_
48
+ from mapproxy.util.py import reraise, reraise_exception
49
+ import sys
50
+ import logging
51
+ log = logging.getLogger('mapproxy.cache.tile')
52
+
53
+
54
+ class TileManager(object):
55
+ """
56
+ Manages tiles for a single grid.
57
+ Loads tiles from the cache, creates new tiles from sources and stores them
58
+ into the cache, or removes tiles.
59
+
60
+ :param pre_store_filter: a list with filter. each filter will be called
61
+ with a tile before it will be stored to disc. the filter should
62
+ return this or a new tile object.
63
+ """
64
+
65
+ def __init__(self, grid, cache, sources, format, locker, image_opts=None, request_format=None,
66
+ meta_buffer=None, meta_size=None, minimize_meta_requests=False, identifier=None,
67
+ pre_store_filter=None, concurrent_tile_creators=1, tile_creator_class=None,
68
+ bulk_meta_tiles=False,
69
+ rescale_tiles=0,
70
+ cache_rescaled_tiles=False,
71
+ dimensions=None
72
+ ):
73
+ self.grid = grid
74
+ self.cache = cache
75
+ self.locker = locker
76
+ self.identifier = identifier
77
+ self.meta_grid = None
78
+ self.format = format
79
+ self.image_opts = image_opts
80
+ self.request_format = request_format or format
81
+ self.sources = sources
82
+ self.minimize_meta_requests = minimize_meta_requests
83
+ self._expire_timestamp = None
84
+ self._refresh_before = {}
85
+ self.pre_store_filter = pre_store_filter or []
86
+ self.concurrent_tile_creators = concurrent_tile_creators
87
+ self.tile_creator_class = tile_creator_class or TileCreator
88
+ self.dimensions = dimensions
89
+
90
+ self.rescale_tiles = rescale_tiles
91
+ self.cache_rescaled_tiles = cache_rescaled_tiles
92
+
93
+ if meta_buffer or (meta_size and not meta_size == [1, 1]):
94
+ if all(source.supports_meta_tiles for source in sources):
95
+ self.meta_grid = MetaGrid(grid, meta_size=meta_size, meta_buffer=meta_buffer)
96
+ elif any(source.supports_meta_tiles for source in sources):
97
+ raise ValueError('meta tiling configured but not supported by all sources')
98
+ elif meta_size and not meta_size == [1, 1] and bulk_meta_tiles:
99
+ # meta tiles configured but all sources are tiled
100
+ # use bulk_meta_tile mode that download tiles in parallel
101
+ self.meta_grid = MetaGrid(grid, meta_size=meta_size, meta_buffer=0)
102
+ self.tile_creator_class = partial(self.tile_creator_class, bulk_meta_tiles=True)
103
+
104
+ @contextmanager
105
+ def session(self):
106
+ """
107
+ Context manager for access to the cache. Cleans up after usage
108
+ for connection based caches.
109
+
110
+ >>> with tile_manager.session(): #doctest: +SKIP
111
+ ... tile_manager.load_tile_coords(tile_coords)
112
+
113
+ """
114
+ yield
115
+ self.cleanup()
116
+
117
+ def cleanup(self):
118
+ if hasattr(self.cache, 'cleanup'):
119
+ self.cache.cleanup()
120
+
121
+ def load_tile_coord(self, tile_coord, dimensions=None, with_metadata=False):
122
+ return self.load_tile_coords(
123
+ [tile_coord], dimensions=dimensions, with_metadata=with_metadata,
124
+ )[0]
125
+
126
+ def load_tile_coords(self, tile_coords, dimensions=None, with_metadata=False):
127
+ tiles = TileCollection(tile_coords)
128
+ rescale_till_zoom = 0
129
+ if self.rescale_tiles:
130
+ for t in tiles.tiles:
131
+ # Use zoom level from first None tile.
132
+ if t.coord is not None:
133
+ rescale_till_zoom = t.coord[2] + self.rescale_tiles
134
+ break
135
+ else:
136
+ return tiles
137
+
138
+ if rescale_till_zoom < 0:
139
+ rescale_till_zoom = 0
140
+ if rescale_till_zoom > self.grid.levels:
141
+ rescale_till_zoom = self.grid.levels
142
+
143
+ # Remove tiles that are not in the cache coverage
144
+ if self.cache.coverage:
145
+ for t in tiles.tiles:
146
+ if t.coord:
147
+ tile_bbox = self.grid.tile_bbox(t.coord)
148
+ if not self.cache.coverage.intersects(tile_bbox, self.grid.srs):
149
+ t.coord = None
150
+
151
+ tiles = self._load_tile_coords(
152
+ tiles, dimensions=dimensions, with_metadata=with_metadata,
153
+ rescale_till_zoom=rescale_till_zoom, rescaled_tiles={},
154
+ )
155
+
156
+ for t in tiles.tiles:
157
+ # Clip tiles if clipping is enabled for coverage
158
+ if t.coord and self.cache.coverage and self.cache.coverage.clip and t.source:
159
+ tile_bbox = self.grid.tile_bbox(t.coord)
160
+ coverage = self.cache.coverage
161
+
162
+ if coverage.intersects(tile_bbox, self.grid.srs):
163
+ t.source = mask_image_source_from_coverage(
164
+ t.source, tile_bbox, self.grid.srs, coverage, ImageOptions(transparent=True, format='png'))
165
+
166
+ # Remove our internal marker source, for missing tiles.
167
+ if t.source is RESCALE_TILE_MISSING:
168
+ t.source = None
169
+ return tiles
170
+
171
+ def _is_tile_missing(self, tile, cache_only, dimensions=None):
172
+ if tile.coord is None:
173
+ return False
174
+ if cache_only:
175
+ # in cache_only mode, we already fetched the tile from cache
176
+ return tile.is_missing()
177
+ else:
178
+ # missing or staled
179
+ return not self.is_cached(tile, dimensions=dimensions)
180
+
181
+ def _load_tile_coords(self, tiles, dimensions=None, with_metadata=False,
182
+ rescale_till_zoom=None, rescaled_tiles=None
183
+ ):
184
+ uncached_tiles = []
185
+
186
+ if rescaled_tiles:
187
+ for t in tiles:
188
+ if t.coord in rescaled_tiles:
189
+ t.source = rescaled_tiles[t.coord].source
190
+
191
+ # load all in batch
192
+ self.cache.load_tiles(tiles, with_metadata, dimensions=dimensions)
193
+
194
+ # if no real source, we are running in cache_only mode
195
+ cache_only = self.sources == [] or (len(self.sources) == 1 and isinstance(self.sources[0], DummySource))
196
+ # if no rescale_tiles and cache_only, we dont have any additional processing to do
197
+ if (self.rescale_tiles == 0 and cache_only):
198
+ return tiles
199
+
200
+ for tile in tiles:
201
+ if self._is_tile_missing(tile, cache_only, dimensions=dimensions):
202
+ uncached_tiles.append(tile)
203
+
204
+ if uncached_tiles:
205
+ creator = self.creator(dimensions=dimensions)
206
+ created_tiles = creator.create_tiles(uncached_tiles)
207
+ if not created_tiles and self.rescale_tiles:
208
+ created_tiles = [self._scaled_tile(t, rescale_till_zoom, rescaled_tiles) for t in uncached_tiles]
209
+
210
+ for created_tile in created_tiles:
211
+ if created_tile.coord in tiles:
212
+ tiles[created_tile.coord].source = created_tile.source
213
+
214
+ return tiles
215
+
216
+ def remove_tile_coords(self, tile_coords, dimensions=None):
217
+ tiles = TileCollection(tile_coords)
218
+ self.cache.remove_tiles(tiles)
219
+
220
+ def creator(self, dimensions=None):
221
+ return self.tile_creator_class(self, dimensions=dimensions)
222
+
223
+ def lock(self, tile):
224
+ if self.meta_grid:
225
+ tile = Tile(self.meta_grid.main_tile(tile.coord))
226
+ return self.locker.lock(tile)
227
+
228
+ def is_cached(self, tile, dimensions=None):
229
+ """
230
+ Return True if the tile is cached.
231
+ """
232
+ if isinstance(tile, tuple):
233
+ tile = Tile(tile)
234
+ if tile.coord is None:
235
+ return True
236
+ cached = self.cache.is_cached(tile, dimensions=dimensions)
237
+ max_mtime = self.expire_timestamp(tile)
238
+ if cached and max_mtime is not None:
239
+ self.cache.load_tile_metadata(tile, dimensions=self.dimensions)
240
+ # file time stamp must be rounded to integer since time conversion functions
241
+ # mktime and timetuple strip decimals from seconds
242
+ stale = int(tile.timestamp) <= max_mtime
243
+ if stale:
244
+ cached = False
245
+ return cached
246
+
247
+ def is_stale(self, tile, dimensions=None):
248
+ """
249
+ Return True if tile exists _and_ is expired.
250
+ """
251
+ if isinstance(tile, tuple):
252
+ tile = Tile(tile)
253
+ if self.cache.is_cached(tile, dimensions=dimensions):
254
+ # tile exists
255
+ if not self.is_cached(tile, dimensions=dimensions):
256
+ # expired
257
+ return True
258
+ return False
259
+ return False
260
+
261
+ def expire_timestamp(self, tile=None):
262
+ """
263
+ Return the timestamp until which a tile should be accepted as up-to-date,
264
+ or ``None`` if the tiles should not expire.
265
+
266
+ :note: Returns _expire_timestamp by default.
267
+ """
268
+ if self._refresh_before:
269
+ from mapproxy.seed.config import before_timestamp_from_options
270
+ return before_timestamp_from_options(self._refresh_before)
271
+ return self._expire_timestamp
272
+
273
+ def apply_tile_filter(self, tile):
274
+ """
275
+ Apply all `pre_store_filter` to this tile.
276
+ Returns filtered tile.
277
+ """
278
+ if tile.stored:
279
+ return tile
280
+
281
+ for img_filter in self.pre_store_filter:
282
+ tile = img_filter(tile)
283
+ return tile
284
+
285
+ def _scaled_tile(self, tile, stop_zoom, rescaled_tiles):
286
+ """
287
+ Try to load tile by loading, scaling and clipping tiles from zoom levels above or
288
+ below. stop_zoom determines if tiles from above should be scaled up, or if tiles
289
+ from below should be scaled down.
290
+ Returns an empty Tile if tile zoom level is stop_zoom.
291
+ """
292
+ if tile.coord in rescaled_tiles:
293
+ return rescaled_tiles[tile.coord]
294
+
295
+ # Cache tile in rescaled_tiles. We initially set source to a fixed
296
+ # BlankImageSource and overwrite it if we actually rescaled the tile.
297
+ tile.source = RESCALE_TILE_MISSING
298
+ rescaled_tiles[tile.coord] = tile
299
+
300
+ tile_bbox = self.grid.tile_bbox(tile.coord)
301
+ current_zoom = tile.coord[2]
302
+ if stop_zoom == current_zoom:
303
+ return tile
304
+ if stop_zoom > current_zoom:
305
+ src_level = current_zoom + 1
306
+ else:
307
+ src_level = current_zoom - 1
308
+
309
+ src_bbox, src_tile_grid, affected_tile_coords = self.grid.get_affected_level_tiles(tile_bbox, src_level)
310
+
311
+ affected_tiles = TileCollection(affected_tile_coords)
312
+ for t in affected_tiles:
313
+ # Add sources of cached tiles, to avoid loading same tile multiple times
314
+ # loading recursive.
315
+ if t.coord in rescaled_tiles:
316
+ t.source = rescaled_tiles[t.coord].source
317
+
318
+ tile_collection = self._load_tile_coords(
319
+ affected_tiles,
320
+ rescale_till_zoom=stop_zoom,
321
+ rescaled_tiles=rescaled_tiles,
322
+ )
323
+
324
+ if tile_collection.blank:
325
+ return tile
326
+
327
+ tile_sources = []
328
+ for t in tile_collection:
329
+ # Replace RESCALE_TILE_MISSING with None, before transforming tiles.
330
+ tile_sources.append(t.source if t.source is not RESCALE_TILE_MISSING else None)
331
+
332
+ tiled_image = TiledImage(tile_sources, src_bbox=src_bbox, src_srs=self.grid.srs,
333
+ tile_grid=src_tile_grid, tile_size=self.grid.tile_size)
334
+ tile.source = tiled_image.transform(tile_bbox, self.grid.srs, self.grid.tile_size, self.image_opts)
335
+
336
+ if self.cache_rescaled_tiles:
337
+ self.cache.store_tile(tile)
338
+ return tile
339
+
340
+
341
+ # RESCALE_TILE_MISSING is a dummy source to prevent a tile cache from loading
342
+ # a tile that we already found out is missing.
343
+ RESCALE_TILE_MISSING = BlankImageSource((256, 256), ImageOptions())
344
+
345
+
346
+ class TileCreator(object):
347
+ def __init__(self, tile_mgr, dimensions=None, image_merger=None, bulk_meta_tiles=False):
348
+ self.cache = tile_mgr.cache
349
+ self.sources = tile_mgr.sources
350
+ self.grid = tile_mgr.grid
351
+ self.meta_grid = tile_mgr.meta_grid
352
+ self.bulk_meta_tiles = bulk_meta_tiles
353
+ self.tile_mgr = tile_mgr
354
+ self.dimensions = dimensions
355
+ self.image_merger = image_merger
356
+
357
+ def is_cached(self, tile, dimensions=None):
358
+ """
359
+ Return True if the tile is cached.
360
+ """
361
+ return self.tile_mgr.is_cached(tile, dimensions=dimensions)
362
+
363
+ def is_stale(self, tile):
364
+ """
365
+ Return True if the tile exists in cache and is expired.
366
+ """
367
+ return self.tile_mgr.is_stale(tile)
368
+
369
+ def create_tiles(self, tiles):
370
+ if not self.sources:
371
+ return []
372
+ if not self.meta_grid:
373
+ created_tiles = self._create_single_tiles(tiles)
374
+ elif self.tile_mgr.minimize_meta_requests and len(tiles) > 1:
375
+ # use minimal requests only for mulitple tile requests (ie not for TMS)
376
+ meta_tile = self.meta_grid.minimal_meta_tile([t.coord for t in tiles])
377
+ created_tiles = self._create_meta_tile(meta_tile)
378
+ else:
379
+ meta_tiles = []
380
+ meta_bboxes = set()
381
+ for tile in tiles:
382
+ meta_tile = self.meta_grid.meta_tile(tile.coord)
383
+ if meta_tile.bbox not in meta_bboxes:
384
+ meta_tiles.append(meta_tile)
385
+ meta_bboxes.add(meta_tile.bbox)
386
+
387
+ created_tiles = self._create_meta_tiles(meta_tiles)
388
+
389
+ return created_tiles
390
+
391
+ def _create_single_tiles(self, tiles):
392
+ if self.tile_mgr.concurrent_tile_creators > 1 and len(tiles) > 1:
393
+ return self._create_threaded(self._create_single_tile, tiles)
394
+
395
+ created_tiles = []
396
+ for tile in tiles:
397
+ created_tiles.extend(self._create_single_tile(tile))
398
+ return created_tiles
399
+
400
+ def _create_threaded(self, create_func, tiles):
401
+ result = []
402
+ async_pool = async_.Pool(self.tile_mgr.concurrent_tile_creators)
403
+ for new_tiles in async_pool.imap(create_func, tiles):
404
+ result.extend(new_tiles)
405
+ return result
406
+
407
+ def _create_single_tile(self, tile, dimensions=None):
408
+ tile_bbox = self.grid.tile_bbox(tile.coord)
409
+ query = MapQuery(tile_bbox, self.grid.tile_size, self.grid.srs,
410
+ self.tile_mgr.request_format, dimensions=self.dimensions)
411
+ with self.tile_mgr.lock(tile):
412
+ if not self.is_cached(tile, dimensions=dimensions):
413
+ source = None
414
+ try:
415
+ source = self._query_sources(query)
416
+ # if source is not available, try to serve tile in cache
417
+ except SourceError as e:
418
+ if self.is_stale(tile):
419
+ self.cache.load_tile(tile)
420
+ else:
421
+ reraise_exception(e, sys.exc_info())
422
+ if not source:
423
+ return []
424
+ if source.authorize_stale and self.is_stale(tile):
425
+ # The configuration authorises blank tiles generated by the error_handler
426
+ # to be replaced by stale tiles from cache.
427
+ self.cache.load_tile(tile)
428
+ return [tile]
429
+ if self.tile_mgr.image_opts != source.image_opts:
430
+ # call as_buffer to force conversion into cache format
431
+ source.as_buffer(self.tile_mgr.image_opts)
432
+ source.image_opts = self.tile_mgr.image_opts
433
+ tile.source = source
434
+ tile.cacheable = source.cacheable
435
+ tile = self.tile_mgr.apply_tile_filter(tile)
436
+ if source.cacheable:
437
+ self.cache.store_tile(tile)
438
+ else:
439
+ self.cache.load_tile(tile)
440
+ return [tile]
441
+
442
+ def _query_sources(self, query):
443
+ """
444
+ Query all sources and return the results as a single ImageSource.
445
+ Multiple sources will be merged into a single image.
446
+ """
447
+
448
+ # directly return get_map without merge if ...
449
+ if (len(self.sources) == 1 and
450
+ not self.image_merger and # no special image_merger (like BandMerger)
451
+ not (self.sources[0].coverage and # no clipping coverage
452
+ self.sources[0].coverage.clip and
453
+ self.sources[0].coverage.intersects(query.bbox, query.srs))):
454
+ try:
455
+ return self.sources[0].get_map(query)
456
+ except BlankImage:
457
+ return None
458
+
459
+ def get_map_from_source(source):
460
+ try:
461
+ img = source.get_map(query)
462
+ except BlankImage:
463
+ return None, None
464
+ else:
465
+ return (img, source.coverage)
466
+
467
+ layers = []
468
+ for layer in async_.imap(get_map_from_source, self.sources):
469
+ if layer[0] is not None:
470
+ layers.append(layer)
471
+
472
+ return merge_images(layers, size=query.size, bbox=query.bbox, bbox_srs=query.srs,
473
+ image_opts=self.tile_mgr.image_opts, merger=self.image_merger)
474
+
475
+ def _create_meta_tiles(self, meta_tiles):
476
+ if self.bulk_meta_tiles:
477
+ created_tiles = []
478
+ for meta_tile in meta_tiles:
479
+ created_tiles.extend(self._create_bulk_meta_tile(meta_tile))
480
+ return created_tiles
481
+
482
+ if self.tile_mgr.concurrent_tile_creators > 1 and len(meta_tiles) > 1:
483
+ return self._create_threaded(self._create_meta_tile, meta_tiles)
484
+
485
+ created_tiles = []
486
+ for meta_tile in meta_tiles:
487
+ created_tiles.extend(self._create_meta_tile(meta_tile))
488
+ return created_tiles
489
+
490
+ def _create_meta_tile(self, meta_tile):
491
+ """
492
+ _create_meta_tile queries a single meta tile and splits it into
493
+ tiles.
494
+ """
495
+ tile_size = self.grid.tile_size
496
+ query = MapQuery(meta_tile.bbox, meta_tile.size, self.grid.srs, self.tile_mgr.request_format,
497
+ dimensions=self.dimensions)
498
+ main_tile = Tile(meta_tile.main_tile_coord)
499
+ with self.tile_mgr.lock(main_tile):
500
+ if not all(self.is_cached(t, dimensions=self.dimensions) for t in meta_tile.tiles if t is not None):
501
+ meta_tile_image = self._query_sources(query)
502
+ if not meta_tile_image:
503
+ return []
504
+ splitted_tiles = split_meta_tiles(meta_tile_image, meta_tile.tile_patterns,
505
+ tile_size, self.tile_mgr.image_opts)
506
+ splitted_tiles = [self.tile_mgr.apply_tile_filter(t) for t in splitted_tiles]
507
+ if meta_tile_image.cacheable:
508
+ self.cache.store_tiles(splitted_tiles, dimensions=self.dimensions)
509
+ return splitted_tiles
510
+ # else
511
+ tiles = [Tile(coord) for coord in meta_tile.tiles]
512
+ self.cache.load_tiles(tiles, dimensions=self.dimensions)
513
+ return tiles
514
+
515
+ def _create_bulk_meta_tile(self, meta_tile):
516
+ """
517
+ _create_bulk_meta_tile queries each tile of the meta tile in parallel
518
+ (using concurrent_tile_creators).
519
+ """
520
+ tile_size = self.grid.tile_size
521
+ main_tile = Tile(meta_tile.main_tile_coord)
522
+ with self.tile_mgr.lock(main_tile):
523
+ if not all(self.is_cached(t, dimensions=self.dimensions) for t in meta_tile.tiles if t is not None):
524
+ async_pool = async_.Pool(self.tile_mgr.concurrent_tile_creators)
525
+
526
+ def query_tile(coord):
527
+ try:
528
+ query = MapQuery(
529
+ self.grid.tile_bbox(coord), tile_size, self.grid.srs, self.tile_mgr.request_format,
530
+ dimensions=self.dimensions)
531
+ tile_image = self._query_sources(query)
532
+ if tile_image is None:
533
+ return None
534
+
535
+ if self.tile_mgr.image_opts != tile_image.image_opts:
536
+ # call as_buffer to force conversion into cache format
537
+ tile_image.as_buffer(self.tile_mgr.image_opts)
538
+
539
+ tile = Tile(coord, cacheable=tile_image.cacheable)
540
+ tile.source = tile_image
541
+ tile = self.tile_mgr.apply_tile_filter(tile)
542
+ except BlankImage:
543
+ return None
544
+ else:
545
+ return tile
546
+
547
+ tiles = []
548
+ for tile_task in async_pool.imap(query_tile,
549
+ [t for t in meta_tile.tiles if t is not None],
550
+ use_result_objects=True,
551
+ ):
552
+ if tile_task.exception is None:
553
+ tile = tile_task.result
554
+ if tile is not None:
555
+ tiles.append(tile)
556
+ else:
557
+ ex = tile_task.exception
558
+ async_pool.shutdown(True)
559
+ reraise(ex)
560
+
561
+ self.cache.store_tiles([t for t in tiles if t.cacheable], dimensions=self.dimensions)
562
+ return tiles
563
+
564
+ # else
565
+ tiles = [Tile(coord) for coord in meta_tile.tiles]
566
+ self.cache.load_tiles(tiles, dimensions=self.dimensions)
567
+ return tiles
568
+
569
+
570
+ class Tile(object):
571
+ """
572
+ Internal data object for all tiles. Stores the tile-``coord`` and the tile data.
573
+
574
+ :ivar source: the data of this tile
575
+ :type source: ImageSource
576
+ """
577
+
578
+ def __init__(self, coord, source=None, cacheable=True):
579
+ self.coord = coord
580
+ self.source = source
581
+ self.location = None
582
+ self.stored = False
583
+ self._cacheable = cacheable
584
+ self.size = None
585
+ self.timestamp = None
586
+
587
+ def _cacheable_get(self):
588
+ return CacheInfo(cacheable=self._cacheable, timestamp=self.timestamp,
589
+ size=self.size)
590
+
591
+ def _cacheable_set(self, cacheable):
592
+ if isinstance(cacheable, bool):
593
+ self._cacheable = cacheable
594
+ else: # assume cacheable is CacheInfo
595
+ self._cacheable = cacheable.cacheable
596
+ self.timestamp = cacheable.timestamp
597
+ self.size = cacheable.size
598
+
599
+ cacheable = property(_cacheable_get, _cacheable_set)
600
+
601
+ def source_buffer(self, *args, **kw):
602
+ if self.source is not None:
603
+ return self.source.as_buffer(*args, **kw)
604
+ else:
605
+ return None
606
+
607
+ def source_image(self, *args, **kw):
608
+ if self.source is not None:
609
+ return self.source.as_image(*args, **kw)
610
+ else:
611
+ return None
612
+
613
+ def is_missing(self):
614
+ """
615
+ Returns ``True`` when the tile has no ``data``, except when the ``coord``
616
+ is ``None``. It doesn't check if the tile exists.
617
+
618
+ >>> Tile((1, 2, 3)).is_missing()
619
+ True
620
+ >>> Tile((1, 2, 3), './tmp/foo').is_missing()
621
+ False
622
+ >>> Tile(None).is_missing()
623
+ False
624
+ """
625
+ if self.coord is None:
626
+ return False
627
+ return self.source is None
628
+
629
+ def __eq__(self, other):
630
+ """
631
+ >>> Tile((0, 0, 1)) == Tile((0, 0, 1))
632
+ True
633
+ >>> Tile((0, 0, 1)) == Tile((1, 0, 1))
634
+ False
635
+ >>> Tile((0, 0, 1)) is None
636
+ False
637
+ """
638
+ if isinstance(other, Tile):
639
+ return (self.coord == other.coord and
640
+ self.source == other.source)
641
+ else:
642
+ return NotImplemented
643
+
644
+ def __ne__(self, other):
645
+ """
646
+ >>> Tile((0, 0, 1)) != Tile((0, 0, 1))
647
+ False
648
+ >>> Tile((0, 0, 1)) != Tile((1, 0, 1))
649
+ True
650
+ >>> Tile((0, 0, 1)) != None
651
+ True
652
+ """
653
+ equal_result = self.__eq__(other)
654
+ if equal_result is NotImplemented:
655
+ return NotImplemented
656
+ else:
657
+ return not equal_result
658
+
659
+ def __repr__(self):
660
+ return 'Tile(%r, source=%r)' % (self.coord, self.source)
661
+
662
+
663
+ class CacheInfo(object):
664
+ def __init__(self, cacheable=True, timestamp=None, size=None):
665
+ self.cacheable = cacheable
666
+ self.timestamp = timestamp
667
+ self.size = size
668
+
669
+ def __bool__(self):
670
+ return self.cacheable
671
+
672
+
673
+ class TileCollection(object):
674
+ def __init__(self, tile_coords):
675
+ self.tiles = [Tile(coord) for coord in tile_coords]
676
+ self.tiles_dict = {}
677
+ for tile in self.tiles:
678
+ self.tiles_dict[tile.coord] = tile
679
+
680
+ def __getitem__(self, idx_or_coord):
681
+ if isinstance(idx_or_coord, int):
682
+ return self.tiles[idx_or_coord]
683
+ if idx_or_coord in self.tiles_dict:
684
+ return self.tiles_dict[idx_or_coord]
685
+ return Tile(idx_or_coord)
686
+
687
+ def __contains__(self, tile_or_coord):
688
+ if isinstance(tile_or_coord, tuple):
689
+ return tile_or_coord in self.tiles_dict
690
+ if hasattr(tile_or_coord, 'coord'):
691
+ return tile_or_coord.coord in self.tiles_dict
692
+ return False
693
+
694
+ def __len__(self):
695
+ return len(self.tiles)
696
+
697
+ def __iter__(self):
698
+ return iter(self.tiles)
699
+
700
+ @property
701
+ def empty(self):
702
+ """
703
+ Returns True if no tile in this collection contains a source.
704
+ """
705
+ return all((t.source is None for t in self.tiles))
706
+
707
+ @property
708
+ def blank(self):
709
+ """
710
+ Returns True if all sources collection are BlankImageSources or have not source at all.
711
+ """
712
+ return all((t.source is None or isinstance(t.source, BlankImageSource) for t in self.tiles))
713
+
714
+ def __repr__(self):
715
+ return 'TileCollection(%r)' % self.tiles
716
+
717
+
718
+ def split_meta_tiles(meta_tile, tiles, tile_size, image_opts):
719
+ try:
720
+ # TODO png8
721
+ # if not self.transparent and format == 'png':
722
+ # format = 'png8'
723
+ splitter = TileSplitter(meta_tile, image_opts)
724
+ except IOError:
725
+ # TODO
726
+ raise
727
+ split_tiles = []
728
+ for tile in tiles:
729
+ tile_coord, crop_coord = tile
730
+ if tile_coord is None:
731
+ continue
732
+ data = splitter.get_tile(crop_coord, tile_size)
733
+ new_tile = Tile(tile_coord, cacheable=meta_tile.cacheable)
734
+ new_tile.source = data
735
+ split_tiles.append(new_tile)
736
+ return split_tiles