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
mapproxy/grid.py ADDED
@@ -0,0 +1,1170 @@
1
+ # This file is part of the MapProxy project.
2
+ # Copyright (C) 2010 Omniscale <http://omniscale.de>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ (Meta-)Tile grids (data and calculations).
18
+ """
19
+ from __future__ import division
20
+ import math
21
+
22
+ from mapproxy.srs import SRS, get_epsg_num, merge_bbox, bbox_equals
23
+ from mapproxy.util.collections import ImmutableDictList
24
+ from mapproxy.compat import string_type, iteritems
25
+
26
+ geodetic_epsg_codes = [4326]
27
+
28
+ class GridError(Exception):
29
+ pass
30
+
31
+ class NoTiles(GridError):
32
+ pass
33
+
34
+ def get_resolution(bbox, size):
35
+ """
36
+ Calculate the highest resolution needed to draw the bbox
37
+ into an image with given size.
38
+
39
+ >>> get_resolution((-180,-90,180,90), (256, 256))
40
+ 0.703125
41
+
42
+ :returns: the resolution
43
+ :rtype: float
44
+ """
45
+ w = abs(bbox[0] - bbox[2])
46
+ h = abs(bbox[1] - bbox[3])
47
+ return min(w/size[0], h/size[1])
48
+
49
+ def tile_grid_for_epsg(epsg, bbox=None, tile_size=(256, 256), res=None):
50
+ """
51
+ Create a tile grid that matches the given epsg code:
52
+
53
+ :param epsg: the epsg code
54
+ :type epsg: 'EPSG:0000', '0000' or 0000
55
+ :param bbox: the bbox of the grid
56
+ :param tile_size: the size of each tile
57
+ :param res: a list with all resolutions
58
+ """
59
+ epsg = get_epsg_num(epsg)
60
+ if epsg in geodetic_epsg_codes:
61
+ return TileGrid(epsg, is_geodetic=True, bbox=bbox, tile_size=tile_size, res=res)
62
+ return TileGrid(epsg, bbox=bbox, tile_size=tile_size, res=res)
63
+
64
+
65
+ # defer loading of default bbox since custom proj settings
66
+ # are not loaded on import time
67
+ class _default_bboxs(object):
68
+ _defaults = {
69
+ 4326: (-180, -90, 180, 90),
70
+ }
71
+ for epsg_num in (900913, 3857, 102100, 102113):
72
+ _defaults[epsg_num] = (-20037508.342789244,
73
+ -20037508.342789244,
74
+ 20037508.342789244,
75
+ 20037508.342789244)
76
+ defaults = None
77
+ def get(self, key, default=None):
78
+ try:
79
+ return self[key]
80
+ except KeyError:
81
+ return default
82
+ def __getitem__(self, key):
83
+ if self.defaults is None:
84
+ defaults = {}
85
+ for epsg, bbox in iteritems(self._defaults):
86
+ defaults[SRS(epsg)] = bbox
87
+ self.defaults = defaults
88
+ return self.defaults[key]
89
+ default_bboxs = _default_bboxs()
90
+
91
+ def tile_grid(srs=None, bbox=None, bbox_srs=None, tile_size=(256, 256),
92
+ res=None, res_factor=2.0, threshold_res=None,
93
+ num_levels=None, min_res=None, max_res=None,
94
+ stretch_factor=1.15, max_shrink_factor=4.0,
95
+ align_with=None, origin='ll', name=None
96
+ ):
97
+ """
98
+ This function creates a new TileGrid.
99
+ """
100
+ if srs is None: srs = 'EPSG:900913'
101
+ srs = SRS(srs)
102
+
103
+ if not bbox:
104
+ bbox = default_bboxs.get(srs)
105
+ if not bbox:
106
+ raise ValueError('need a bbox for grid with %s' % srs)
107
+
108
+ bbox = grid_bbox(bbox, srs=srs, bbox_srs=bbox_srs)
109
+
110
+ if res:
111
+ if isinstance(res, list):
112
+ if isinstance(res[0], (tuple, list)):
113
+ # named resolutions
114
+ res = sorted(res, key=lambda x: x[1], reverse=True)
115
+ else:
116
+ res = sorted(res, reverse=True)
117
+ assert min_res is None
118
+ assert max_res is None
119
+ assert align_with is None
120
+ else:
121
+ raise ValueError("res is not a list, use res_factor for float values")
122
+
123
+
124
+ elif align_with is not None:
125
+ res = aligned_resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size,
126
+ align_with)
127
+ else:
128
+ res = resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size)
129
+
130
+ origin = origin_from_string(origin)
131
+
132
+ return TileGrid(srs, bbox=bbox, tile_size=tile_size, res=res, threshold_res=threshold_res,
133
+ stretch_factor=stretch_factor, max_shrink_factor=max_shrink_factor,
134
+ origin=origin, name=name)
135
+
136
+ ORIGIN_UL = 'ul'
137
+ ORIGIN_LL = 'll'
138
+
139
+ def origin_from_string(origin):
140
+ if origin == None:
141
+ origin = ORIGIN_LL
142
+ elif origin.lower() in ('ll', 'sw'):
143
+ origin = ORIGIN_LL
144
+ elif origin.lower() in ('ul', 'nw'):
145
+ origin = ORIGIN_UL
146
+ else:
147
+ raise ValueError("unknown origin value '%s'" % origin)
148
+ return origin
149
+
150
+ def aligned_resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
151
+ bbox=None, tile_size=(256, 256), align_with=None):
152
+
153
+
154
+ alinged_res = align_with.resolutions
155
+ res = list(alinged_res)
156
+
157
+ if not min_res:
158
+ width = bbox[2] - bbox[0]
159
+ height = bbox[3] - bbox[1]
160
+ min_res = max(width/tile_size[0], height/tile_size[1])
161
+
162
+ res = [r for r in res if r <= min_res]
163
+
164
+ if max_res:
165
+ res = [r for r in res if r >= max_res]
166
+
167
+ if num_levels:
168
+ res = res[:num_levels]
169
+
170
+ factor_calculated = res[0]/res[1]
171
+ if res_factor == 'sqrt2' and round(factor_calculated, 8) != round(math.sqrt(2), 8):
172
+ if round(factor_calculated, 8) == 2.0:
173
+ new_res = []
174
+ for r in res:
175
+ new_res.append(r)
176
+ new_res.append(r/math.sqrt(2))
177
+ res = new_res
178
+ elif res_factor == 2.0 and round(factor_calculated, 8) != round(2.0, 8):
179
+ if round(factor_calculated, 8) == round(math.sqrt(2), 8):
180
+ res = res[::2]
181
+ return res
182
+
183
+
184
+ def resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
185
+ bbox=None, tile_size=(256, 256)):
186
+ if res_factor == 'sqrt2':
187
+ res_factor = math.sqrt(2)
188
+
189
+ res = []
190
+ if not min_res:
191
+ width = bbox[2] - bbox[0]
192
+ height = bbox[3] - bbox[1]
193
+ min_res = max(width/tile_size[0], height/tile_size[1])
194
+
195
+ if max_res:
196
+ if num_levels:
197
+ res_step = (math.log10(min_res) - math.log10(max_res)) / (num_levels-1)
198
+ res = [10**(math.log10(min_res) - res_step*i) for i in range(num_levels)]
199
+ else:
200
+ res = [min_res]
201
+ while True:
202
+ next_res = res[-1]/res_factor
203
+ if max_res >= next_res:
204
+ break
205
+ res.append(next_res)
206
+ else:
207
+ if not num_levels:
208
+ num_levels = 20 if res_factor != math.sqrt(2) else 40
209
+ res = [min_res]
210
+ while len(res) < num_levels:
211
+ res.append(res[-1]/res_factor)
212
+
213
+ return res
214
+
215
+ def grid_bbox(bbox, bbox_srs, srs):
216
+ bbox = bbox_tuple(bbox)
217
+ if bbox_srs:
218
+ bbox = SRS(bbox_srs).transform_bbox_to(srs, bbox)
219
+ return bbox
220
+
221
+ def bbox_tuple(bbox):
222
+ """
223
+ >>> bbox_tuple('20,-30,40,-10')
224
+ (20.0, -30.0, 40.0, -10.0)
225
+ >>> bbox_tuple([20,-30,40,-10])
226
+ (20.0, -30.0, 40.0, -10.0)
227
+
228
+ """
229
+ if isinstance(bbox, string_type):
230
+ bbox = bbox.split(',')
231
+ bbox = tuple(map(float, bbox))
232
+ return bbox
233
+
234
+
235
+
236
+ def bbox_width(bbox):
237
+ return bbox[2] - bbox[0]
238
+
239
+ def bbox_height(bbox):
240
+ return bbox[3] - bbox[1]
241
+
242
+ def bbox_size(bbox):
243
+ return bbox_width(bbox), bbox_height(bbox)
244
+
245
+
246
+ class NamedGridList(ImmutableDictList):
247
+ def __init__(self, items):
248
+ tmp = []
249
+ for i, value in enumerate(items):
250
+ if isinstance(value, (tuple, list)):
251
+ name, value = value
252
+ else:
253
+ name = str('%02d' % i)
254
+ tmp.append((name, value))
255
+ ImmutableDictList.__init__(self, tmp)
256
+
257
+ class TileGrid(object):
258
+ """
259
+ This class represents a regular tile grid. The first level (0) contains a single
260
+ tile, the origin is bottom-left.
261
+
262
+ :ivar levels: the number of levels
263
+ :ivar tile_size: the size of each tile in pixel
264
+ :type tile_size: ``int(with), int(height)``
265
+ :ivar srs: the srs of the grid
266
+ :type srs: `SRS`
267
+ :ivar bbox: the bbox of the grid, tiles may overlap this bbox
268
+ """
269
+
270
+ spheroid_a = 6378137.0 # for 900913
271
+ flipped_y_axis = False
272
+
273
+ def __init__(self, srs=900913, bbox=None, tile_size=(256, 256), res=None,
274
+ threshold_res=None, is_geodetic=False, levels=None,
275
+ stretch_factor=1.15, max_shrink_factor=4.0, origin='ll',
276
+ name=None):
277
+ """
278
+ :param stretch_factor: allow images to be scaled up by this factor
279
+ before the next level will be selected
280
+ :param max_shrink_factor: allow images to be scaled down by this
281
+ factor before NoTiles is raised
282
+
283
+ >>> grid = TileGrid(srs=900913)
284
+ >>> [round(x, 2) for x in grid.bbox]
285
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
286
+ """
287
+ if isinstance(srs, (int, string_type)):
288
+ srs = SRS(srs)
289
+ self.srs = srs
290
+ self.tile_size = tile_size
291
+ self.origin = origin_from_string(origin)
292
+ self.name = name
293
+
294
+ if self.origin == 'ul':
295
+ self.flipped_y_axis = True
296
+
297
+ self.is_geodetic = is_geodetic
298
+
299
+ self.stretch_factor = stretch_factor
300
+ self.max_shrink_factor = max_shrink_factor
301
+
302
+ if levels is None:
303
+ self.levels = 20
304
+ else:
305
+ self.levels = levels
306
+
307
+ if bbox is None:
308
+ bbox = self._calc_bbox()
309
+ self.bbox = bbox
310
+
311
+ factor = None
312
+
313
+ if res is None:
314
+ factor = 2.0
315
+ res = self._calc_res(factor=factor)
316
+ elif res == 'sqrt2':
317
+ if levels is None:
318
+ self.levels = 40
319
+ factor = math.sqrt(2)
320
+ res = self._calc_res(factor=factor)
321
+ elif is_float(res):
322
+ factor = float(res)
323
+ res = self._calc_res(factor=factor)
324
+
325
+ self.levels = len(res)
326
+ self.resolutions = NamedGridList(res)
327
+
328
+ self.threshold_res = None
329
+ if threshold_res:
330
+ self.threshold_res = sorted(threshold_res)
331
+
332
+
333
+ self.grid_sizes = self._calc_grids()
334
+
335
+ def _calc_grids(self):
336
+ width = self.bbox[2] - self.bbox[0]
337
+ height = self.bbox[3] - self.bbox[1]
338
+ grids = []
339
+ for idx, res in self.resolutions.iteritems():
340
+ x = max(math.ceil(width // res / self.tile_size[0]), 1)
341
+ y = max(math.ceil(height // res / self.tile_size[1]), 1)
342
+ grids.append((idx, (int(x), int(y))))
343
+ return NamedGridList(grids)
344
+
345
+ def _calc_bbox(self):
346
+ if self.is_geodetic:
347
+ return (-180.0, -90.0, 180.0, 90.0)
348
+ else:
349
+ circum = 2 * math.pi * self.spheroid_a
350
+ offset = circum / 2.0
351
+ return (-offset, -offset, offset, offset)
352
+
353
+ def _calc_res(self, factor=None):
354
+ width = self.bbox[2] - self.bbox[0]
355
+ height = self.bbox[3] - self.bbox[1]
356
+ initial_res = max(width/self.tile_size[0], height/self.tile_size[1])
357
+ if factor is None:
358
+ return pyramid_res_level(initial_res, levels=self.levels)
359
+ else:
360
+ return pyramid_res_level(initial_res, factor, levels=self.levels)
361
+
362
+ def resolution(self, level):
363
+ """
364
+ Returns the resolution of the `level` in units/pixel.
365
+
366
+ :param level: the zoom level index (zero is top)
367
+
368
+ >>> grid = TileGrid(SRS(900913))
369
+ >>> '%.5f' % grid.resolution(0)
370
+ '156543.03393'
371
+ >>> '%.5f' % grid.resolution(1)
372
+ '78271.51696'
373
+ >>> '%.5f' % grid.resolution(4)
374
+ '9783.93962'
375
+ """
376
+ return self.resolutions[level]
377
+
378
+ def closest_level(self, res):
379
+ """
380
+ Returns the level index that offers the required resolution.
381
+
382
+ :param res: the required resolution
383
+ :returns: the level with the requested or higher resolution
384
+
385
+ >>> grid = TileGrid(SRS(900913))
386
+ >>> grid.stretch_factor = 1.1
387
+ >>> l1_res = grid.resolution(1)
388
+ >>> [grid.closest_level(x) for x in (320000.0, 160000.0, l1_res+50, l1_res, \
389
+ l1_res-50, l1_res*0.91, l1_res*0.89, 8000.0)]
390
+ [0, 0, 1, 1, 1, 1, 2, 5]
391
+ """
392
+ prev_l_res = self.resolutions[0]
393
+ threshold = None
394
+ thresholds = []
395
+ if self.threshold_res:
396
+ thresholds = self.threshold_res[:]
397
+ threshold = thresholds.pop()
398
+ # skip thresholds above first res
399
+ while threshold > prev_l_res and thresholds:
400
+ threshold = thresholds.pop()
401
+
402
+ threshold_result = None
403
+ for level, l_res in enumerate(self.resolutions):
404
+ if threshold and prev_l_res > threshold >= l_res:
405
+ if res > threshold:
406
+ return level-1
407
+ elif res >= l_res:
408
+ return level
409
+ threshold = thresholds.pop() if thresholds else None
410
+
411
+ if threshold_result is not None:
412
+ # Use previous level that was within stretch_factor,
413
+ # but only if this level res is smaller then res.
414
+ # This fixes selection for resolutions that are closer together then stretch_factor.
415
+ #
416
+ if l_res < res:
417
+ return threshold_result
418
+
419
+ if l_res <= res*self.stretch_factor:
420
+ # l_res within stretch_factor
421
+ # remember this level, check for thresholds or better res in next loop
422
+ threshold_result = level
423
+ prev_l_res = l_res
424
+ return level
425
+
426
+ def tile(self, x, y, level):
427
+ """
428
+ Returns the tile id for the given point.
429
+
430
+ >>> grid = TileGrid(SRS(900913))
431
+ >>> grid.tile(1000, 1000, 0)
432
+ (0, 0, 0)
433
+ >>> grid.tile(1000, 1000, 1)
434
+ (1, 1, 1)
435
+ >>> grid = TileGrid(SRS(900913), tile_size=(512, 512))
436
+ >>> grid.tile(1000, 1000, 2)
437
+ (2, 2, 2)
438
+ """
439
+ res = self.resolution(level)
440
+ x = x - self.bbox[0]
441
+ if self.flipped_y_axis:
442
+ y = self.bbox[3] - y
443
+ else:
444
+ y = y - self.bbox[1]
445
+ tile_x = x/float(res*self.tile_size[0])
446
+ tile_y = y/float(res*self.tile_size[1])
447
+ return (int(math.floor(tile_x)), int(math.floor(tile_y)), level)
448
+
449
+ def flip_tile_coord(self, tile_coord):
450
+ """
451
+ Flip the tile coord on the y-axis. (Switch between bottom-left and top-left
452
+ origin.)
453
+
454
+ >>> grid = TileGrid(SRS(900913))
455
+ >>> grid.flip_tile_coord((0, 1, 1))
456
+ (0, 0, 1)
457
+ >>> grid.flip_tile_coord((1, 3, 2))
458
+ (1, 0, 2)
459
+ """
460
+ (x, y, z) = tile_coord
461
+ return (x, self.grid_sizes[z][1]-1-y, z)
462
+
463
+ def supports_access_with_origin(self, origin):
464
+ if origin_from_string(origin) == self.origin:
465
+ return True
466
+
467
+ # check for each level if the top and bottom coordinates of the tiles
468
+ # match the bbox of the grid. only in this case we can flip y-axis
469
+ # without any issues
470
+
471
+ # allow for some rounding errors in the _tiles_bbox calculations
472
+ delta = max(abs(self.bbox[1]), abs(self.bbox[3])) / 1e12
473
+
474
+ for level, grid_size in enumerate(self.grid_sizes):
475
+ level_bbox = self._tiles_bbox([(0, 0, level),
476
+ (grid_size[0] - 1, grid_size[1] - 1, level)])
477
+
478
+ if abs(self.bbox[1] - level_bbox[1]) > delta or abs(self.bbox[3] - level_bbox[3]) > delta:
479
+ return False
480
+ return True
481
+
482
+ def origin_tile(self, level, origin):
483
+ assert self.supports_access_with_origin(origin), 'tile origins are incompatible'
484
+ tile = (0, 0, level)
485
+
486
+ if origin_from_string(origin) == self.origin:
487
+ return tile
488
+
489
+ return self.flip_tile_coord(tile)
490
+
491
+ def get_affected_tiles(self, bbox, size, req_srs=None):
492
+ """
493
+ Get a list with all affected tiles for a bbox and output size.
494
+
495
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
496
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
497
+
498
+ >>> grid = TileGrid()
499
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
500
+ >>> tile_size = (256, 256)
501
+ >>> grid.get_affected_tiles(bbox, tile_size)
502
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
503
+ ((-20037508.342789244, -20037508.342789244,\
504
+ 20037508.342789244, 20037508.342789244), (1, 1),\
505
+ <generator object ...>)
506
+ """
507
+ src_bbox, level = self.get_affected_bbox_and_level(bbox, size, req_srs=req_srs)
508
+ return self.get_affected_level_tiles(src_bbox, level)
509
+
510
+ def get_affected_bbox_and_level(self, bbox, size, req_srs=None):
511
+ if req_srs and req_srs != self.srs:
512
+ src_bbox = req_srs.transform_bbox_to(self.srs, bbox)
513
+ else:
514
+ src_bbox = bbox
515
+
516
+ if not bbox_intersects(self.bbox, src_bbox):
517
+ raise NoTiles()
518
+
519
+ res = get_resolution(src_bbox, size)
520
+ level = self.closest_level(res)
521
+
522
+ if res > self.resolutions[0]*self.max_shrink_factor:
523
+ raise NoTiles()
524
+
525
+ return src_bbox, level
526
+
527
+ def get_affected_level_tiles(self, bbox, level):
528
+ """
529
+ Get a list with all affected tiles for a `bbox` in the given `level`.
530
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
531
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
532
+
533
+ >>> grid = TileGrid()
534
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
535
+ >>> grid.get_affected_level_tiles(bbox, 0)
536
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
537
+ ((-20037508.342789244, -20037508.342789244,\
538
+ 20037508.342789244, 20037508.342789244), (1, 1),\
539
+ <generator object ...>)
540
+ """
541
+ # remove 1/10 of a pixel so we don't get a tiles we only touch
542
+ delta = self.resolutions[level] / 10.0
543
+ x0, y0, _ = self.tile(bbox[0]+delta, bbox[1]+delta, level)
544
+ x1, y1, _ = self.tile(bbox[2]-delta, bbox[3]-delta, level)
545
+ try:
546
+ return self._tile_iter(x0, y0, x1, y1, level)
547
+ except IndexError:
548
+ raise GridError('Invalid BBOX')
549
+
550
+ def _tile_iter(self, x0, y0, x1, y1, level):
551
+ xs = list(range(x0, x1+1))
552
+ if self.flipped_y_axis:
553
+ y0, y1 = y1, y0
554
+ ys = list(range(y0, y1+1))
555
+ else:
556
+ ys = list(range(y1, y0-1, -1))
557
+
558
+ ll = (xs[0], ys[-1], level)
559
+ ur = (xs[-1], ys[0], level)
560
+
561
+ abbox = self._tiles_bbox([ll, ur])
562
+ return (abbox, (len(xs), len(ys)),
563
+ _create_tile_list(xs, ys, level, self.grid_sizes[level]))
564
+
565
+ def _tiles_bbox(self, tiles):
566
+ """
567
+ Returns the bbox of multiple tiles.
568
+ The tiles should be ordered row-wise, bottom-up.
569
+
570
+ :param tiles: ordered list of tiles
571
+ :returns: the bbox of all tiles
572
+ """
573
+ ll_bbox = self.tile_bbox(tiles[0])
574
+ ur_bbox = self.tile_bbox(tiles[-1])
575
+ return merge_bbox(ll_bbox, ur_bbox)
576
+
577
+ def tile_bbox(self, tile_coord, limit=False):
578
+ """
579
+ Returns the bbox of the given tile.
580
+
581
+ >>> grid = TileGrid(SRS(900913))
582
+ >>> [round(x, 2) for x in grid.tile_bbox((0, 0, 0))]
583
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
584
+ >>> [round(x, 2) for x in grid.tile_bbox((1, 1, 1))]
585
+ [0.0, 0.0, 20037508.34, 20037508.34]
586
+ """
587
+ x, y, z = tile_coord
588
+ res = self.resolution(z)
589
+
590
+ x0 = self.bbox[0] + round(x * res * self.tile_size[0], 12)
591
+ x1 = x0 + round(res * self.tile_size[0], 12)
592
+
593
+ if self.flipped_y_axis:
594
+ y1 = self.bbox[3] - round(y * res * self.tile_size[1], 12)
595
+ y0 = y1 - round(res * self.tile_size[1], 12)
596
+ else:
597
+ y0 = self.bbox[1] + round(y * res * self.tile_size[1], 12)
598
+ y1 = y0 + round(res * self.tile_size[1], 12)
599
+
600
+ if limit:
601
+ return (
602
+ max(x0, self.bbox[0]),
603
+ max(y0, self.bbox[1]),
604
+ min(x1, self.bbox[2]),
605
+ min(y1, self.bbox[3])
606
+ )
607
+
608
+ return x0, y0, x1, y1
609
+
610
+ def limit_tile(self, tile_coord):
611
+ """
612
+ Check if the `tile_coord` is in the grid.
613
+
614
+ :returns: the `tile_coord` if it is within the ``grid``,
615
+ otherwise ``None``.
616
+
617
+ >>> grid = TileGrid(SRS(900913))
618
+ >>> grid.limit_tile((-1, 0, 2)) == None
619
+ True
620
+ >>> grid.limit_tile((1, 2, 1)) == None
621
+ True
622
+ >>> grid.limit_tile((1, 2, 2))
623
+ (1, 2, 2)
624
+ """
625
+ x, y, z = tile_coord
626
+ if isinstance(z, string_type):
627
+ if z not in self.grid_sizes:
628
+ return None
629
+ elif z < 0 or z >= self.levels:
630
+ return None
631
+ grid = self.grid_sizes[z]
632
+ if x < 0 or y < 0 or x >= grid[0] or y >= grid[1]:
633
+ return None
634
+ return x, y, z
635
+
636
+ def __repr__(self):
637
+ return '%s(%r, (%.4f, %.4f, %.4f, %.4f),...)' % (self.__class__.__name__,
638
+ self.srs, self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3])
639
+
640
+ def is_subset_of(self, other):
641
+ """
642
+ Returns ``True`` if every tile in `self` is present in `other`.
643
+ Tile coordinates might differ and `other` may contain more
644
+ tiles (more levels, larger bbox).
645
+ """
646
+ if self.srs != other.srs:
647
+ return False
648
+
649
+ if self.tile_size != other.tile_size:
650
+ return False
651
+
652
+ # check if all level tiles from self align with (affected)
653
+ # tiles from other
654
+ for self_level, self_level_res in self.resolutions.iteritems():
655
+ level_size = (
656
+ self.grid_sizes[self_level][0] * self.tile_size[0],
657
+ self.grid_sizes[self_level][1] * self.tile_size[1]
658
+ )
659
+ level_bbox = self._tiles_bbox([
660
+ (0, 0, self_level),
661
+ (self.grid_sizes[self_level][0] - 1, self.grid_sizes[self_level][1] - 1, self_level)
662
+ ])
663
+
664
+ try:
665
+ bbox, level = other.get_affected_bbox_and_level(level_bbox, level_size)
666
+ except NoTiles:
667
+ return False
668
+ try:
669
+ bbox, grid_size, tiles = other.get_affected_level_tiles(level_bbox, level)
670
+ except GridError:
671
+ return False
672
+
673
+ if other.resolution(level) != self_level_res:
674
+ return False
675
+ if not bbox_equals(bbox, level_bbox):
676
+ return False
677
+
678
+ return True
679
+
680
+ def _create_tile_list(xs, ys, level, grid_size):
681
+ """
682
+ Returns an iterator tile_coords for the given tile ranges (`xs` and `ys`).
683
+ If the one tile_coord is negative or out of the `grid_size` bound,
684
+ the coord is None.
685
+ """
686
+ x_limit = grid_size[0]
687
+ y_limit = grid_size[1]
688
+ for y in ys:
689
+ for x in xs:
690
+ if x < 0 or y < 0 or x >= x_limit or y >= y_limit:
691
+ yield None
692
+ else:
693
+ yield x, y, level
694
+
695
+ def is_float(x):
696
+ try:
697
+ float(x)
698
+ return True
699
+ except TypeError:
700
+ return False
701
+
702
+ def pyramid_res_level(initial_res, factor=2.0, levels=20):
703
+ """
704
+ Return resolutions of an image pyramid.
705
+
706
+ :param initial_res: the resolution of the top level (0)
707
+ :param factor: the factor between each level, for tms access 2
708
+ :param levels: number of resolutions to generate
709
+
710
+ >>> list(pyramid_res_level(10000, levels=5))
711
+ [10000.0, 5000.0, 2500.0, 1250.0, 625.0]
712
+ >>> [round(x, 4) for x in
713
+ ... pyramid_res_level(10000, factor=1/0.75, levels=5)]
714
+ [10000.0, 7500.0, 5625.0, 4218.75, 3164.0625]
715
+ """
716
+ return [initial_res/factor**n for n in range(levels)]
717
+
718
+ class MetaGrid(object):
719
+ """
720
+ This class contains methods to calculate bbox, etc. of metatiles.
721
+
722
+ :param grid: the grid to use for the metatiles
723
+ :param meta_size: the number of tiles a metatile consist
724
+ :type meta_size: ``(x_size, y_size)``
725
+ :param meta_buffer: the buffer size in pixel that is added to each metatile.
726
+ the number is added to all four borders.
727
+ this buffer may improve the handling of lables overlapping (meta)tile borders.
728
+ :type meta_buffer: pixel
729
+ """
730
+ def __init__(self, grid, meta_size, meta_buffer=0):
731
+ self.grid = grid
732
+ self.meta_size = meta_size or 0
733
+ self.meta_buffer = meta_buffer
734
+
735
+ def _meta_bbox(self, tile_coord=None, tiles=None, limit_to_bbox=True):
736
+ """
737
+ Returns the bbox of the metatile that contains `tile_coord`.
738
+
739
+ :type tile_coord: ``(x, y, z)``
740
+
741
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
742
+ >>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 2))[0]]
743
+ [-20037508.34, -20037508.34, 0.0, 0.0]
744
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
745
+ >>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 0))[0]]
746
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
747
+ """
748
+ if tiles:
749
+ assert tile_coord is None
750
+ level = tiles[0][2]
751
+ bbox = self.grid._tiles_bbox(tiles)
752
+ else:
753
+ level = tile_coord[2]
754
+ bbox = self.unbuffered_meta_bbox(tile_coord)
755
+ return self._buffered_bbox(bbox, level, limit_to_bbox)
756
+
757
+
758
+ def unbuffered_meta_bbox(self, tile_coord):
759
+ x, y, z = tile_coord
760
+
761
+ meta_size = self._meta_size(z)
762
+
763
+ return self.grid._tiles_bbox([(tile_coord),
764
+ (x+meta_size[0]-1, y+meta_size[1]-1, z)])
765
+
766
+ def _buffered_bbox(self, bbox, level, limit_to_grid_bbox=True):
767
+ minx, miny, maxx, maxy = bbox
768
+
769
+ buffers = (0, 0, 0, 0)
770
+ if self.meta_buffer > 0:
771
+ res = self.grid.resolution(level)
772
+ minx -= self.meta_buffer * res
773
+ miny -= self.meta_buffer * res
774
+ maxx += self.meta_buffer * res
775
+ maxy += self.meta_buffer * res
776
+ buffers = [self.meta_buffer, self.meta_buffer, self.meta_buffer, self.meta_buffer]
777
+
778
+ if limit_to_grid_bbox:
779
+ if self.grid.bbox[0] > minx:
780
+ delta = self.grid.bbox[0] - minx
781
+ buffers[0] = buffers[0] - int(round(delta / res, 5))
782
+ minx = self.grid.bbox[0]
783
+ if self.grid.bbox[1] > miny:
784
+ delta = self.grid.bbox[1] - miny
785
+ buffers[1] = buffers[1] - int(round(delta / res, 5))
786
+ miny = self.grid.bbox[1]
787
+ if self.grid.bbox[2] < maxx:
788
+ delta = maxx - self.grid.bbox[2]
789
+ buffers[2] = buffers[2] - int(round(delta / res, 5))
790
+ maxx = self.grid.bbox[2]
791
+ if self.grid.bbox[3] < maxy:
792
+ delta = maxy - self.grid.bbox[3]
793
+ buffers[3] = buffers[3] - int(round(delta / res, 5))
794
+ maxy = self.grid.bbox[3]
795
+ return (minx, miny, maxx, maxy), tuple(buffers)
796
+
797
+ def meta_tile(self, tile_coord):
798
+ """
799
+ Returns the meta tile for `tile_coord`.
800
+ """
801
+ tile_coord = self.main_tile(tile_coord)
802
+ level = tile_coord[2]
803
+ bbox, buffers = self._meta_bbox(tile_coord)
804
+ grid_size = self._meta_size(level)
805
+ size = self._size_from_buffered_bbox(bbox, level)
806
+
807
+ tile_patterns = self._tiles_pattern(tile=tile_coord, grid_size=grid_size, buffers=buffers)
808
+
809
+ return MetaTile(bbox=bbox, size=size, tile_patterns=tile_patterns,
810
+ grid_size=grid_size
811
+ )
812
+
813
+ def minimal_meta_tile(self, tiles):
814
+ """
815
+ Returns a MetaTile that contains all `tiles` plus ``meta_buffer``,
816
+ but nothing more.
817
+ """
818
+
819
+ tiles, grid_size, bounds = self._full_tile_list(tiles)
820
+ tiles = list(tiles)
821
+ bbox, buffers = self._meta_bbox(tiles=bounds)
822
+
823
+ level = tiles[0][2]
824
+ size = self._size_from_buffered_bbox(bbox, level)
825
+
826
+ tile_pattern = self._tiles_pattern(tiles=tiles, grid_size=grid_size, buffers=buffers)
827
+
828
+ return MetaTile(
829
+ bbox=bbox,
830
+ size=size,
831
+ tile_patterns=tile_pattern,
832
+ grid_size=grid_size,
833
+ )
834
+
835
+ def _size_from_buffered_bbox(self, bbox, level):
836
+ # meta_size * tile_size + 2*buffer does not work,
837
+ # since the buffer can get truncated at the grid border
838
+ res = self.grid.resolution(level)
839
+ width = int(round((bbox[2] - bbox[0]) / res))
840
+ height = int(round((bbox[3] - bbox[1]) / res))
841
+ return width, height
842
+
843
+ def _full_tile_list(self, tiles):
844
+ """
845
+ Return a complete list of all tiles that a minimal meta tile with `tiles` contains.
846
+
847
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
848
+ >>> mgrid._full_tile_list([(0, 0, 2), (1, 1, 2)])
849
+ ([(0, 1, 2), (1, 1, 2), (0, 0, 2), (1, 0, 2)], (2, 2), ((0, 0, 2), (1, 1, 2)))
850
+ """
851
+ tile = tiles.pop()
852
+ z = tile[2]
853
+ minx = maxx = tile[0]
854
+ miny = maxy = tile[1]
855
+
856
+ for tile in tiles:
857
+ x, y = tile[:2]
858
+ minx = min(minx, x)
859
+ maxx = max(maxx, x)
860
+ miny = min(miny, y)
861
+ maxy = max(maxy, y)
862
+
863
+ grid_size = 1+maxx-minx, 1+maxy-miny
864
+
865
+ if self.grid.flipped_y_axis:
866
+ ys = range(miny, maxy+1)
867
+ else:
868
+ ys = range(maxy, miny-1, -1)
869
+ xs = range(minx, maxx+1)
870
+
871
+ bounds = (minx, miny, z), (maxx, maxy, z)
872
+
873
+ return list(_create_tile_list(xs, ys, z, (maxx+1, maxy+1))), grid_size, bounds
874
+
875
+ def main_tile(self, tile_coord):
876
+ x, y, z = tile_coord
877
+
878
+ meta_size = self._meta_size(z)
879
+
880
+ x0 = x//meta_size[0] * meta_size[0]
881
+ y0 = y//meta_size[1] * meta_size[1]
882
+
883
+ return x0, y0, z
884
+
885
+ def tile_list(self, main_tile):
886
+ tile_grid = self._meta_size(main_tile[2])
887
+ return self._meta_tile_list(main_tile, tile_grid)
888
+
889
+ def _meta_tile_list(self, main_tile, tile_grid):
890
+ """
891
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
892
+ >>> mgrid._meta_tile_list((0, 1, 3), (2, 2))
893
+ [(0, 1, 3), (1, 1, 3), (0, 0, 3), (1, 0, 3)]
894
+ """
895
+ minx, miny, z = self.main_tile(main_tile)
896
+ maxx = minx + tile_grid[0] - 1
897
+ maxy = miny + tile_grid[1] - 1
898
+ if self.grid.flipped_y_axis:
899
+ ys = range(miny, maxy+1)
900
+ else:
901
+ ys = range(maxy, miny-1, -1)
902
+ xs = range(minx, maxx+1)
903
+
904
+ return list(_create_tile_list(xs, ys, z, self.grid.grid_sizes[z]))
905
+
906
+ def _tiles_pattern(self, grid_size, buffers, tile=None, tiles=None):
907
+ """
908
+ Returns the tile pattern for the given list of tiles.
909
+ The result contains for each tile the ``tile_coord`` and the upper-left
910
+ pixel coordinate of the tile in the meta tile image.
911
+
912
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
913
+ >>> tiles = list(mgrid._tiles_pattern(tiles=[(0, 1, 2), (1, 1, 2)],
914
+ ... grid_size=(2, 1),
915
+ ... buffers=(0, 0, 10, 10)))
916
+ >>> tiles[0], tiles[-1]
917
+ (((0, 1, 2), (0, 10)), ((1, 1, 2), (256, 10)))
918
+
919
+ >>> tiles = list(mgrid._tiles_pattern(tile=(1, 1, 2),
920
+ ... grid_size=(2, 2),
921
+ ... buffers=(10, 20, 30, 40)))
922
+ >>> tiles[0], tiles[-1]
923
+ (((0, 1, 2), (10, 40)), ((1, 0, 2), (266, 296)))
924
+
925
+ """
926
+ if tile:
927
+ tiles = self._meta_tile_list(tile, grid_size)
928
+
929
+ for i in range(grid_size[1]):
930
+ for j in range(grid_size[0]):
931
+ yield tiles[j+i*grid_size[0]], (
932
+ j*self.grid.tile_size[0] + buffers[0],
933
+ i*self.grid.tile_size[1] + buffers[3])
934
+
935
+ def _meta_size(self, level):
936
+ grid_size = self.grid.grid_sizes[level]
937
+ return min(self.meta_size[0], grid_size[0]), min(self.meta_size[1], grid_size[1])
938
+
939
+ def get_affected_level_tiles(self, bbox, level):
940
+ """
941
+ Get a list with all affected tiles for a `bbox` in the given `level`.
942
+
943
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
944
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
945
+
946
+ >>> grid = MetaGrid(TileGrid(), (2, 2))
947
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
948
+ >>> grid.get_affected_level_tiles(bbox, 0)
949
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
950
+ ((-20037508.342789244, -20037508.342789244,\
951
+ 20037508.342789244, 20037508.342789244), (1, 1),\
952
+ <generator object ...>)
953
+ """
954
+
955
+ # remove 1/10 of a pixel so we don't get a tiles we only touch
956
+ delta = self.grid.resolutions[level] / 10.0
957
+ x0, y0, _ = self.grid.tile(bbox[0]+delta, bbox[1]+delta, level)
958
+ x1, y1, _ = self.grid.tile(bbox[2]-delta, bbox[3]-delta, level)
959
+
960
+ meta_size = self._meta_size(level)
961
+
962
+ x0 = x0//meta_size[0] * meta_size[0]
963
+ x1 = x1//meta_size[0] * meta_size[0]
964
+ y0 = y0//meta_size[1] * meta_size[1]
965
+ y1 = y1//meta_size[1] * meta_size[1]
966
+
967
+ try:
968
+ return self._tile_iter(x0, y0, x1, y1, level)
969
+ except IndexError:
970
+ raise GridError('Invalid BBOX')
971
+
972
+ def _tile_iter(self, x0, y0, x1, y1, level):
973
+ meta_size = self._meta_size(level)
974
+
975
+ xs = list(range(x0, x1+1, meta_size[0]))
976
+ if self.grid.flipped_y_axis:
977
+ y0, y1 = y1, y0
978
+ ys = list(range(y0, y1+1, meta_size[1]))
979
+ else:
980
+ ys = list(range(y1, y0-1, -meta_size[1]))
981
+
982
+ ll = (xs[0], ys[-1], level)
983
+ ur = (xs[-1], ys[0], level)
984
+ # add meta_size to get full affected bbox
985
+ ur = ur[0]+meta_size[0]-1, ur[1]+meta_size[1]-1, ur[2]
986
+ abbox = self.grid._tiles_bbox([ll, ur])
987
+ return (abbox, (len(xs), len(ys)),
988
+ _create_tile_list(xs, ys, level, self.grid.grid_sizes[level]))
989
+
990
+
991
+ class MetaTile(object):
992
+ def __init__(self, bbox, size, tile_patterns, grid_size):
993
+ self.bbox = bbox
994
+ self.size = size
995
+ self.tile_patterns = list(tile_patterns)
996
+ self.grid_size = grid_size
997
+
998
+ @property
999
+ def tiles(self):
1000
+ return [t[0] for t in self.tile_patterns]
1001
+
1002
+ @property
1003
+ def main_tile_coord(self):
1004
+ """
1005
+ Returns the "main" tile of the meta tile. This tile(coord) can be used
1006
+ for locking.
1007
+
1008
+ >>> t = MetaTile(None, None, [((0, 0, 0), (0, 0)), ((1, 0, 0), (100, 0))], (2, 1))
1009
+ >>> t.main_tile_coord
1010
+ (0, 0, 0)
1011
+ >>> t = MetaTile(None, None, [(None, None), ((1, 0, 0), (100, 0))], (2, 1))
1012
+ >>> t.main_tile_coord
1013
+ (1, 0, 0)
1014
+ """
1015
+ for t in self.tiles:
1016
+ if t is not None:
1017
+ return t
1018
+
1019
+ def __repr__(self):
1020
+ return "MetaTile(%r, %r, %r, %r)" % (self.bbox, self.size, self.grid_size,
1021
+ self.tile_patterns)
1022
+
1023
+ def bbox_intersects(one, two):
1024
+ a_x0, a_y0, a_x1, a_y1 = one
1025
+ b_x0, b_y0, b_x1, b_y1 = two
1026
+
1027
+ if (
1028
+ a_x0 < b_x1 and
1029
+ a_x1 > b_x0 and
1030
+ a_y0 < b_y1 and
1031
+ a_y1 > b_y0
1032
+ ): return True
1033
+
1034
+ return False
1035
+
1036
+ def bbox_contains(one, two):
1037
+ """
1038
+ Returns ``True`` if `one` contains `two`.
1039
+
1040
+ >>> bbox_contains([0, 0, 10, 10], [2, 2, 4, 4])
1041
+ True
1042
+ >>> bbox_contains([0, 0, 10, 10], [0, 0, 11, 10])
1043
+ False
1044
+
1045
+ Allow tiny rounding errors:
1046
+
1047
+ >>> bbox_contains([0, 0, 10, 10], [0.000001, 0.0000001, 10.000001, 10.000001])
1048
+ False
1049
+ >>> bbox_contains([0, 0, 10, 10], [0.0000000000001, 0.0000000000001, 10.0000000000001, 10.0000000000001])
1050
+ True
1051
+ """
1052
+ a_x0, a_y0, a_x1, a_y1 = one
1053
+ b_x0, b_y0, b_x1, b_y1 = two
1054
+
1055
+ x_delta = abs(a_x1 - a_x0) / 10e12
1056
+ y_delta = abs(a_y1 - a_y0) / 10e12
1057
+
1058
+ if (
1059
+ a_x0 <= b_x0 + x_delta and
1060
+ a_x1 >= b_x1 - x_delta and
1061
+ a_y0 <= b_y0 + y_delta and
1062
+ a_y1 >= b_y1 - y_delta
1063
+ ): return True
1064
+
1065
+ return False
1066
+
1067
+ def deg_to_m(deg):
1068
+ return deg * (6378137 * 2 * math.pi) / 360
1069
+
1070
+ OGC_PIXEL_SIZE = 0.00028 #m/px
1071
+
1072
+ def ogc_scale_to_res(scale):
1073
+ return scale * OGC_PIXEL_SIZE
1074
+ def res_to_ogc_scale(res):
1075
+ return res / OGC_PIXEL_SIZE
1076
+
1077
+ def resolution_range(min_res=None, max_res=None, max_scale=None, min_scale=None):
1078
+ if min_scale == max_scale == min_res == max_res == None:
1079
+ return None
1080
+ if min_res or max_res:
1081
+ if not max_scale and not min_scale:
1082
+ return ResolutionRange(min_res, max_res)
1083
+ elif max_scale or min_scale:
1084
+ if not min_res and not max_res:
1085
+ min_res = ogc_scale_to_res(max_scale)
1086
+ max_res = ogc_scale_to_res(min_scale)
1087
+ return ResolutionRange(min_res, max_res)
1088
+
1089
+ raise ValueError('requires either min_res/max_res or max_scale/min_scale')
1090
+
1091
+ class ResolutionRange(object):
1092
+ def __init__(self, min_res, max_res):
1093
+ self.min_res = min_res
1094
+ self.max_res = max_res
1095
+
1096
+ if min_res and max_res:
1097
+ assert min_res > max_res
1098
+
1099
+ def scale_denominator(self):
1100
+ min_scale = res_to_ogc_scale(self.max_res) if self.max_res else None
1101
+ max_scale = res_to_ogc_scale(self.min_res) if self.min_res else None
1102
+ return min_scale, max_scale
1103
+
1104
+ def scale_hint(self):
1105
+ """
1106
+ Returns the min and max diagonal resolution.
1107
+ """
1108
+ min_res = self.min_res
1109
+ max_res = self.max_res
1110
+ if min_res:
1111
+ min_res = math.sqrt(2*min_res**2)
1112
+ if max_res:
1113
+ max_res = math.sqrt(2*max_res**2)
1114
+ return min_res, max_res
1115
+
1116
+ def contains(self, bbox, size, srs):
1117
+ width, height = bbox_size(bbox)
1118
+ if srs.is_latlong:
1119
+ width = deg_to_m(width)
1120
+ height = deg_to_m(height)
1121
+
1122
+ x_res = width/size[0]
1123
+ y_res = height/size[1]
1124
+
1125
+ if self.min_res:
1126
+ min_res = self.min_res + 1e-6
1127
+ if min_res <= x_res or min_res <= y_res:
1128
+ return False
1129
+ if self.max_res:
1130
+ max_res = self.max_res
1131
+ if max_res > x_res or max_res > y_res:
1132
+ return False
1133
+
1134
+ return True
1135
+
1136
+ def __eq__(self, other):
1137
+ if not isinstance(other, ResolutionRange):
1138
+ return NotImplemented
1139
+
1140
+ return (self.min_res == other.min_res
1141
+ and self.max_res == other.max_res)
1142
+
1143
+ def __ne__(self, other):
1144
+ if not isinstance(other, ResolutionRange):
1145
+ return NotImplemented
1146
+ return not self == other
1147
+
1148
+ def __repr__(self):
1149
+ return '<ResolutionRange(min_res=%.3f, max_res=%.3f)>' % (
1150
+ self.min_res or 9e99, self.max_res or 0)
1151
+
1152
+
1153
+ def max_with_none(a, b):
1154
+ if a is None or b is None:
1155
+ return None
1156
+ else:
1157
+ return max(a, b)
1158
+
1159
+ def min_with_none(a, b):
1160
+ if a is None or b is None:
1161
+ return None
1162
+ else:
1163
+ return min(a, b)
1164
+
1165
+
1166
+ def merge_resolution_range(a, b):
1167
+ if a and b:
1168
+ return resolution_range(min_res=max_with_none(a.min_res, b.min_res),
1169
+ max_res=min_with_none(a.max_res, b.max_res))
1170
+ return None