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,665 @@
1
+ # This file is part of the MapProxy project.
2
+ # Copyright (C) 2016-2017 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
+ import contextlib
17
+ import errno
18
+ import hashlib
19
+ import os
20
+ import shutil
21
+ import struct
22
+ from io import BytesIO
23
+
24
+ from mapproxy.image import ImageSource
25
+ from mapproxy.cache.base import TileCacheBase, tile_buffer
26
+ from mapproxy.util.fs import ensure_directory, write_atomic
27
+ from mapproxy.util.lock import FileLock
28
+
29
+ import logging
30
+ log = logging.getLogger(__name__)
31
+
32
+
33
+ class CompactCacheBase(TileCacheBase):
34
+ supports_timestamp = False
35
+ bundle_class = None
36
+
37
+ def __init__(self, cache_dir, coverage=None):
38
+ super(CompactCacheBase, self).__init__(coverage)
39
+ md5 = hashlib.new('md5', cache_dir.encode('utf-8'), usedforsecurity=False)
40
+ self.lock_cache_id = 'compactcache-' + md5.hexdigest()
41
+ self.cache_dir = cache_dir
42
+
43
+ def _get_bundle_fname_and_offset(self, tile_coord):
44
+ x, y, z = tile_coord
45
+
46
+ level_dir = os.path.join(self.cache_dir, 'L%02d' % z)
47
+
48
+ c = x // BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_WIDTH
49
+ r = y // BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_HEIGHT
50
+
51
+ basename = 'R%04xC%04x' % (r, c)
52
+ return os.path.join(level_dir, basename), (c, r)
53
+
54
+ def _get_bundle(self, tile_coord):
55
+ bundle_fname, offset = self._get_bundle_fname_and_offset(tile_coord)
56
+ return self.bundle_class(bundle_fname, offset=offset)
57
+
58
+ def is_cached(self, tile, dimensions=None):
59
+ if tile.coord is None:
60
+ return True
61
+ if tile.source:
62
+ return True
63
+
64
+ return self._get_bundle(tile.coord).is_cached(tile, dimensions=dimensions)
65
+
66
+ def store_tile(self, tile, dimensions=None):
67
+ if tile.stored:
68
+ return True
69
+
70
+ return self._get_bundle(tile.coord).store_tile(tile, dimensions=dimensions)
71
+
72
+ def store_tiles(self, tiles, dimensions=None):
73
+ if len(tiles) > 1:
74
+ # Check if all tiles are from a single bundle.
75
+ bundle_files = set()
76
+ tile_coord = None
77
+ for t in tiles:
78
+ if t.stored:
79
+ continue
80
+ bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
81
+ tile_coord = t.coord
82
+ if len(bundle_files) == 1:
83
+ return self._get_bundle(tile_coord).store_tiles(tiles, dimensions=dimensions)
84
+
85
+ # Tiles are across multiple bundles
86
+ failed = False
87
+ for tile in tiles:
88
+ if not self.store_tile(tile, dimensions=dimensions):
89
+ failed = True
90
+ return not failed
91
+
92
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
93
+ if tile.source or tile.coord is None:
94
+ return True
95
+
96
+ return self._get_bundle(tile.coord).load_tile(tile, dimensions=dimensions)
97
+
98
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
99
+ if len(tiles) > 1:
100
+ # Check if all tiles are from a single bundle.
101
+ bundle_files = set()
102
+ tile_coord = None
103
+ for t in tiles:
104
+ if t.source or t.coord is None:
105
+ continue
106
+ bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
107
+ tile_coord = t.coord
108
+ if len(bundle_files) == 1:
109
+ return self._get_bundle(tile_coord).load_tiles(tiles, dimensions=dimensions)
110
+
111
+ # No support_bulk_load or tiles are across multiple bundles
112
+ missing = False
113
+ for tile in tiles:
114
+ if not self.load_tile(tile, with_metadata=with_metadata, dimensions=dimensions):
115
+ missing = True
116
+ return not missing
117
+
118
+ def remove_tile(self, tile, dimensions=None):
119
+ if tile.coord is None:
120
+ return True
121
+
122
+ return self._get_bundle(tile.coord).remove_tile(tile, dimensions=dimensions)
123
+
124
+ def load_tile_metadata(self, tile, dimensions=None):
125
+ if self.load_tile(tile, dimensions=dimensions):
126
+ tile.timestamp = -1
127
+
128
+ def remove_level_tiles_before(self, level, timestamp):
129
+ if timestamp == 0:
130
+ level_dir = os.path.join(self.cache_dir, 'L%02d' % level)
131
+ shutil.rmtree(level_dir, ignore_errors=True)
132
+ return True
133
+ return False
134
+
135
+
136
+ BUNDLE_EXT = '.bundle'
137
+ BUNDLEX_V1_EXT = '.bundlx'
138
+
139
+
140
+ class BundleV1(object):
141
+ def __init__(self, base_filename, offset):
142
+ self.base_filename = base_filename
143
+ self.lock_filename = base_filename + '.lck'
144
+ self.offset = offset
145
+
146
+ def _rel_tile_coord(self, tile_coord):
147
+ return (
148
+ tile_coord[0] % BUNDLEX_V1_GRID_WIDTH,
149
+ tile_coord[1] % BUNDLEX_V1_GRID_HEIGHT,
150
+ )
151
+
152
+ def data(self):
153
+ return BundleDataV1(self.base_filename + BUNDLE_EXT, self.offset)
154
+
155
+ def index(self):
156
+ return BundleIndexV1(self.base_filename + BUNDLEX_V1_EXT)
157
+
158
+ def is_cached(self, tile, dimensions=None):
159
+ if tile.source or tile.coord is None:
160
+ return True
161
+
162
+ with self.index().readonly() as idx:
163
+ if not idx:
164
+ return False
165
+ x, y = self._rel_tile_coord(tile.coord)
166
+ offset = idx.tile_offset(x, y)
167
+ if offset == 0:
168
+ return False
169
+
170
+ with self.data().readonly() as bundle:
171
+ size = bundle.read_size(offset)
172
+ return size != 0
173
+
174
+ def store_tile(self, tile, dimensions=None):
175
+ if tile.stored:
176
+ return True
177
+ return self.store_tiles([tile], dimensions=dimensions)
178
+
179
+ def store_tiles(self, tiles, dimensions=None):
180
+ tiles_data = []
181
+ for t in tiles:
182
+ if t.stored:
183
+ continue
184
+ with tile_buffer(t) as buf:
185
+ data = buf.read()
186
+ tiles_data.append((t.coord, data))
187
+
188
+ with FileLock(self.lock_filename):
189
+ with self.data().readwrite() as bundle:
190
+ with self.index().readwrite() as idx:
191
+ for tile_coord, data in tiles_data:
192
+ x, y = self._rel_tile_coord(tile_coord)
193
+ offset = idx.tile_offset(x, y)
194
+ offset, size = bundle.append_tile(data, prev_offset=offset)
195
+ idx.update_tile_offset(x, y, offset=offset, size=size)
196
+
197
+ return True
198
+
199
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
200
+ if tile.source or tile.coord is None:
201
+ return True
202
+ return self.load_tiles([tile], with_metadata, dimensions=dimensions)
203
+
204
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
205
+ missing = False
206
+
207
+ with self.index().readonly() as idx:
208
+ if not idx:
209
+ return False
210
+ with self.data().readonly() as bundle:
211
+ for t in tiles:
212
+ if t.source or t.coord is None:
213
+ continue
214
+ x, y = self._rel_tile_coord(t.coord)
215
+ offset = idx.tile_offset(x, y)
216
+ if offset == 0:
217
+ missing = True
218
+ continue
219
+
220
+ data = bundle.read_tile(offset)
221
+ if not data:
222
+ missing = True
223
+ continue
224
+ t.source = ImageSource(BytesIO(data))
225
+
226
+ return not missing
227
+
228
+ def remove_tile(self, tile, dimensions=None):
229
+ if tile.coord is None:
230
+ return True
231
+
232
+ with FileLock(self.lock_filename):
233
+ with self.index().readwrite() as idx:
234
+ x, y = self._rel_tile_coord(tile.coord)
235
+ idx.remove_tile_offset(x, y)
236
+
237
+ return True
238
+
239
+ def size(self):
240
+ total_size = 0
241
+
242
+ with self.index().readonly() as idx:
243
+ if not idx:
244
+ return 0, 0
245
+
246
+ with self.data().readonly() as bundle:
247
+ for y in range(BUNDLEX_V1_GRID_HEIGHT):
248
+ for x in range(BUNDLEX_V1_GRID_WIDTH):
249
+ offset = idx.tile_offset(x, y)
250
+ if not offset:
251
+ continue
252
+ size = bundle.read_size(offset)
253
+ if not size:
254
+ continue
255
+ total_size += size + 4
256
+
257
+ actual_size = os.path.getsize(bundle.filename)
258
+ return total_size + BUNDLE_V1_HEADER_SIZE + (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4), actual_size
259
+
260
+
261
+ BUNDLEX_V1_GRID_WIDTH = 128
262
+ BUNDLEX_V1_GRID_HEIGHT = 128
263
+ BUNDLEX_V1_HEADER_SIZE = 16
264
+ BUNDLEX_V1_HEADER = b'\x03\x00\x00\x00\x10\x00\x00\x00\x00\x40\x00\x00\x05\x00\x00\x00'
265
+ BUNDLEX_V1_FOOTER_SIZE = 16
266
+ BUNDLEX_V1_FOOTER = b'\x00\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00'
267
+
268
+ INT64LE = struct.Struct('<Q')
269
+
270
+
271
+ class BundleIndexV1(object):
272
+ def __init__(self, filename):
273
+ self.filename = filename
274
+ self._fh = None
275
+ # defer initialization to update/remove calls to avoid
276
+ # index creation on is_cached (prevents new files in read-only caches)
277
+ self._initialized = False
278
+
279
+ def _init_index(self):
280
+ self._initialized = True
281
+ if os.path.exists(self.filename):
282
+ return
283
+ ensure_directory(self.filename)
284
+ buf = BytesIO()
285
+ buf.write(BUNDLEX_V1_HEADER)
286
+
287
+ for i in range(BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_HEIGHT):
288
+ buf.write(INT64LE.pack((i*4)+BUNDLE_V1_HEADER_SIZE)[:5])
289
+ buf.write(BUNDLEX_V1_FOOTER)
290
+ write_atomic(self.filename, buf.getvalue())
291
+
292
+ def _tile_index_offset(self, x, y):
293
+ return BUNDLEX_V1_HEADER_SIZE + (x * BUNDLEX_V1_GRID_HEIGHT + y) * 5
294
+
295
+ def tile_offset(self, x, y):
296
+ if self._fh is None:
297
+ raise RuntimeError('not called within readonly/readwrite context')
298
+ idx_offset = self._tile_index_offset(x, y)
299
+ self._fh.seek(idx_offset)
300
+ offset = INT64LE.unpack(self._fh.read(5) + b'\x00\x00\x00')[0]
301
+ return offset
302
+
303
+ def update_tile_offset(self, x, y, offset, size):
304
+ if self._fh is None:
305
+ raise RuntimeError('not called within readwrite context')
306
+ idx_offset = self._tile_index_offset(x, y)
307
+ offset = INT64LE.pack(offset)[:5]
308
+ self._fh.seek(idx_offset, os.SEEK_SET)
309
+ self._fh.write(offset)
310
+
311
+ def remove_tile_offset(self, x, y):
312
+ if self._fh is None:
313
+ raise RuntimeError('not called within readwrite context')
314
+ idx_offset = self._tile_index_offset(x, y)
315
+ self._fh.seek(idx_offset)
316
+ self._fh.write(b'\x00' * 5)
317
+
318
+ @contextlib.contextmanager
319
+ def readonly(self):
320
+ try:
321
+ with open(self.filename, 'rb') as fh:
322
+ b = BundleIndexV1(self.filename)
323
+ b._fh = fh
324
+ yield b
325
+ except IOError as ex:
326
+ if ex.errno == errno.ENOENT:
327
+ # missing bundle file -> missing tile
328
+ yield None
329
+ else:
330
+ raise ex
331
+
332
+ @contextlib.contextmanager
333
+ def readwrite(self):
334
+ self._init_index()
335
+ with open(self.filename, 'r+b') as fh:
336
+ b = BundleIndexV1(self.filename)
337
+ b._fh = fh
338
+ yield b
339
+
340
+ # The bundle file has a header with 15 little-endian long values (60 bytes).
341
+ # NOTE: the fixed values might be some flags for image options (format, aliasing)
342
+ # all files available for testing had the same values however.
343
+
344
+
345
+ BUNDLE_V1_HEADER_SIZE = 60
346
+ BUNDLE_V1_HEADER = [
347
+ 3, # 0, fixed
348
+ 16384, # 1, max. num of tiles 128*128 = 16384
349
+ 16, # 2, size of largest tile
350
+ 5, # 3, fixed
351
+ 0, # 4, num of tiles in bundle (*4)
352
+ 60+65536, # 5, bundle size
353
+ 40, # 6 fixed
354
+ 16, # 7, fixed
355
+ 0, # 8, y0
356
+ 127, # 9, y1
357
+ 0, # 10, x0
358
+ 127, # 11, x1
359
+ ]
360
+ BUNDLE_V1_HEADER_STRUCT_FORMAT = '<4I3Q5I'
361
+
362
+
363
+ class BundleDataV1(object):
364
+ def __init__(self, filename, tile_offsets):
365
+ self.filename = filename
366
+ self.tile_offsets = tile_offsets
367
+ self._fh = None
368
+ if not os.path.exists(self.filename):
369
+ self._init_bundle()
370
+
371
+ def _init_bundle(self):
372
+ ensure_directory(self.filename)
373
+ header = list(BUNDLE_V1_HEADER)
374
+ header[10], header[8] = self.tile_offsets
375
+ header[11], header[9] = header[10]+127, header[8]+127
376
+ write_atomic(self.filename,
377
+ struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header) +
378
+ # zero-size entry for each tile
379
+ (b'\x00' * (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4)))
380
+
381
+ @contextlib.contextmanager
382
+ def readonly(self):
383
+ with open(self.filename, 'rb') as fh:
384
+ b = BundleDataV1(self.filename, self.tile_offsets)
385
+ b._fh = fh
386
+ yield b
387
+
388
+ @contextlib.contextmanager
389
+ def readwrite(self):
390
+ with open(self.filename, 'r+b') as fh:
391
+ b = BundleDataV1(self.filename, self.tile_offsets)
392
+ b._fh = fh
393
+ yield b
394
+
395
+ def read_size(self, offset):
396
+ if self._fh is None:
397
+ raise RuntimeError('not called within readonly/readwrite context')
398
+ self._fh.seek(offset)
399
+ return struct.unpack('<L', self._fh.read(4))[0]
400
+
401
+ def read_tile(self, offset):
402
+ if self._fh is None:
403
+ raise RuntimeError('not called within readonly/readwrite context')
404
+ self._fh.seek(offset)
405
+ size = struct.unpack('<L', self._fh.read(4))[0]
406
+ if size <= 0:
407
+ return False
408
+ return self._fh.read(size)
409
+
410
+ def append_tile(self, data, prev_offset):
411
+ if self._fh is None:
412
+ raise RuntimeError('not called within readwrite context')
413
+ size = len(data)
414
+ is_new_tile = True
415
+ if prev_offset:
416
+ self._fh.seek(prev_offset, os.SEEK_SET)
417
+ if self._fh.tell() == prev_offset:
418
+ if struct.unpack('<L', self._fh.read(4))[0] > 0:
419
+ is_new_tile = False
420
+
421
+ self._fh.seek(0, os.SEEK_END)
422
+ offset = self._fh.tell()
423
+ if offset == 0:
424
+ self._fh.write(b'\x00' * 16) # header
425
+ offset = 16
426
+ self._fh.write(struct.pack('<L', size))
427
+ self._fh.write(data)
428
+
429
+ # update header
430
+ self._fh.seek(0, os.SEEK_SET)
431
+ header = list(struct.unpack(BUNDLE_V1_HEADER_STRUCT_FORMAT, self._fh.read(60)))
432
+ header[2] = max(header[2], size)
433
+ header[5] += size + 4
434
+ if is_new_tile:
435
+ header[4] += 4
436
+ self._fh.seek(0, os.SEEK_SET)
437
+ self._fh.write(struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header))
438
+
439
+ return offset, size
440
+
441
+
442
+ BUNDLE_V2_GRID_WIDTH = 128
443
+ BUNDLE_V2_GRID_HEIGHT = 128
444
+ BUNDLE_V2_TILES = BUNDLE_V2_GRID_WIDTH * BUNDLE_V2_GRID_HEIGHT
445
+ BUNDLE_V2_INDEX_SIZE = BUNDLE_V2_TILES * 8
446
+
447
+ BUNDLE_V2_HEADER = (
448
+ 3, # Version
449
+ BUNDLE_V2_TILES, # numRecords
450
+ 0, # maxRecord Size
451
+ 5, # Offset Size
452
+ 0, # Slack Space
453
+ 64 + BUNDLE_V2_INDEX_SIZE, # File Size
454
+ 40, # User Header Offset
455
+ 20 + BUNDLE_V2_INDEX_SIZE, # User Header Size
456
+ 3, # Legacy 1
457
+ 16, # Legacy 2 0?
458
+ BUNDLE_V2_TILES, # Legacy 3
459
+ 5, # Legacy 4
460
+ BUNDLE_V2_INDEX_SIZE # Index Size
461
+ )
462
+ BUNDLE_V2_HEADER_STRUCT_FORMAT = '<4I3Q6I'
463
+ BUNDLE_V2_HEADER_SIZE = 64
464
+
465
+
466
+ class BundleV2(object):
467
+ def __init__(self, base_filename, offset=None):
468
+ # offset not used by V2
469
+ self.filename = base_filename + '.bundle'
470
+ self.lock_filename = base_filename + '.lck'
471
+
472
+ # defer initialization to update/remove calls to avoid
473
+ # index creation on is_cached (prevents new files in read-only caches)
474
+ self._initialized = False
475
+
476
+ def _init_index(self):
477
+ self._initialized = True
478
+ if os.path.exists(self.filename):
479
+ return
480
+ ensure_directory(self.filename)
481
+ buf = BytesIO()
482
+ buf.write(struct.pack(BUNDLE_V2_HEADER_STRUCT_FORMAT, *BUNDLE_V2_HEADER))
483
+ # Empty index (ArcGIS stores an offset of 4 and size of 0 for missing tiles)
484
+ buf.write(struct.pack('<%dQ' % BUNDLE_V2_TILES, *(4, ) * BUNDLE_V2_TILES))
485
+ write_atomic(self.filename, buf.getvalue())
486
+
487
+ def _tile_idx_offset(self, x, y):
488
+ return BUNDLE_V2_HEADER_SIZE + (x + BUNDLE_V2_GRID_HEIGHT * y) * 8
489
+
490
+ def _rel_tile_coord(self, tile_coord):
491
+ return (tile_coord[0] % BUNDLE_V2_GRID_WIDTH,
492
+ tile_coord[1] % BUNDLE_V2_GRID_HEIGHT, )
493
+
494
+ def _tile_offset_size(self, fh, x, y):
495
+ idx_offset = self._tile_idx_offset(x, y)
496
+ fh.seek(idx_offset)
497
+ val = INT64LE.unpack(fh.read(8))[0]
498
+ # Index contains 8 bytes per tile.
499
+ # Size is stored in 24 most significant bits.
500
+ # Offset in the least significant 40 bits.
501
+ size = val >> 40
502
+ if size == 0:
503
+ return 0, 0
504
+ offset = val - (size << 40)
505
+ return offset, size
506
+
507
+ def _load_tile(self, fh, tile, dimensions=None):
508
+ if tile.source or tile.coord is None:
509
+ return True
510
+
511
+ x, y = self._rel_tile_coord(tile.coord)
512
+ offset, size = self._tile_offset_size(fh, x, y)
513
+ if not size:
514
+ return False
515
+
516
+ fh.seek(offset)
517
+ data = fh.read(size)
518
+
519
+ tile.source = ImageSource(BytesIO(data))
520
+ return True
521
+
522
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
523
+ if tile.source or tile.coord is None:
524
+ return True
525
+
526
+ return self.load_tiles([tile], with_metadata, dimensions=dimensions)
527
+
528
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
529
+ missing = False
530
+
531
+ with self._readonly() as fh:
532
+ if not fh:
533
+ return False
534
+
535
+ for t in tiles:
536
+ if t.source or t.coord is None:
537
+ continue
538
+ if not self._load_tile(fh, t):
539
+ missing = True
540
+
541
+ return not missing
542
+
543
+ def is_cached(self, tile, dimensions=None):
544
+ with self._readonly() as fh:
545
+ if not fh:
546
+ return False
547
+
548
+ x, y = self._rel_tile_coord(tile.coord)
549
+ _, size = self._tile_offset_size(fh, x, y)
550
+ if not size:
551
+ return False
552
+ return True
553
+
554
+ def _update_tile_offset(self, fh, x, y, offset, size):
555
+ idx_offset = self._tile_idx_offset(x, y)
556
+ val = offset + (size << 40)
557
+
558
+ fh.seek(idx_offset, os.SEEK_SET)
559
+ fh.write(INT64LE.pack(val))
560
+
561
+ def _append_tile(self, fh, data):
562
+ # Write tile size first, then tile data.
563
+ # Offset points to actual tile data.
564
+ fh.seek(0, os.SEEK_END)
565
+ fh.write(struct.pack('<L', len(data)))
566
+ offset = fh.tell()
567
+ fh.write(data)
568
+ return offset
569
+
570
+ def _update_metadata(self, fh, filesize, tilesize):
571
+ # Max record/tile size
572
+ fh.seek(8)
573
+ old_tilesize = struct.unpack('<I', fh.read(4))[0]
574
+ if tilesize > old_tilesize:
575
+ fh.seek(8)
576
+ fh.write(struct.pack('<I', tilesize))
577
+
578
+ # Complete file size
579
+ fh.seek(24)
580
+ fh.write(struct.pack("<Q", filesize))
581
+
582
+ def _store_tile(self, fh, tile_coord, data, dimensions=None):
583
+ size = len(data)
584
+ x, y = self._rel_tile_coord(tile_coord)
585
+ offset = self._append_tile(fh, data)
586
+ self._update_tile_offset(fh, x, y, offset, size)
587
+
588
+ filesize = offset + size
589
+ self._update_metadata(fh, filesize, size)
590
+
591
+ def store_tile(self, tile, dimensions=None):
592
+ if tile.stored:
593
+ return True
594
+
595
+ return self.store_tiles([tile], dimensions=dimensions)
596
+
597
+ def store_tiles(self, tiles, dimensions=None):
598
+ self._init_index()
599
+
600
+ tiles_data = []
601
+ for t in tiles:
602
+ if t.stored:
603
+ continue
604
+ with tile_buffer(t) as buf:
605
+ data = buf.read()
606
+ tiles_data.append((t.coord, data))
607
+
608
+ with FileLock(self.lock_filename):
609
+ with self._readwrite() as fh:
610
+ for tile_coord, data in tiles_data:
611
+ self._store_tile(fh, tile_coord, data, dimensions=dimensions)
612
+
613
+ return True
614
+
615
+ def remove_tile(self, tile, dimensions=None):
616
+ if tile.coord is None:
617
+ return True
618
+
619
+ self._init_index()
620
+ with FileLock(self.lock_filename):
621
+ with self._readwrite() as fh:
622
+ x, y = self._rel_tile_coord(tile.coord)
623
+ self._update_tile_offset(fh, x, y, 0, 0)
624
+
625
+ return True
626
+
627
+ def size(self):
628
+ total_size = 0
629
+ with self._readonly() as fh:
630
+ if not fh:
631
+ return 0, 0
632
+ for y in range(BUNDLE_V2_GRID_HEIGHT):
633
+ for x in range(BUNDLE_V2_GRID_WIDTH):
634
+ _, size = self._tile_offset_size(fh, x, y)
635
+ if size:
636
+ total_size += size + 4
637
+ fh.seek(0, os.SEEK_END)
638
+ actual_size = fh.tell()
639
+ return total_size + 64 + BUNDLE_V2_INDEX_SIZE, actual_size
640
+
641
+ @contextlib.contextmanager
642
+ def _readonly(self):
643
+ try:
644
+ with open(self.filename, 'rb') as fh:
645
+ yield fh
646
+ except IOError as ex:
647
+ if ex.errno == errno.ENOENT:
648
+ # missing bundle file -> missing tile
649
+ yield None
650
+ else:
651
+ raise ex
652
+
653
+ @contextlib.contextmanager
654
+ def _readwrite(self):
655
+ self._init_index()
656
+ with open(self.filename, 'r+b') as fh:
657
+ yield fh
658
+
659
+
660
+ class CompactCacheV1(CompactCacheBase):
661
+ bundle_class = BundleV1
662
+
663
+
664
+ class CompactCacheV2(CompactCacheBase):
665
+ bundle_class = BundleV2