MapProxy 1.16.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. mapproxy/__init__.py +0 -0
  2. mapproxy/cache/__init__.py +36 -0
  3. mapproxy/cache/azureblob.py +145 -0
  4. mapproxy/cache/base.py +111 -0
  5. mapproxy/cache/compact.py +664 -0
  6. mapproxy/cache/couchdb.py +295 -0
  7. mapproxy/cache/dummy.py +34 -0
  8. mapproxy/cache/file.py +185 -0
  9. mapproxy/cache/geopackage.py +609 -0
  10. mapproxy/cache/legend.py +83 -0
  11. mapproxy/cache/mbtiles.py +392 -0
  12. mapproxy/cache/meta.py +78 -0
  13. mapproxy/cache/path.py +250 -0
  14. mapproxy/cache/redis.py +88 -0
  15. mapproxy/cache/renderd.py +95 -0
  16. mapproxy/cache/riak.py +202 -0
  17. mapproxy/cache/s3.py +177 -0
  18. mapproxy/cache/tile.py +699 -0
  19. mapproxy/client/__init__.py +0 -0
  20. mapproxy/client/arcgis.py +79 -0
  21. mapproxy/client/cgi.py +139 -0
  22. mapproxy/client/http.py +315 -0
  23. mapproxy/client/log.py +33 -0
  24. mapproxy/client/tile.py +150 -0
  25. mapproxy/client/wms.py +254 -0
  26. mapproxy/compat/__init__.py +46 -0
  27. mapproxy/compat/image.py +79 -0
  28. mapproxy/compat/itertools.py +29 -0
  29. mapproxy/compat/modules.py +13 -0
  30. mapproxy/config/__init__.py +22 -0
  31. mapproxy/config/config.py +201 -0
  32. mapproxy/config/coverage.py +107 -0
  33. mapproxy/config/defaults.py +98 -0
  34. mapproxy/config/loader.py +2286 -0
  35. mapproxy/config/spec.py +644 -0
  36. mapproxy/config/validator.py +239 -0
  37. mapproxy/config_template/__init__.py +0 -0
  38. mapproxy/config_template/base_config/config.wsgi +10 -0
  39. mapproxy/config_template/base_config/full_example.yaml +593 -0
  40. mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
  41. mapproxy/config_template/base_config/log.ini +35 -0
  42. mapproxy/config_template/base_config/mapproxy.yaml +60 -0
  43. mapproxy/config_template/base_config/seed.yaml +27 -0
  44. mapproxy/exception.py +142 -0
  45. mapproxy/featureinfo.py +252 -0
  46. mapproxy/grid.py +1170 -0
  47. mapproxy/image/__init__.py +536 -0
  48. mapproxy/image/fonts/DejaVuSans.ttf +0 -0
  49. mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
  50. mapproxy/image/fonts/LICENSE +99 -0
  51. mapproxy/image/fonts/__init__.py +0 -0
  52. mapproxy/image/mask.py +75 -0
  53. mapproxy/image/merge.py +316 -0
  54. mapproxy/image/message.py +347 -0
  55. mapproxy/image/opts.py +182 -0
  56. mapproxy/image/tile.py +167 -0
  57. mapproxy/image/transform.py +350 -0
  58. mapproxy/layer.py +470 -0
  59. mapproxy/multiapp.py +231 -0
  60. mapproxy/proj.py +302 -0
  61. mapproxy/request/__init__.py +18 -0
  62. mapproxy/request/arcgis.py +259 -0
  63. mapproxy/request/base.py +476 -0
  64. mapproxy/request/tile.py +128 -0
  65. mapproxy/request/wms/__init__.py +793 -0
  66. mapproxy/request/wms/exception.py +99 -0
  67. mapproxy/request/wmts.py +436 -0
  68. mapproxy/response.py +237 -0
  69. mapproxy/script/__init__.py +0 -0
  70. mapproxy/script/conf/__init__.py +0 -0
  71. mapproxy/script/conf/app.py +195 -0
  72. mapproxy/script/conf/caches.py +45 -0
  73. mapproxy/script/conf/layers.py +54 -0
  74. mapproxy/script/conf/seeds.py +37 -0
  75. mapproxy/script/conf/sources.py +86 -0
  76. mapproxy/script/conf/utils.py +143 -0
  77. mapproxy/script/defrag.py +184 -0
  78. mapproxy/script/export.py +333 -0
  79. mapproxy/script/grids.py +188 -0
  80. mapproxy/script/scales.py +126 -0
  81. mapproxy/script/util.py +406 -0
  82. mapproxy/script/wms_capabilities.py +152 -0
  83. mapproxy/seed/__init__.py +0 -0
  84. mapproxy/seed/cachelock.py +121 -0
  85. mapproxy/seed/cleanup.py +187 -0
  86. mapproxy/seed/config.py +469 -0
  87. mapproxy/seed/script.py +388 -0
  88. mapproxy/seed/seeder.py +538 -0
  89. mapproxy/seed/spec.py +64 -0
  90. mapproxy/seed/util.py +254 -0
  91. mapproxy/service/__init__.py +14 -0
  92. mapproxy/service/base.py +46 -0
  93. mapproxy/service/demo.py +356 -0
  94. mapproxy/service/kml.py +331 -0
  95. mapproxy/service/ows.py +38 -0
  96. mapproxy/service/template_helper.py +53 -0
  97. mapproxy/service/templates/demo/capabilities_demo.html +16 -0
  98. mapproxy/service/templates/demo/demo.html +181 -0
  99. mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
  100. mapproxy/service/templates/demo/static/img/blank.gif +0 -0
  101. mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
  102. mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
  103. mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
  104. mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
  105. mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
  106. mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
  107. mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
  108. mapproxy/service/templates/demo/static/logo.png +0 -0
  109. mapproxy/service/templates/demo/static/ol.css +345 -0
  110. mapproxy/service/templates/demo/static/ol.js +4 -0
  111. mapproxy/service/templates/demo/static/proj4.min.js +1 -0
  112. mapproxy/service/templates/demo/static/proj4defs.js +1 -0
  113. mapproxy/service/templates/demo/static/site.css +137 -0
  114. mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
  115. mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
  116. mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
  117. mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
  118. mapproxy/service/templates/demo/static.html +34 -0
  119. mapproxy/service/templates/demo/tms_demo.html +103 -0
  120. mapproxy/service/templates/demo/wms_demo.html +140 -0
  121. mapproxy/service/templates/demo/wmts_demo.html +110 -0
  122. mapproxy/service/templates/tms_capabilities.xml +13 -0
  123. mapproxy/service/templates/tms_exception.xml +4 -0
  124. mapproxy/service/templates/tms_root_resource.xml +7 -0
  125. mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
  126. mapproxy/service/templates/wms100capabilities.xml +112 -0
  127. mapproxy/service/templates/wms100exception.xml +4 -0
  128. mapproxy/service/templates/wms110capabilities.xml +152 -0
  129. mapproxy/service/templates/wms110exception.xml +5 -0
  130. mapproxy/service/templates/wms111capabilities.xml +183 -0
  131. mapproxy/service/templates/wms111exception.xml +5 -0
  132. mapproxy/service/templates/wms130capabilities.xml +326 -0
  133. mapproxy/service/templates/wms130exception.xml +8 -0
  134. mapproxy/service/templates/wmts100capabilities.xml +155 -0
  135. mapproxy/service/templates/wmts100exception.xml +9 -0
  136. mapproxy/service/tile.py +536 -0
  137. mapproxy/service/wms.py +851 -0
  138. mapproxy/service/wmts.py +381 -0
  139. mapproxy/source/__init__.py +75 -0
  140. mapproxy/source/arcgis.py +39 -0
  141. mapproxy/source/error.py +39 -0
  142. mapproxy/source/mapnik.py +259 -0
  143. mapproxy/source/tile.py +96 -0
  144. mapproxy/source/wms.py +270 -0
  145. mapproxy/srs.py +726 -0
  146. mapproxy/template.py +54 -0
  147. mapproxy/test/__init__.py +0 -0
  148. mapproxy/test/conftest.py +7 -0
  149. mapproxy/test/helper.py +247 -0
  150. mapproxy/test/http.py +494 -0
  151. mapproxy/test/image.py +210 -0
  152. mapproxy/test/mocker.py +2268 -0
  153. mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
  154. mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
  155. mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
  156. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
  157. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
  158. mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
  159. mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
  160. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
  161. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
  162. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
  163. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
  164. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
  165. mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
  166. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
  167. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
  168. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
  169. mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
  170. mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
  171. mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
  172. mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
  173. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
  174. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
  175. mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
  176. mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
  177. mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
  178. mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
  179. mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
  180. mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
  181. mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
  182. mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
  183. mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
  184. mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
  185. mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
  186. mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
  187. mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
  188. mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
  189. mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
  190. mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
  191. mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
  192. mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
  193. mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
  194. mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
  195. mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
  196. mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
  197. mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
  198. mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
  199. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
  200. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
  201. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
  202. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
  203. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
  204. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
  205. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
  206. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
  207. mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
  208. mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  209. mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
  210. mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  211. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
  212. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
  213. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
  214. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
  215. mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
  216. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
  217. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
  218. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
  219. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
  220. mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
  221. mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  222. mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
  223. mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  224. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
  225. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
  226. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
  227. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
  228. mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
  229. mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
  230. mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
  231. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
  232. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
  233. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
  234. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
  235. mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
  236. mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
  237. mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
  238. mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
  239. mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
  240. mapproxy/test/schemas/xml.xsd +287 -0
  241. mapproxy/test/system/__init__.py +98 -0
  242. mapproxy/test/system/fixture/arcgis.yaml +57 -0
  243. mapproxy/test/system/fixture/auth.yaml +70 -0
  244. mapproxy/test/system/fixture/cache.mbtiles +0 -0
  245. mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
  246. mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
  247. mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
  248. mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
  249. mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
  250. mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
  251. mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
  252. mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
  253. mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
  254. mapproxy/test/system/fixture/cache_s3.yaml +58 -0
  255. mapproxy/test/system/fixture/cache_source.yaml +81 -0
  256. mapproxy/test/system/fixture/combined_sources.yaml +130 -0
  257. mapproxy/test/system/fixture/coverage.yaml +77 -0
  258. mapproxy/test/system/fixture/demo.yaml +135 -0
  259. mapproxy/test/system/fixture/dimension.yaml +59 -0
  260. mapproxy/test/system/fixture/disable_storage.yaml +25 -0
  261. mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
  262. mapproxy/test/system/fixture/formats.yaml +72 -0
  263. mapproxy/test/system/fixture/inspire.yaml +101 -0
  264. mapproxy/test/system/fixture/inspire_full.yaml +124 -0
  265. mapproxy/test/system/fixture/kml_layer.yaml +66 -0
  266. mapproxy/test/system/fixture/layer.yaml +260 -0
  267. mapproxy/test/system/fixture/layergroups.yaml +57 -0
  268. mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
  269. mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
  270. mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
  271. mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
  272. mapproxy/test/system/fixture/mapserver.yaml +23 -0
  273. mapproxy/test/system/fixture/minimal_cgi.py +16 -0
  274. mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
  275. mapproxy/test/system/fixture/multi_cache_layers.yaml +100 -0
  276. mapproxy/test/system/fixture/multiapp1.yaml +20 -0
  277. mapproxy/test/system/fixture/multiapp2.yaml +19 -0
  278. mapproxy/test/system/fixture/renderd_client.yaml +55 -0
  279. mapproxy/test/system/fixture/scalehints.yaml +70 -0
  280. mapproxy/test/system/fixture/seed.yaml +94 -0
  281. mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
  282. mapproxy/test/system/fixture/seed_old.yaml +12 -0
  283. mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
  284. mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
  285. mapproxy/test/system/fixture/seedonly.yaml +51 -0
  286. mapproxy/test/system/fixture/sld.yaml +35 -0
  287. mapproxy/test/system/fixture/source_errors.yaml +84 -0
  288. mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
  289. mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
  290. mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
  291. mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
  292. mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
  293. mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
  294. mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
  295. mapproxy/test/system/fixture/util_grids.yaml +30 -0
  296. mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
  297. mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
  298. mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
  299. mapproxy/test/system/fixture/watermark.yaml +50 -0
  300. mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
  301. mapproxy/test/system/fixture/wms_versions.yaml +38 -0
  302. mapproxy/test/system/fixture/wmts.yaml +134 -0
  303. mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
  304. mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
  305. mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
  306. mapproxy/test/system/test_arcgis.py +156 -0
  307. mapproxy/test/system/test_auth.py +1134 -0
  308. mapproxy/test/system/test_behind_proxy.py +75 -0
  309. mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
  310. mapproxy/test/system/test_cache_azureblob.py +127 -0
  311. mapproxy/test/system/test_cache_band_merge.py +103 -0
  312. mapproxy/test/system/test_cache_geopackage.py +144 -0
  313. mapproxy/test/system/test_cache_grid_names.py +89 -0
  314. mapproxy/test/system/test_cache_mbtiles.py +85 -0
  315. mapproxy/test/system/test_cache_s3.py +115 -0
  316. mapproxy/test/system/test_cache_source.py +146 -0
  317. mapproxy/test/system/test_combined_sources.py +335 -0
  318. mapproxy/test/system/test_coverage.py +140 -0
  319. mapproxy/test/system/test_decorate_img.py +214 -0
  320. mapproxy/test/system/test_demo.py +106 -0
  321. mapproxy/test/system/test_demo_with_extra_service.py +53 -0
  322. mapproxy/test/system/test_dimensions.py +278 -0
  323. mapproxy/test/system/test_disable_storage.py +42 -0
  324. mapproxy/test/system/test_formats.py +219 -0
  325. mapproxy/test/system/test_inspire_vs.py +173 -0
  326. mapproxy/test/system/test_kml.py +262 -0
  327. mapproxy/test/system/test_layergroups.py +160 -0
  328. mapproxy/test/system/test_legendgraphic.py +308 -0
  329. mapproxy/test/system/test_mapnik.py +161 -0
  330. mapproxy/test/system/test_mapserver.py +81 -0
  331. mapproxy/test/system/test_mixed_mode_format.py +195 -0
  332. mapproxy/test/system/test_multi_cache_layers.py +167 -0
  333. mapproxy/test/system/test_multiapp.py +92 -0
  334. mapproxy/test/system/test_refresh.py +207 -0
  335. mapproxy/test/system/test_renderd_client.py +304 -0
  336. mapproxy/test/system/test_response_headers.py +54 -0
  337. mapproxy/test/system/test_scalehints.py +140 -0
  338. mapproxy/test/system/test_seed.py +422 -0
  339. mapproxy/test/system/test_seed_only.py +93 -0
  340. mapproxy/test/system/test_sld.py +120 -0
  341. mapproxy/test/system/test_source_errors.py +377 -0
  342. mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
  343. mapproxy/test/system/test_tms.py +276 -0
  344. mapproxy/test/system/test_tms_origin.py +46 -0
  345. mapproxy/test/system/test_util_conf.py +304 -0
  346. mapproxy/test/system/test_util_export.py +210 -0
  347. mapproxy/test/system/test_util_grids.py +88 -0
  348. mapproxy/test/system/test_util_wms_capabilities.py +182 -0
  349. mapproxy/test/system/test_watermark.py +91 -0
  350. mapproxy/test/system/test_wms.py +1611 -0
  351. mapproxy/test/system/test_wms_srs_extent.py +165 -0
  352. mapproxy/test/system/test_wms_version.py +85 -0
  353. mapproxy/test/system/test_wmsc.py +116 -0
  354. mapproxy/test/system/test_wmts.py +334 -0
  355. mapproxy/test/system/test_wmts_dimensions.py +206 -0
  356. mapproxy/test/system/test_wmts_restful.py +198 -0
  357. mapproxy/test/system/test_xslt_featureinfo.py +425 -0
  358. mapproxy/test/test_http_helper.py +219 -0
  359. mapproxy/test/unit/__init__.py +0 -0
  360. mapproxy/test/unit/epsg +2 -0
  361. mapproxy/test/unit/polygons/polygons.dbf +0 -0
  362. mapproxy/test/unit/polygons/polygons.shp +0 -0
  363. mapproxy/test/unit/polygons/polygons.shx +0 -0
  364. mapproxy/test/unit/test_async.py +245 -0
  365. mapproxy/test/unit/test_auth.py +419 -0
  366. mapproxy/test/unit/test_cache.py +1193 -0
  367. mapproxy/test/unit/test_cache_azureblob.py +94 -0
  368. mapproxy/test/unit/test_cache_compact.py +319 -0
  369. mapproxy/test/unit/test_cache_couchdb.py +114 -0
  370. mapproxy/test/unit/test_cache_geopackage.py +221 -0
  371. mapproxy/test/unit/test_cache_redis.py +67 -0
  372. mapproxy/test/unit/test_cache_riak.py +76 -0
  373. mapproxy/test/unit/test_cache_s3.py +84 -0
  374. mapproxy/test/unit/test_cache_tile.py +427 -0
  375. mapproxy/test/unit/test_client.py +479 -0
  376. mapproxy/test/unit/test_client_arcgis.py +73 -0
  377. mapproxy/test/unit/test_client_cgi.py +136 -0
  378. mapproxy/test/unit/test_collections.py +116 -0
  379. mapproxy/test/unit/test_concat_legends.py +37 -0
  380. mapproxy/test/unit/test_conf_loader.py +1061 -0
  381. mapproxy/test/unit/test_conf_validator.py +416 -0
  382. mapproxy/test/unit/test_config.py +117 -0
  383. mapproxy/test/unit/test_decorate_img.py +185 -0
  384. mapproxy/test/unit/test_exceptions.py +258 -0
  385. mapproxy/test/unit/test_featureinfo.py +291 -0
  386. mapproxy/test/unit/test_file_lock_load.py +49 -0
  387. mapproxy/test/unit/test_geom.py +503 -0
  388. mapproxy/test/unit/test_grid.py +1258 -0
  389. mapproxy/test/unit/test_image.py +1053 -0
  390. mapproxy/test/unit/test_image_mask.py +181 -0
  391. mapproxy/test/unit/test_image_messages.py +197 -0
  392. mapproxy/test/unit/test_image_options.py +160 -0
  393. mapproxy/test/unit/test_isodate.py +122 -0
  394. mapproxy/test/unit/test_multiapp.py +163 -0
  395. mapproxy/test/unit/test_ogr_reader.py +50 -0
  396. mapproxy/test/unit/test_request.py +745 -0
  397. mapproxy/test/unit/test_request_wmts.py +178 -0
  398. mapproxy/test/unit/test_response.py +79 -0
  399. mapproxy/test/unit/test_seed.py +365 -0
  400. mapproxy/test/unit/test_seed_cachelock.py +90 -0
  401. mapproxy/test/unit/test_srs.py +215 -0
  402. mapproxy/test/unit/test_tiled_source.py +122 -0
  403. mapproxy/test/unit/test_tilefilter.py +31 -0
  404. mapproxy/test/unit/test_times.py +25 -0
  405. mapproxy/test/unit/test_timeutils.py +50 -0
  406. mapproxy/test/unit/test_util_conf_utils.py +75 -0
  407. mapproxy/test/unit/test_utils.py +476 -0
  408. mapproxy/test/unit/test_wms_capabilities.py +44 -0
  409. mapproxy/test/unit/test_wms_layer.py +113 -0
  410. mapproxy/test/unit/test_yaml.py +69 -0
  411. mapproxy/tilefilter.py +59 -0
  412. mapproxy/util/__init__.py +0 -0
  413. mapproxy/util/async_.py +227 -0
  414. mapproxy/util/collections.py +132 -0
  415. mapproxy/util/coverage.py +329 -0
  416. mapproxy/util/escape.py +10 -0
  417. mapproxy/util/ext/__init__.py +14 -0
  418. mapproxy/util/ext/dictspec/__init__.py +1 -0
  419. mapproxy/util/ext/dictspec/spec.py +124 -0
  420. mapproxy/util/ext/dictspec/test/__init__.py +0 -0
  421. mapproxy/util/ext/dictspec/test/test_validator.py +274 -0
  422. mapproxy/util/ext/dictspec/validator.py +189 -0
  423. mapproxy/util/ext/local.py +196 -0
  424. mapproxy/util/ext/lockfile.py +138 -0
  425. mapproxy/util/ext/odict.py +330 -0
  426. mapproxy/util/ext/serving.py +508 -0
  427. mapproxy/util/ext/tempita/__init__.py +1174 -0
  428. mapproxy/util/ext/tempita/_looper.py +163 -0
  429. mapproxy/util/ext/tempita/compat3.py +46 -0
  430. mapproxy/util/ext/wmsparse/__init__.py +3 -0
  431. mapproxy/util/ext/wmsparse/duration.py +597 -0
  432. mapproxy/util/ext/wmsparse/parse.py +305 -0
  433. mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
  434. mapproxy/util/ext/wmsparse/test/test_parse.py +162 -0
  435. mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
  436. mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
  437. mapproxy/util/ext/wmsparse/test/wms-omniscale-111.xml +90 -0
  438. mapproxy/util/ext/wmsparse/test/wms-omniscale-130.xml +120 -0
  439. mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
  440. mapproxy/util/ext/wmsparse/util.py +187 -0
  441. mapproxy/util/fs.py +156 -0
  442. mapproxy/util/geom.py +295 -0
  443. mapproxy/util/lib.py +115 -0
  444. mapproxy/util/lock.py +163 -0
  445. mapproxy/util/ogr.py +231 -0
  446. mapproxy/util/py.py +81 -0
  447. mapproxy/util/times.py +75 -0
  448. mapproxy/util/yaml.py +56 -0
  449. mapproxy/version.py +31 -0
  450. mapproxy/wsgiapp.py +164 -0
  451. mapproxy-1.16.1.dist-info/METADATA +151 -0
  452. mapproxy-1.16.1.dist-info/RECORD +458 -0
  453. mapproxy-1.16.1.dist-info/WHEEL +5 -0
  454. mapproxy-1.16.1.dist-info/entry_points.txt +3 -0
  455. mapproxy-1.16.1.dist-info/licenses/AUTHORS.txt +33 -0
  456. mapproxy-1.16.1.dist-info/licenses/COPYING.txt +60 -0
  457. mapproxy-1.16.1.dist-info/licenses/LICENSE.txt +202 -0
  458. mapproxy-1.16.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,664 @@
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
+
23
+ from mapproxy.image import ImageSource
24
+ from mapproxy.cache.base import TileCacheBase, tile_buffer
25
+ from mapproxy.util.fs import ensure_directory, write_atomic
26
+ from mapproxy.util.lock import FileLock
27
+ from mapproxy.compat import BytesIO
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):
38
+ self.lock_cache_id = 'compactcache-' + hashlib.md5(cache_dir.encode('utf-8')).hexdigest()
39
+ self.cache_dir = cache_dir
40
+
41
+ def _get_bundle_fname_and_offset(self, tile_coord):
42
+ x, y, z = tile_coord
43
+
44
+ level_dir = os.path.join(self.cache_dir, 'L%02d' % z)
45
+
46
+ c = x // BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_WIDTH
47
+ r = y // BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_HEIGHT
48
+
49
+ basename = 'R%04xC%04x' % (r, c)
50
+ return os.path.join(level_dir, basename), (c, r)
51
+
52
+ def _get_bundle(self, tile_coord):
53
+ bundle_fname, offset = self._get_bundle_fname_and_offset(tile_coord)
54
+ return self.bundle_class(bundle_fname, offset=offset)
55
+
56
+ def is_cached(self, tile, dimensions=None):
57
+ if tile.coord is None:
58
+ return True
59
+ if tile.source:
60
+ return True
61
+
62
+ return self._get_bundle(tile.coord).is_cached(tile, dimensions=dimensions)
63
+
64
+ def store_tile(self, tile, dimensions=None):
65
+ if tile.stored:
66
+ return True
67
+
68
+ return self._get_bundle(tile.coord).store_tile(tile, dimensions=dimensions)
69
+
70
+ def store_tiles(self, tiles, dimensions=None):
71
+ if len(tiles) > 1:
72
+ # Check if all tiles are from a single bundle.
73
+ bundle_files = set()
74
+ tile_coord = None
75
+ for t in tiles:
76
+ if t.stored:
77
+ continue
78
+ bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
79
+ tile_coord = t.coord
80
+ if len(bundle_files) == 1:
81
+ return self._get_bundle(tile_coord).store_tiles(tiles, dimensions=dimensions)
82
+
83
+ # Tiles are across multiple bundles
84
+ failed = False
85
+ for tile in tiles:
86
+ if not self.store_tile(tile, dimensions=dimensions):
87
+ failed = True
88
+ return not failed
89
+
90
+
91
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
92
+ if tile.source or tile.coord is None:
93
+ return True
94
+
95
+ return self._get_bundle(tile.coord).load_tile(tile, dimensions=dimensions)
96
+
97
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
98
+ if len(tiles) > 1:
99
+ # Check if all tiles are from a single bundle.
100
+ bundle_files = set()
101
+ tile_coord = None
102
+ for t in tiles:
103
+ if t.source or t.coord is None:
104
+ continue
105
+ bundle_files.add(self._get_bundle_fname_and_offset(t.coord)[0])
106
+ tile_coord = t.coord
107
+ if len(bundle_files) == 1:
108
+ return self._get_bundle(tile_coord).load_tiles(tiles, dimensions=dimensions)
109
+
110
+ # No support_bulk_load or tiles are across multiple bundles
111
+ missing = False
112
+ for tile in tiles:
113
+ if not self.load_tile(tile, with_metadata=with_metadata, dimensions=dimensions):
114
+ missing = True
115
+ return not missing
116
+
117
+ def remove_tile(self, tile, dimensions=None):
118
+ if tile.coord is None:
119
+ return True
120
+
121
+ return self._get_bundle(tile.coord).remove_tile(tile, dimensions=dimensions)
122
+
123
+ def load_tile_metadata(self, tile, dimensions=None):
124
+ if self.load_tile(tile, dimensions=dimensions):
125
+ tile.timestamp = -1
126
+
127
+ def remove_level_tiles_before(self, level, timestamp):
128
+ if timestamp == 0:
129
+ level_dir = os.path.join(self.cache_dir, 'L%02d' % level)
130
+ shutil.rmtree(level_dir, ignore_errors=True)
131
+ return True
132
+ return False
133
+
134
+ BUNDLE_EXT = '.bundle'
135
+ BUNDLEX_V1_EXT = '.bundlx'
136
+
137
+ class BundleV1(object):
138
+ def __init__(self, base_filename, offset):
139
+ self.base_filename = base_filename
140
+ self.lock_filename = base_filename + '.lck'
141
+ self.offset = offset
142
+
143
+ def _rel_tile_coord(self, tile_coord):
144
+ return (
145
+ tile_coord[0] % BUNDLEX_V1_GRID_WIDTH,
146
+ tile_coord[1] % BUNDLEX_V1_GRID_HEIGHT,
147
+ )
148
+
149
+ def data(self):
150
+ return BundleDataV1(self.base_filename + BUNDLE_EXT, self.offset)
151
+
152
+ def index(self):
153
+ return BundleIndexV1(self.base_filename + BUNDLEX_V1_EXT)
154
+
155
+ def is_cached(self, tile, dimensions=None):
156
+ if tile.source or tile.coord is None:
157
+ return True
158
+
159
+ with self.index().readonly() as idx:
160
+ if not idx:
161
+ return False
162
+ x, y = self._rel_tile_coord(tile.coord)
163
+ offset = idx.tile_offset(x, y)
164
+ if offset == 0:
165
+ return False
166
+
167
+ with self.data().readonly() as bundle:
168
+ size = bundle.read_size(offset)
169
+ return size != 0
170
+
171
+ def store_tile(self, tile, dimensions=None):
172
+ if tile.stored:
173
+ return True
174
+ return self.store_tiles([tile], dimensions=dimensions)
175
+
176
+ def store_tiles(self, tiles, dimensions=None):
177
+ tiles_data = []
178
+ for t in tiles:
179
+ if t.stored:
180
+ continue
181
+ with tile_buffer(t) as buf:
182
+ data = buf.read()
183
+ tiles_data.append((t.coord, data))
184
+
185
+ with FileLock(self.lock_filename):
186
+ with self.data().readwrite() as bundle:
187
+ with self.index().readwrite() as idx:
188
+ for tile_coord, data in tiles_data:
189
+ x, y = self._rel_tile_coord(tile_coord)
190
+ offset = idx.tile_offset(x, y)
191
+ offset, size = bundle.append_tile(data, prev_offset=offset)
192
+ idx.update_tile_offset(x, y, offset=offset, size=size)
193
+
194
+ return True
195
+
196
+
197
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
198
+ if tile.source or tile.coord is None:
199
+ return True
200
+ return self.load_tiles([tile], with_metadata, dimensions=dimensions)
201
+
202
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
203
+ missing = False
204
+
205
+ with self.index().readonly() as idx:
206
+ if not idx:
207
+ return False
208
+ with self.data().readonly() as bundle:
209
+ for t in tiles:
210
+ if t.source or t.coord is None:
211
+ continue
212
+ x, y = self._rel_tile_coord(t.coord)
213
+ offset = idx.tile_offset(x, y)
214
+ if offset == 0:
215
+ missing = True
216
+ continue
217
+
218
+ data = bundle.read_tile(offset)
219
+ if not data:
220
+ missing = True
221
+ continue
222
+ t.source = ImageSource(BytesIO(data))
223
+
224
+ return not missing
225
+
226
+ def remove_tile(self, tile, dimensions=None):
227
+ if tile.coord is None:
228
+ return True
229
+
230
+ with FileLock(self.lock_filename):
231
+ with self.index().readwrite() as idx:
232
+ x, y = self._rel_tile_coord(tile.coord)
233
+ idx.remove_tile_offset(x, y)
234
+
235
+ return True
236
+
237
+ def size(self):
238
+ total_size = 0
239
+
240
+ with self.index().readonly() as idx:
241
+ if not idx:
242
+ return 0, 0
243
+
244
+ with self.data().readonly() as bundle:
245
+ for y in range(BUNDLEX_V1_GRID_HEIGHT):
246
+ for x in range(BUNDLEX_V1_GRID_WIDTH):
247
+ offset = idx.tile_offset(x, y)
248
+ if not offset:
249
+ continue
250
+ size = bundle.read_size(offset)
251
+ if not size:
252
+ continue
253
+ total_size += size + 4
254
+
255
+ actual_size = os.path.getsize(bundle.filename)
256
+ return total_size + BUNDLE_V1_HEADER_SIZE + (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4), actual_size
257
+
258
+
259
+ BUNDLEX_V1_GRID_WIDTH = 128
260
+ BUNDLEX_V1_GRID_HEIGHT = 128
261
+ BUNDLEX_V1_HEADER_SIZE = 16
262
+ BUNDLEX_V1_HEADER = b'\x03\x00\x00\x00\x10\x00\x00\x00\x00\x40\x00\x00\x05\x00\x00\x00'
263
+ BUNDLEX_V1_FOOTER_SIZE = 16
264
+ BUNDLEX_V1_FOOTER = b'\x00\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00'
265
+
266
+ INT64LE = struct.Struct('<Q')
267
+
268
+ class BundleIndexV1(object):
269
+ def __init__(self, filename):
270
+ self.filename = filename
271
+ self._fh = None
272
+ # defer initialization to update/remove calls to avoid
273
+ # index creation on is_cached (prevents new files in read-only caches)
274
+ self._initialized = False
275
+
276
+ def _init_index(self):
277
+ self._initialized = True
278
+ if os.path.exists(self.filename):
279
+ return
280
+ ensure_directory(self.filename)
281
+ buf = BytesIO()
282
+ buf.write(BUNDLEX_V1_HEADER)
283
+
284
+ for i in range(BUNDLEX_V1_GRID_WIDTH * BUNDLEX_V1_GRID_HEIGHT):
285
+ buf.write(INT64LE.pack((i*4)+BUNDLE_V1_HEADER_SIZE)[:5])
286
+ buf.write(BUNDLEX_V1_FOOTER)
287
+ write_atomic(self.filename, buf.getvalue())
288
+
289
+ def _tile_index_offset(self, x, y):
290
+ return BUNDLEX_V1_HEADER_SIZE + (x * BUNDLEX_V1_GRID_HEIGHT + y) * 5
291
+
292
+ def tile_offset(self, x, y):
293
+ if self._fh is None:
294
+ raise RuntimeError('not called within readonly/readwrite context')
295
+ idx_offset = self._tile_index_offset(x, y)
296
+ self._fh.seek(idx_offset)
297
+ offset = INT64LE.unpack(self._fh.read(5) + b'\x00\x00\x00')[0]
298
+ return offset
299
+
300
+ def update_tile_offset(self, x, y, offset, size):
301
+ if self._fh is None:
302
+ raise RuntimeError('not called within readwrite context')
303
+ idx_offset = self._tile_index_offset(x, y)
304
+ offset = INT64LE.pack(offset)[:5]
305
+ self._fh.seek(idx_offset, os.SEEK_SET)
306
+ self._fh.write(offset)
307
+
308
+ def remove_tile_offset(self, x, y):
309
+ if self._fh is None:
310
+ raise RuntimeError('not called within readwrite context')
311
+ idx_offset = self._tile_index_offset(x, y)
312
+ self._fh.seek(idx_offset)
313
+ self._fh.write(b'\x00' * 5)
314
+
315
+ @contextlib.contextmanager
316
+ def readonly(self):
317
+ try:
318
+ with open(self.filename, 'rb') as fh:
319
+ b = BundleIndexV1(self.filename)
320
+ b._fh = fh
321
+ yield b
322
+ except IOError as ex:
323
+ if ex.errno == errno.ENOENT:
324
+ # missing bundle file -> missing tile
325
+ yield None
326
+ else:
327
+ raise ex
328
+
329
+ @contextlib.contextmanager
330
+ def readwrite(self):
331
+ self._init_index()
332
+ with open(self.filename, 'r+b') as fh:
333
+ b = BundleIndexV1(self.filename)
334
+ b._fh = fh
335
+ yield b
336
+
337
+
338
+
339
+ # The bundle file has a header with 15 little-endian long values (60 bytes).
340
+ # NOTE: the fixed values might be some flags for image options (format, aliasing)
341
+ # all files available for testing had the same values however.
342
+ BUNDLE_V1_HEADER_SIZE = 60
343
+ BUNDLE_V1_HEADER = [
344
+ 3 , # 0, fixed
345
+ 16384 , # 1, max. num of tiles 128*128 = 16384
346
+ 16 , # 2, size of largest tile
347
+ 5 , # 3, fixed
348
+ 0 , # 4, num of tiles in bundle (*4)
349
+ 60+65536 , # 5, bundle size
350
+ 40 , # 6 fixed
351
+ 16 , # 7, fixed
352
+ 0 , # 8, y0
353
+ 127 , # 9, y1
354
+ 0 , # 10, x0
355
+ 127 , # 11, x1
356
+ ]
357
+ BUNDLE_V1_HEADER_STRUCT_FORMAT = '<4I3Q5I'
358
+
359
+ class BundleDataV1(object):
360
+ def __init__(self, filename, tile_offsets):
361
+ self.filename = filename
362
+ self.tile_offsets = tile_offsets
363
+ self._fh = None
364
+ if not os.path.exists(self.filename):
365
+ self._init_bundle()
366
+
367
+ def _init_bundle(self):
368
+ ensure_directory(self.filename)
369
+ header = list(BUNDLE_V1_HEADER)
370
+ header[10], header[8] = self.tile_offsets
371
+ header[11], header[9] = header[10]+127, header[8]+127
372
+ write_atomic(self.filename,
373
+ struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header) +
374
+ # zero-size entry for each tile
375
+ (b'\x00' * (BUNDLEX_V1_GRID_HEIGHT * BUNDLEX_V1_GRID_WIDTH * 4)))
376
+
377
+
378
+ @contextlib.contextmanager
379
+ def readonly(self):
380
+ with open(self.filename, 'rb') as fh:
381
+ b = BundleDataV1(self.filename, self.tile_offsets)
382
+ b._fh = fh
383
+ yield b
384
+
385
+ @contextlib.contextmanager
386
+ def readwrite(self):
387
+ with open(self.filename, 'r+b') as fh:
388
+ b = BundleDataV1(self.filename, self.tile_offsets)
389
+ b._fh = fh
390
+ yield b
391
+
392
+ def read_size(self, offset):
393
+ if self._fh is None:
394
+ raise RuntimeError('not called within readonly/readwrite context')
395
+ self._fh.seek(offset)
396
+ return struct.unpack('<L', self._fh.read(4))[0]
397
+
398
+ def read_tile(self, offset):
399
+ if self._fh is None:
400
+ raise RuntimeError('not called within readonly/readwrite context')
401
+ self._fh.seek(offset)
402
+ size = struct.unpack('<L', self._fh.read(4))[0]
403
+ if size <= 0:
404
+ return False
405
+ return self._fh.read(size)
406
+
407
+ def append_tile(self, data, prev_offset):
408
+ if self._fh is None:
409
+ raise RuntimeError('not called within readwrite context')
410
+ size = len(data)
411
+ is_new_tile = True
412
+ if prev_offset:
413
+ self._fh.seek(prev_offset, os.SEEK_SET)
414
+ if self._fh.tell() == prev_offset:
415
+ if struct.unpack('<L', self._fh.read(4))[0] > 0:
416
+ is_new_tile = False
417
+
418
+ self._fh.seek(0, os.SEEK_END)
419
+ offset = self._fh.tell()
420
+ if offset == 0:
421
+ self._fh.write(b'\x00' * 16) # header
422
+ offset = 16
423
+ self._fh.write(struct.pack('<L', size))
424
+ self._fh.write(data)
425
+
426
+ # update header
427
+ self._fh.seek(0, os.SEEK_SET)
428
+ header = list(struct.unpack(BUNDLE_V1_HEADER_STRUCT_FORMAT, self._fh.read(60)))
429
+ header[2] = max(header[2], size)
430
+ header[5] += size + 4
431
+ if is_new_tile:
432
+ header[4] += 4
433
+ self._fh.seek(0, os.SEEK_SET)
434
+ self._fh.write(struct.pack(BUNDLE_V1_HEADER_STRUCT_FORMAT, *header))
435
+
436
+ return offset, size
437
+
438
+
439
+ BUNDLE_V2_GRID_WIDTH = 128
440
+ BUNDLE_V2_GRID_HEIGHT = 128
441
+ BUNDLE_V2_TILES = BUNDLE_V2_GRID_WIDTH * BUNDLE_V2_GRID_HEIGHT
442
+ BUNDLE_V2_INDEX_SIZE = BUNDLE_V2_TILES * 8
443
+
444
+ BUNDLE_V2_HEADER = (
445
+ 3, # Version
446
+ BUNDLE_V2_TILES, # numRecords
447
+ 0, # maxRecord Size
448
+ 5, # Offset Size
449
+ 0, # Slack Space
450
+ 64 + BUNDLE_V2_INDEX_SIZE, # File Size
451
+ 40, # User Header Offset
452
+ 20 + BUNDLE_V2_INDEX_SIZE, # User Header Size
453
+ 3, # Legacy 1
454
+ 16, # Legacy 2 0?
455
+ BUNDLE_V2_TILES, # Legacy 3
456
+ 5, # Legacy 4
457
+ BUNDLE_V2_INDEX_SIZE # Index Size
458
+ )
459
+ BUNDLE_V2_HEADER_STRUCT_FORMAT = '<4I3Q6I'
460
+ BUNDLE_V2_HEADER_SIZE = 64
461
+
462
+
463
+ class BundleV2(object):
464
+ def __init__(self, base_filename, offset=None):
465
+ # offset not used by V2
466
+ self.filename = base_filename + '.bundle'
467
+ self.lock_filename = base_filename + '.lck'
468
+
469
+ # defer initialization to update/remove calls to avoid
470
+ # index creation on is_cached (prevents new files in read-only caches)
471
+ self._initialized = False
472
+
473
+ def _init_index(self):
474
+ self._initialized = True
475
+ if os.path.exists(self.filename):
476
+ return
477
+ ensure_directory(self.filename)
478
+ buf = BytesIO()
479
+ buf.write(struct.pack(BUNDLE_V2_HEADER_STRUCT_FORMAT, *BUNDLE_V2_HEADER))
480
+ # Empty index (ArcGIS stores an offset of 4 and size of 0 for missing tiles)
481
+ buf.write(struct.pack('<%dQ' % BUNDLE_V2_TILES, *(4, ) * BUNDLE_V2_TILES))
482
+ write_atomic(self.filename, buf.getvalue())
483
+
484
+ def _tile_idx_offset(self, x, y):
485
+ return BUNDLE_V2_HEADER_SIZE + (x + BUNDLE_V2_GRID_HEIGHT * y) * 8
486
+
487
+ def _rel_tile_coord(self, tile_coord):
488
+ return (tile_coord[0] % BUNDLE_V2_GRID_WIDTH,
489
+ tile_coord[1] % BUNDLE_V2_GRID_HEIGHT, )
490
+
491
+ def _tile_offset_size(self, fh, x, y):
492
+ idx_offset = self._tile_idx_offset(x, y)
493
+ fh.seek(idx_offset)
494
+ val = INT64LE.unpack(fh.read(8))[0]
495
+ # Index contains 8 bytes per tile.
496
+ # Size is stored in 24 most significant bits.
497
+ # Offset in the least significant 40 bits.
498
+ size = val >> 40
499
+ if size == 0:
500
+ return 0, 0
501
+ offset = val - (size << 40)
502
+ return offset, size
503
+
504
+ def _load_tile(self, fh, tile, dimensions=None):
505
+ if tile.source or tile.coord is None:
506
+ return True
507
+
508
+ x, y = self._rel_tile_coord(tile.coord)
509
+ offset, size = self._tile_offset_size(fh, x, y)
510
+ if not size:
511
+ return False
512
+
513
+ fh.seek(offset)
514
+ data = fh.read(size)
515
+
516
+ tile.source = ImageSource(BytesIO(data))
517
+ return True
518
+
519
+ def load_tile(self, tile, with_metadata=False, dimensions=None):
520
+ if tile.source or tile.coord is None:
521
+ return True
522
+
523
+ return self.load_tiles([tile], with_metadata, dimensions=dimensions)
524
+
525
+ def load_tiles(self, tiles, with_metadata=False, dimensions=None):
526
+ missing = False
527
+
528
+ with self._readonly() as fh:
529
+ if not fh:
530
+ return False
531
+
532
+ for t in tiles:
533
+ if t.source or t.coord is None:
534
+ continue
535
+ if not self._load_tile(fh, t):
536
+ missing = True
537
+
538
+ return not missing
539
+
540
+ def is_cached(self, tile, dimensions=None):
541
+ with self._readonly() as fh:
542
+ if not fh:
543
+ return False
544
+
545
+ x, y = self._rel_tile_coord(tile.coord)
546
+ _, size = self._tile_offset_size(fh, x, y)
547
+ if not size:
548
+ return False
549
+ return True
550
+
551
+ def _update_tile_offset(self, fh, x, y, offset, size):
552
+ idx_offset = self._tile_idx_offset(x, y)
553
+ val = offset + (size << 40)
554
+
555
+ fh.seek(idx_offset, os.SEEK_SET)
556
+ fh.write(INT64LE.pack(val))
557
+
558
+ def _append_tile(self, fh, data):
559
+ # Write tile size first, then tile data.
560
+ # Offset points to actual tile data.
561
+ fh.seek(0, os.SEEK_END)
562
+ fh.write(struct.pack('<L', len(data)))
563
+ offset = fh.tell()
564
+ fh.write(data)
565
+ return offset
566
+
567
+ def _update_metadata(self, fh, filesize, tilesize):
568
+ # Max record/tile size
569
+ fh.seek(8)
570
+ old_tilesize = struct.unpack('<I', fh.read(4))[0]
571
+ if tilesize > old_tilesize:
572
+ fh.seek(8)
573
+ fh.write(struct.pack('<I', tilesize))
574
+
575
+ # Complete file size
576
+ fh.seek(24)
577
+ fh.write(struct.pack("<Q", filesize))
578
+
579
+ def _store_tile(self, fh, tile_coord, data, dimensions=None):
580
+ size = len(data)
581
+ x, y = self._rel_tile_coord(tile_coord)
582
+ offset = self._append_tile(fh, data)
583
+ self._update_tile_offset(fh, x, y, offset, size)
584
+
585
+ filesize = offset + size
586
+ self._update_metadata(fh, filesize, size)
587
+
588
+ def store_tile(self, tile, dimensions=None):
589
+ if tile.stored:
590
+ return True
591
+
592
+ return self.store_tiles([tile], dimensions=dimensions)
593
+
594
+ def store_tiles(self, tiles, dimensions=None):
595
+ self._init_index()
596
+
597
+ tiles_data = []
598
+ for t in tiles:
599
+ if t.stored:
600
+ continue
601
+ with tile_buffer(t) as buf:
602
+ data = buf.read()
603
+ tiles_data.append((t.coord, data))
604
+
605
+ with FileLock(self.lock_filename):
606
+ with self._readwrite() as fh:
607
+ for tile_coord, data in tiles_data:
608
+ self._store_tile(fh, tile_coord, data, dimensions=dimensions)
609
+
610
+ return True
611
+
612
+
613
+ def remove_tile(self, tile, dimensions=None):
614
+ if tile.coord is None:
615
+ return True
616
+
617
+ self._init_index()
618
+ with FileLock(self.lock_filename):
619
+ with self._readwrite() as fh:
620
+ x, y = self._rel_tile_coord(tile.coord)
621
+ self._update_tile_offset(fh, x, y, 0, 0)
622
+
623
+ return True
624
+
625
+ def size(self):
626
+ total_size = 0
627
+ with self._readonly() as fh:
628
+ if not fh:
629
+ return 0, 0
630
+ for y in range(BUNDLE_V2_GRID_HEIGHT):
631
+ for x in range(BUNDLE_V2_GRID_WIDTH):
632
+ _, size = self._tile_offset_size(fh, x, y)
633
+ if size:
634
+ total_size += size + 4
635
+ fh.seek(0, os.SEEK_END)
636
+ actual_size = fh.tell()
637
+ return total_size + 64 + BUNDLE_V2_INDEX_SIZE, actual_size
638
+
639
+ @contextlib.contextmanager
640
+ def _readonly(self):
641
+ try:
642
+ with open(self.filename, 'rb') as fh:
643
+ yield fh
644
+ except IOError as ex:
645
+ if ex.errno == errno.ENOENT:
646
+ # missing bundle file -> missing tile
647
+ yield None
648
+ else:
649
+ raise ex
650
+
651
+ @contextlib.contextmanager
652
+ def _readwrite(self):
653
+ self._init_index()
654
+ with open(self.filename, 'r+b') as fh:
655
+ yield fh
656
+
657
+
658
+
659
+ class CompactCacheV1(CompactCacheBase):
660
+ bundle_class = BundleV1
661
+
662
+ class CompactCacheV2(CompactCacheBase):
663
+ bundle_class = BundleV2
664
+