MapProxy 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. MapProxy-2.1.0.dist-info/AUTHORS.txt +33 -0
  2. MapProxy-2.1.0.dist-info/COPYING.txt +60 -0
  3. MapProxy-2.1.0.dist-info/LICENSE.txt +202 -0
  4. MapProxy-2.1.0.dist-info/METADATA +165 -0
  5. MapProxy-2.1.0.dist-info/RECORD +459 -0
  6. MapProxy-2.1.0.dist-info/WHEEL +5 -0
  7. MapProxy-2.1.0.dist-info/entry_points.txt +4 -0
  8. MapProxy-2.1.0.dist-info/top_level.txt +1 -0
  9. mapproxy/__init__.py +0 -0
  10. mapproxy/cache/__init__.py +36 -0
  11. mapproxy/cache/azureblob.py +145 -0
  12. mapproxy/cache/base.py +120 -0
  13. mapproxy/cache/compact.py +665 -0
  14. mapproxy/cache/couchdb.py +301 -0
  15. mapproxy/cache/dummy.py +36 -0
  16. mapproxy/cache/file.py +200 -0
  17. mapproxy/cache/geopackage.py +647 -0
  18. mapproxy/cache/legend.py +87 -0
  19. mapproxy/cache/mbtiles.py +411 -0
  20. mapproxy/cache/meta.py +80 -0
  21. mapproxy/cache/path.py +261 -0
  22. mapproxy/cache/redis.py +152 -0
  23. mapproxy/cache/renderd.py +100 -0
  24. mapproxy/cache/riak.py +206 -0
  25. mapproxy/cache/s3.py +209 -0
  26. mapproxy/cache/tile.py +736 -0
  27. mapproxy/client/__init__.py +0 -0
  28. mapproxy/client/arcgis.py +82 -0
  29. mapproxy/client/cgi.py +141 -0
  30. mapproxy/client/http.py +295 -0
  31. mapproxy/client/log.py +33 -0
  32. mapproxy/client/tile.py +158 -0
  33. mapproxy/client/wms.py +255 -0
  34. mapproxy/compat/__init__.py +0 -0
  35. mapproxy/compat/image.py +86 -0
  36. mapproxy/config/__init__.py +22 -0
  37. mapproxy/config/config-schema.json +813 -0
  38. mapproxy/config/config.py +213 -0
  39. mapproxy/config/coverage.py +108 -0
  40. mapproxy/config/defaults.py +102 -0
  41. mapproxy/config/loader.py +2399 -0
  42. mapproxy/config/spec.py +657 -0
  43. mapproxy/config/validator.py +242 -0
  44. mapproxy/config_template/__init__.py +0 -0
  45. mapproxy/config_template/base_config/config.wsgi +10 -0
  46. mapproxy/config_template/base_config/full_example.yaml +598 -0
  47. mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
  48. mapproxy/config_template/base_config/log.ini +35 -0
  49. mapproxy/config_template/base_config/mapproxy.yaml +60 -0
  50. mapproxy/config_template/base_config/seed.yaml +27 -0
  51. mapproxy/exception.py +149 -0
  52. mapproxy/featureinfo.py +251 -0
  53. mapproxy/grid.py +1199 -0
  54. mapproxy/image/__init__.py +549 -0
  55. mapproxy/image/fonts/DejaVuSans.ttf +0 -0
  56. mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
  57. mapproxy/image/fonts/LICENSE +99 -0
  58. mapproxy/image/fonts/__init__.py +0 -0
  59. mapproxy/image/mask.py +79 -0
  60. mapproxy/image/merge.py +323 -0
  61. mapproxy/image/message.py +357 -0
  62. mapproxy/image/opts.py +185 -0
  63. mapproxy/image/tile.py +171 -0
  64. mapproxy/image/transform.py +350 -0
  65. mapproxy/layer.py +489 -0
  66. mapproxy/multiapp.py +230 -0
  67. mapproxy/proj.py +309 -0
  68. mapproxy/request/__init__.py +18 -0
  69. mapproxy/request/arcgis.py +268 -0
  70. mapproxy/request/base.py +466 -0
  71. mapproxy/request/tile.py +131 -0
  72. mapproxy/request/wms/__init__.py +829 -0
  73. mapproxy/request/wms/exception.py +107 -0
  74. mapproxy/request/wmts.py +442 -0
  75. mapproxy/response.py +237 -0
  76. mapproxy/script/__init__.py +0 -0
  77. mapproxy/script/conf/__init__.py +0 -0
  78. mapproxy/script/conf/app.py +222 -0
  79. mapproxy/script/conf/caches.py +44 -0
  80. mapproxy/script/conf/geopackage.py +136 -0
  81. mapproxy/script/conf/layers.py +54 -0
  82. mapproxy/script/conf/seeds.py +36 -0
  83. mapproxy/script/conf/sources.py +88 -0
  84. mapproxy/script/conf/utils.py +148 -0
  85. mapproxy/script/defrag.py +187 -0
  86. mapproxy/script/export.py +337 -0
  87. mapproxy/script/grids.py +198 -0
  88. mapproxy/script/scales.py +134 -0
  89. mapproxy/script/util.py +410 -0
  90. mapproxy/script/wms_capabilities.py +160 -0
  91. mapproxy/seed/__init__.py +0 -0
  92. mapproxy/seed/cachelock.py +127 -0
  93. mapproxy/seed/cleanup.py +191 -0
  94. mapproxy/seed/config.py +481 -0
  95. mapproxy/seed/script.py +391 -0
  96. mapproxy/seed/seeder.py +551 -0
  97. mapproxy/seed/spec.py +66 -0
  98. mapproxy/seed/util.py +266 -0
  99. mapproxy/service/__init__.py +14 -0
  100. mapproxy/service/base.py +45 -0
  101. mapproxy/service/demo.py +364 -0
  102. mapproxy/service/kml.py +333 -0
  103. mapproxy/service/ows.py +39 -0
  104. mapproxy/service/template_helper.py +55 -0
  105. mapproxy/service/templates/demo/capabilities_demo.html +18 -0
  106. mapproxy/service/templates/demo/demo.html +181 -0
  107. mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
  108. mapproxy/service/templates/demo/static/img/blank.gif +0 -0
  109. mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
  110. mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
  111. mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
  112. mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
  113. mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
  114. mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
  115. mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
  116. mapproxy/service/templates/demo/static/logo.png +0 -0
  117. mapproxy/service/templates/demo/static/ol.css +345 -0
  118. mapproxy/service/templates/demo/static/ol.js +4 -0
  119. mapproxy/service/templates/demo/static/proj4.min.js +1 -0
  120. mapproxy/service/templates/demo/static/proj4defs.js +1 -0
  121. mapproxy/service/templates/demo/static/site.css +137 -0
  122. mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
  123. mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
  124. mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
  125. mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
  126. mapproxy/service/templates/demo/static.html +34 -0
  127. mapproxy/service/templates/demo/tms_demo.html +117 -0
  128. mapproxy/service/templates/demo/wms_demo.html +144 -0
  129. mapproxy/service/templates/demo/wmts_demo.html +118 -0
  130. mapproxy/service/templates/tms_capabilities.xml +13 -0
  131. mapproxy/service/templates/tms_exception.xml +4 -0
  132. mapproxy/service/templates/tms_root_resource.xml +7 -0
  133. mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
  134. mapproxy/service/templates/wms100capabilities.xml +112 -0
  135. mapproxy/service/templates/wms100exception.xml +4 -0
  136. mapproxy/service/templates/wms110capabilities.xml +152 -0
  137. mapproxy/service/templates/wms110exception.xml +5 -0
  138. mapproxy/service/templates/wms111capabilities.xml +183 -0
  139. mapproxy/service/templates/wms111exception.xml +5 -0
  140. mapproxy/service/templates/wms130capabilities.xml +326 -0
  141. mapproxy/service/templates/wms130exception.xml +8 -0
  142. mapproxy/service/templates/wmts100capabilities.xml +155 -0
  143. mapproxy/service/templates/wmts100exception.xml +9 -0
  144. mapproxy/service/tile.py +540 -0
  145. mapproxy/service/wms.py +868 -0
  146. mapproxy/service/wmts.py +387 -0
  147. mapproxy/source/__init__.py +83 -0
  148. mapproxy/source/arcgis.py +39 -0
  149. mapproxy/source/error.py +40 -0
  150. mapproxy/source/mapnik.py +262 -0
  151. mapproxy/source/tile.py +97 -0
  152. mapproxy/source/wms.py +273 -0
  153. mapproxy/srs.py +734 -0
  154. mapproxy/template.py +54 -0
  155. mapproxy/test/__init__.py +0 -0
  156. mapproxy/test/conftest.py +8 -0
  157. mapproxy/test/helper.py +255 -0
  158. mapproxy/test/http.py +511 -0
  159. mapproxy/test/image.py +219 -0
  160. mapproxy/test/mocker.py +2291 -0
  161. mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
  162. mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
  163. mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
  164. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
  165. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
  166. mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
  167. mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
  168. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
  169. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
  170. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
  171. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
  172. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
  173. mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
  174. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
  175. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
  176. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
  177. mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
  178. mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
  179. mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
  180. mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
  181. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
  182. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
  183. mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
  184. mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
  185. mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
  186. mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
  187. mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
  188. mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
  189. mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
  190. mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
  191. mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
  192. mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
  193. mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
  194. mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
  195. mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
  196. mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
  197. mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
  198. mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
  199. mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
  200. mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
  201. mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
  202. mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
  203. mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
  204. mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
  205. mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
  206. mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
  207. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
  208. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
  209. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
  210. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
  211. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
  212. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
  213. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
  214. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
  215. mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
  216. mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  217. mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
  218. mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  219. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
  220. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
  221. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
  222. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
  223. mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
  224. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
  225. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
  226. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
  227. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
  228. mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
  229. mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  230. mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
  231. mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  232. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
  233. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
  234. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
  235. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
  236. mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
  237. mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
  238. mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
  239. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
  240. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
  241. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
  242. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
  243. mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
  244. mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
  245. mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
  246. mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
  247. mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
  248. mapproxy/test/schemas/xml.xsd +287 -0
  249. mapproxy/test/system/__init__.py +98 -0
  250. mapproxy/test/system/fixture/arcgis.yaml +57 -0
  251. mapproxy/test/system/fixture/auth.yaml +70 -0
  252. mapproxy/test/system/fixture/cache.mbtiles +0 -0
  253. mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
  254. mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
  255. mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
  256. mapproxy/test/system/fixture/cache_coverage.yaml +84 -0
  257. mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
  258. mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
  259. mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
  260. mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
  261. mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
  262. mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
  263. mapproxy/test/system/fixture/cache_s3.yaml +58 -0
  264. mapproxy/test/system/fixture/cache_source.yaml +81 -0
  265. mapproxy/test/system/fixture/combined_sources.yaml +130 -0
  266. mapproxy/test/system/fixture/coverage.yaml +77 -0
  267. mapproxy/test/system/fixture/demo.yaml +135 -0
  268. mapproxy/test/system/fixture/dimension.yaml +59 -0
  269. mapproxy/test/system/fixture/disable_storage.yaml +25 -0
  270. mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
  271. mapproxy/test/system/fixture/formats.yaml +72 -0
  272. mapproxy/test/system/fixture/inspire.yaml +101 -0
  273. mapproxy/test/system/fixture/inspire_full.yaml +124 -0
  274. mapproxy/test/system/fixture/kml_layer.yaml +66 -0
  275. mapproxy/test/system/fixture/layer.yaml +260 -0
  276. mapproxy/test/system/fixture/layergroups.yaml +57 -0
  277. mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
  278. mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
  279. mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
  280. mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
  281. mapproxy/test/system/fixture/mapserver.yaml +23 -0
  282. mapproxy/test/system/fixture/minimal_cgi.py +16 -0
  283. mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
  284. mapproxy/test/system/fixture/multi_cache_layers.yaml +111 -0
  285. mapproxy/test/system/fixture/multiapp1.yaml +20 -0
  286. mapproxy/test/system/fixture/multiapp2.yaml +19 -0
  287. mapproxy/test/system/fixture/renderd_client.yaml +55 -0
  288. mapproxy/test/system/fixture/scalehints.yaml +70 -0
  289. mapproxy/test/system/fixture/seed.yaml +94 -0
  290. mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
  291. mapproxy/test/system/fixture/seed_old.yaml +12 -0
  292. mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
  293. mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
  294. mapproxy/test/system/fixture/seedonly.yaml +51 -0
  295. mapproxy/test/system/fixture/sld.yaml +35 -0
  296. mapproxy/test/system/fixture/source_errors.yaml +84 -0
  297. mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
  298. mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
  299. mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
  300. mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
  301. mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
  302. mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
  303. mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
  304. mapproxy/test/system/fixture/util_grids.yaml +29 -0
  305. mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
  306. mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
  307. mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
  308. mapproxy/test/system/fixture/watermark.yaml +50 -0
  309. mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
  310. mapproxy/test/system/fixture/wms_versions.yaml +38 -0
  311. mapproxy/test/system/fixture/wmts.yaml +134 -0
  312. mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
  313. mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
  314. mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
  315. mapproxy/test/system/test_arcgis.py +156 -0
  316. mapproxy/test/system/test_auth.py +1133 -0
  317. mapproxy/test/system/test_behind_proxy.py +75 -0
  318. mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
  319. mapproxy/test/system/test_cache_azureblob.py +127 -0
  320. mapproxy/test/system/test_cache_band_merge.py +103 -0
  321. mapproxy/test/system/test_cache_coverage.py +168 -0
  322. mapproxy/test/system/test_cache_geopackage.py +144 -0
  323. mapproxy/test/system/test_cache_grid_names.py +89 -0
  324. mapproxy/test/system/test_cache_mbtiles.py +85 -0
  325. mapproxy/test/system/test_cache_s3.py +115 -0
  326. mapproxy/test/system/test_cache_source.py +146 -0
  327. mapproxy/test/system/test_combined_sources.py +335 -0
  328. mapproxy/test/system/test_coverage.py +140 -0
  329. mapproxy/test/system/test_decorate_img.py +214 -0
  330. mapproxy/test/system/test_demo.py +56 -0
  331. mapproxy/test/system/test_demo_with_extra_service.py +57 -0
  332. mapproxy/test/system/test_dimensions.py +279 -0
  333. mapproxy/test/system/test_disable_storage.py +42 -0
  334. mapproxy/test/system/test_formats.py +219 -0
  335. mapproxy/test/system/test_inspire_vs.py +173 -0
  336. mapproxy/test/system/test_kml.py +264 -0
  337. mapproxy/test/system/test_layergroups.py +160 -0
  338. mapproxy/test/system/test_legendgraphic.py +308 -0
  339. mapproxy/test/system/test_mapnik.py +162 -0
  340. mapproxy/test/system/test_mapserver.py +83 -0
  341. mapproxy/test/system/test_mixed_mode_format.py +201 -0
  342. mapproxy/test/system/test_multi_cache_layers.py +169 -0
  343. mapproxy/test/system/test_multiapp.py +92 -0
  344. mapproxy/test/system/test_refresh.py +206 -0
  345. mapproxy/test/system/test_renderd_client.py +304 -0
  346. mapproxy/test/system/test_response_headers.py +54 -0
  347. mapproxy/test/system/test_scalehints.py +140 -0
  348. mapproxy/test/system/test_seed.py +425 -0
  349. mapproxy/test/system/test_seed_only.py +93 -0
  350. mapproxy/test/system/test_sld.py +120 -0
  351. mapproxy/test/system/test_source_errors.py +377 -0
  352. mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
  353. mapproxy/test/system/test_tms.py +277 -0
  354. mapproxy/test/system/test_tms_origin.py +46 -0
  355. mapproxy/test/system/test_util_conf.py +434 -0
  356. mapproxy/test/system/test_util_export.py +210 -0
  357. mapproxy/test/system/test_util_grids.py +88 -0
  358. mapproxy/test/system/test_util_wms_capabilities.py +182 -0
  359. mapproxy/test/system/test_watermark.py +91 -0
  360. mapproxy/test/system/test_wms.py +1616 -0
  361. mapproxy/test/system/test_wms_srs_extent.py +165 -0
  362. mapproxy/test/system/test_wms_version.py +85 -0
  363. mapproxy/test/system/test_wmsc.py +116 -0
  364. mapproxy/test/system/test_wmts.py +334 -0
  365. mapproxy/test/system/test_wmts_dimensions.py +206 -0
  366. mapproxy/test/system/test_wmts_restful.py +198 -0
  367. mapproxy/test/system/test_xslt_featureinfo.py +423 -0
  368. mapproxy/test/test_http_helper.py +217 -0
  369. mapproxy/test/unit/__init__.py +0 -0
  370. mapproxy/test/unit/epsg +2 -0
  371. mapproxy/test/unit/polygons/polygons.dbf +0 -0
  372. mapproxy/test/unit/polygons/polygons.shp +0 -0
  373. mapproxy/test/unit/polygons/polygons.shx +0 -0
  374. mapproxy/test/unit/test_async.py +242 -0
  375. mapproxy/test/unit/test_auth.py +430 -0
  376. mapproxy/test/unit/test_cache.py +1356 -0
  377. mapproxy/test/unit/test_cache_azureblob.py +97 -0
  378. mapproxy/test/unit/test_cache_compact.py +324 -0
  379. mapproxy/test/unit/test_cache_couchdb.py +118 -0
  380. mapproxy/test/unit/test_cache_geopackage.py +256 -0
  381. mapproxy/test/unit/test_cache_redis.py +123 -0
  382. mapproxy/test/unit/test_cache_riak.py +80 -0
  383. mapproxy/test/unit/test_cache_s3.py +93 -0
  384. mapproxy/test/unit/test_cache_tile.py +477 -0
  385. mapproxy/test/unit/test_client.py +488 -0
  386. mapproxy/test/unit/test_client_arcgis.py +74 -0
  387. mapproxy/test/unit/test_client_cgi.py +140 -0
  388. mapproxy/test/unit/test_collections.py +116 -0
  389. mapproxy/test/unit/test_concat_legends.py +37 -0
  390. mapproxy/test/unit/test_conf_loader.py +1267 -0
  391. mapproxy/test/unit/test_conf_validator.py +427 -0
  392. mapproxy/test/unit/test_config.py +118 -0
  393. mapproxy/test/unit/test_decorate_img.py +185 -0
  394. mapproxy/test/unit/test_exceptions.py +270 -0
  395. mapproxy/test/unit/test_featureinfo.py +313 -0
  396. mapproxy/test/unit/test_file_lock_load.py +49 -0
  397. mapproxy/test/unit/test_geom.py +512 -0
  398. mapproxy/test/unit/test_grid.py +1279 -0
  399. mapproxy/test/unit/test_image.py +1051 -0
  400. mapproxy/test/unit/test_image_mask.py +181 -0
  401. mapproxy/test/unit/test_image_messages.py +209 -0
  402. mapproxy/test/unit/test_image_options.py +160 -0
  403. mapproxy/test/unit/test_isodate.py +118 -0
  404. mapproxy/test/unit/test_multiapp.py +163 -0
  405. mapproxy/test/unit/test_ogr_reader.py +51 -0
  406. mapproxy/test/unit/test_request.py +745 -0
  407. mapproxy/test/unit/test_request_wmts.py +178 -0
  408. mapproxy/test/unit/test_response.py +78 -0
  409. mapproxy/test/unit/test_seed.py +365 -0
  410. mapproxy/test/unit/test_seed_cachelock.py +91 -0
  411. mapproxy/test/unit/test_srs.py +215 -0
  412. mapproxy/test/unit/test_tiled_source.py +122 -0
  413. mapproxy/test/unit/test_tilefilter.py +31 -0
  414. mapproxy/test/unit/test_times.py +25 -0
  415. mapproxy/test/unit/test_timeutils.py +50 -0
  416. mapproxy/test/unit/test_util_conf_utils.py +75 -0
  417. mapproxy/test/unit/test_utils.py +476 -0
  418. mapproxy/test/unit/test_wms_capabilities.py +44 -0
  419. mapproxy/test/unit/test_wms_layer.py +113 -0
  420. mapproxy/test/unit/test_yaml.py +68 -0
  421. mapproxy/tilefilter.py +61 -0
  422. mapproxy/util/__init__.py +0 -0
  423. mapproxy/util/async_.py +229 -0
  424. mapproxy/util/collections.py +134 -0
  425. mapproxy/util/coverage.py +337 -0
  426. mapproxy/util/ext/__init__.py +14 -0
  427. mapproxy/util/ext/dictspec/__init__.py +1 -0
  428. mapproxy/util/ext/dictspec/spec.py +131 -0
  429. mapproxy/util/ext/dictspec/test/__init__.py +0 -0
  430. mapproxy/util/ext/dictspec/test/test_validator.py +278 -0
  431. mapproxy/util/ext/dictspec/validator.py +194 -0
  432. mapproxy/util/ext/local.py +198 -0
  433. mapproxy/util/ext/lockfile.py +140 -0
  434. mapproxy/util/ext/odict.py +321 -0
  435. mapproxy/util/ext/serving.py +491 -0
  436. mapproxy/util/ext/tempita/__init__.py +1093 -0
  437. mapproxy/util/ext/tempita/_looper.py +163 -0
  438. mapproxy/util/ext/tempita/string_utils.py +24 -0
  439. mapproxy/util/ext/wmsparse/__init__.py +3 -0
  440. mapproxy/util/ext/wmsparse/duration.py +600 -0
  441. mapproxy/util/ext/wmsparse/parse.py +307 -0
  442. mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
  443. mapproxy/util/ext/wmsparse/test/test_parse.py +111 -0
  444. mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
  445. mapproxy/util/ext/wmsparse/test/wms-example-111.xml +90 -0
  446. mapproxy/util/ext/wmsparse/test/wms-example-130.xml +120 -0
  447. mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
  448. mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
  449. mapproxy/util/ext/wmsparse/util.py +189 -0
  450. mapproxy/util/fs.py +164 -0
  451. mapproxy/util/geom.py +307 -0
  452. mapproxy/util/lib.py +117 -0
  453. mapproxy/util/lock.py +171 -0
  454. mapproxy/util/ogr.py +247 -0
  455. mapproxy/util/py.py +75 -0
  456. mapproxy/util/times.py +78 -0
  457. mapproxy/util/yaml.py +58 -0
  458. mapproxy/version.py +33 -0
  459. mapproxy/wsgiapp.py +167 -0
mapproxy/grid.py ADDED
@@ -0,0 +1,1199 @@
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
+
25
+ geodetic_epsg_codes = [4326]
26
+
27
+
28
+ class GridError(Exception):
29
+ pass
30
+
31
+
32
+ class NoTiles(GridError):
33
+ pass
34
+
35
+
36
+ def get_resolution(bbox, size):
37
+ """
38
+ Calculate the highest resolution needed to draw the bbox
39
+ into an image with given size.
40
+
41
+ >>> get_resolution((-180,-90,180,90), (256, 256))
42
+ 0.703125
43
+
44
+ :returns: the resolution
45
+ :rtype: float
46
+ """
47
+ w = abs(bbox[0] - bbox[2])
48
+ h = abs(bbox[1] - bbox[3])
49
+ return min(w/size[0], h/size[1])
50
+
51
+
52
+ def tile_grid_for_epsg(epsg, bbox=None, tile_size=(256, 256), res=None):
53
+ """
54
+ Create a tile grid that matches the given epsg code:
55
+
56
+ :param epsg: the epsg code
57
+ :type epsg: 'EPSG:0000', '0000' or 0000
58
+ :param bbox: the bbox of the grid
59
+ :param tile_size: the size of each tile
60
+ :param res: a list with all resolutions
61
+ """
62
+ epsg = get_epsg_num(epsg)
63
+ if epsg in geodetic_epsg_codes:
64
+ return TileGrid(epsg, is_geodetic=True, bbox=bbox, tile_size=tile_size, res=res)
65
+ return TileGrid(epsg, bbox=bbox, tile_size=tile_size, res=res)
66
+
67
+
68
+ # defer loading of default bbox since custom proj settings
69
+ # are not loaded on import time
70
+ class _default_bboxs(object):
71
+ _defaults = {
72
+ 4326: (-180, -90, 180, 90),
73
+ }
74
+ for epsg_num in (900913, 3857, 102100, 102113):
75
+ _defaults[epsg_num] = (-20037508.342789244,
76
+ -20037508.342789244,
77
+ 20037508.342789244,
78
+ 20037508.342789244)
79
+ defaults = None
80
+
81
+ def get(self, key, default=None):
82
+ try:
83
+ return self[key]
84
+ except KeyError:
85
+ return default
86
+
87
+ def __getitem__(self, key):
88
+ if self.defaults is None:
89
+ defaults = {}
90
+ for epsg, bbox in self._defaults.items():
91
+ defaults[SRS(epsg)] = bbox
92
+ self.defaults = defaults
93
+ return self.defaults[key]
94
+
95
+
96
+ default_bboxs = _default_bboxs()
97
+
98
+
99
+ def tile_grid(srs=None, bbox=None, bbox_srs=None, tile_size=(256, 256),
100
+ res=None, res_factor=2.0, threshold_res=None,
101
+ num_levels=None, min_res=None, max_res=None,
102
+ stretch_factor=1.15, max_shrink_factor=4.0,
103
+ align_with=None, origin='ll', name=None
104
+ ):
105
+ """
106
+ This function creates a new TileGrid.
107
+ """
108
+ if srs is None:
109
+ srs = 'EPSG:900913'
110
+ srs = SRS(srs)
111
+
112
+ if not bbox:
113
+ bbox = default_bboxs.get(srs)
114
+ if not bbox:
115
+ raise ValueError('need a bbox for grid with %s' % srs)
116
+
117
+ bbox = grid_bbox(bbox, srs=srs, bbox_srs=bbox_srs)
118
+
119
+ if res:
120
+ if isinstance(res, list):
121
+ if isinstance(res[0], (tuple, list)):
122
+ # named resolutions
123
+ res = sorted(res, key=lambda x: x[1], reverse=True)
124
+ else:
125
+ res = sorted(res, reverse=True)
126
+ assert min_res is None
127
+ assert max_res is None
128
+ assert align_with is None
129
+ else:
130
+ raise ValueError("res is not a list, use res_factor for float values")
131
+
132
+ elif align_with is not None:
133
+ res = aligned_resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size,
134
+ align_with)
135
+ else:
136
+ res = resolutions(min_res, max_res, res_factor, num_levels, bbox, tile_size)
137
+
138
+ origin = origin_from_string(origin)
139
+
140
+ return TileGrid(srs, bbox=bbox, tile_size=tile_size, res=res, threshold_res=threshold_res,
141
+ stretch_factor=stretch_factor, max_shrink_factor=max_shrink_factor,
142
+ origin=origin, name=name)
143
+
144
+
145
+ ORIGIN_UL = 'ul'
146
+ ORIGIN_LL = 'll'
147
+
148
+
149
+ def origin_from_string(origin):
150
+ if origin is None:
151
+ origin = ORIGIN_LL
152
+ elif origin.lower() in ('ll', 'sw'):
153
+ origin = ORIGIN_LL
154
+ elif origin.lower() in ('ul', 'nw'):
155
+ origin = ORIGIN_UL
156
+ else:
157
+ raise ValueError("unknown origin value '%s'" % origin)
158
+ return origin
159
+
160
+
161
+ def aligned_resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
162
+ bbox=None, tile_size=(256, 256), align_with=None):
163
+
164
+ alinged_res = align_with.resolutions
165
+ res = list(alinged_res)
166
+
167
+ if not min_res:
168
+ width = bbox[2] - bbox[0]
169
+ height = bbox[3] - bbox[1]
170
+ min_res = max(width/tile_size[0], height/tile_size[1])
171
+
172
+ res = [r for r in res if r <= min_res]
173
+
174
+ if max_res:
175
+ res = [r for r in res if r >= max_res]
176
+
177
+ if num_levels:
178
+ res = res[:num_levels]
179
+
180
+ factor_calculated = res[0]/res[1]
181
+ if res_factor == 'sqrt2' and round(factor_calculated, 8) != round(math.sqrt(2), 8):
182
+ if round(factor_calculated, 8) == 2.0:
183
+ new_res = []
184
+ for r in res:
185
+ new_res.append(r)
186
+ new_res.append(r/math.sqrt(2))
187
+ res = new_res
188
+ elif res_factor == 2.0 and round(factor_calculated, 8) != round(2.0, 8):
189
+ if round(factor_calculated, 8) == round(math.sqrt(2), 8):
190
+ res = res[::2]
191
+ return res
192
+
193
+
194
+ def resolutions(min_res=None, max_res=None, res_factor=2.0, num_levels=None,
195
+ bbox=None, tile_size=(256, 256)):
196
+ if res_factor == 'sqrt2':
197
+ res_factor = math.sqrt(2)
198
+
199
+ res = []
200
+ if not min_res:
201
+ width = bbox[2] - bbox[0]
202
+ height = bbox[3] - bbox[1]
203
+ min_res = max(width/tile_size[0], height/tile_size[1])
204
+
205
+ if max_res:
206
+ if num_levels:
207
+ res_step = (math.log10(min_res) - math.log10(max_res)) / (num_levels-1)
208
+ res = [10**(math.log10(min_res) - res_step*i) for i in range(num_levels)]
209
+ else:
210
+ res = [min_res]
211
+ while True:
212
+ next_res = res[-1]/res_factor
213
+ if max_res >= next_res:
214
+ break
215
+ res.append(next_res)
216
+ else:
217
+ if not num_levels:
218
+ num_levels = 20 if res_factor != math.sqrt(2) else 40
219
+ res = [min_res]
220
+ while len(res) < num_levels:
221
+ res.append(res[-1]/res_factor)
222
+
223
+ return res
224
+
225
+
226
+ def grid_bbox(bbox, bbox_srs, srs):
227
+ bbox = bbox_tuple(bbox)
228
+ if bbox_srs:
229
+ bbox = SRS(bbox_srs).transform_bbox_to(srs, bbox)
230
+ return bbox
231
+
232
+
233
+ def bbox_tuple(bbox):
234
+ """
235
+ >>> bbox_tuple('20,-30,40,-10')
236
+ (20.0, -30.0, 40.0, -10.0)
237
+ >>> bbox_tuple([20,-30,40,-10])
238
+ (20.0, -30.0, 40.0, -10.0)
239
+
240
+ """
241
+ if isinstance(bbox, str):
242
+ bbox = bbox.split(',')
243
+ bbox = tuple(map(float, bbox))
244
+ return bbox
245
+
246
+
247
+ def bbox_width(bbox):
248
+ return bbox[2] - bbox[0]
249
+
250
+
251
+ def bbox_height(bbox):
252
+ return bbox[3] - bbox[1]
253
+
254
+
255
+ def bbox_size(bbox):
256
+ return bbox_width(bbox), bbox_height(bbox)
257
+
258
+
259
+ class NamedGridList(ImmutableDictList):
260
+ def __init__(self, items):
261
+ tmp = []
262
+ for i, value in enumerate(items):
263
+ if isinstance(value, (tuple, list)):
264
+ name, value = value
265
+ else:
266
+ name = str('%02d' % i)
267
+ tmp.append((name, value))
268
+ ImmutableDictList.__init__(self, tmp)
269
+
270
+
271
+ class TileGrid(object):
272
+ """
273
+ This class represents a regular tile grid. The first level (0) contains a single
274
+ tile, the origin is bottom-left.
275
+
276
+ :ivar levels: the number of levels
277
+ :ivar tile_size: the size of each tile in pixel
278
+ :type tile_size: ``int(with), int(height)``
279
+ :ivar srs: the srs of the grid
280
+ :type srs: `SRS`
281
+ :ivar bbox: the bbox of the grid, tiles may overlap this bbox
282
+ """
283
+
284
+ spheroid_a = 6378137.0 # for 900913
285
+ flipped_y_axis = False
286
+
287
+ def __init__(self, srs=900913, bbox=None, tile_size=(256, 256), res=None,
288
+ threshold_res=None, is_geodetic=False, levels=None,
289
+ stretch_factor=1.15, max_shrink_factor=4.0, origin='ll',
290
+ name=None):
291
+ """
292
+ :param stretch_factor: allow images to be scaled up by this factor
293
+ before the next level will be selected
294
+ :param max_shrink_factor: allow images to be scaled down by this
295
+ factor before NoTiles is raised
296
+
297
+ >>> grid = TileGrid(srs=900913)
298
+ >>> [round(x, 2) for x in grid.bbox]
299
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
300
+ """
301
+ if isinstance(srs, (int, str)):
302
+ srs = SRS(srs)
303
+ self.srs = srs
304
+ self.tile_size = tile_size
305
+ self.origin = origin_from_string(origin)
306
+ self.name = name
307
+
308
+ if self.origin == 'ul':
309
+ self.flipped_y_axis = True
310
+
311
+ self.is_geodetic = is_geodetic
312
+
313
+ self.stretch_factor = stretch_factor
314
+ self.max_shrink_factor = max_shrink_factor
315
+
316
+ if levels is None:
317
+ self.levels = 20
318
+ else:
319
+ self.levels = levels
320
+
321
+ if bbox is None:
322
+ bbox = self._calc_bbox()
323
+ self.bbox = bbox
324
+
325
+ factor = None
326
+
327
+ if res is None:
328
+ factor = 2.0
329
+ res = self._calc_res(factor=factor)
330
+ elif res == 'sqrt2':
331
+ if levels is None:
332
+ self.levels = 40
333
+ factor = math.sqrt(2)
334
+ res = self._calc_res(factor=factor)
335
+ elif is_float(res):
336
+ factor = float(res)
337
+ res = self._calc_res(factor=factor)
338
+
339
+ self.levels = len(res)
340
+ self.resolutions = NamedGridList(res)
341
+
342
+ self.threshold_res = None
343
+ if threshold_res:
344
+ self.threshold_res = sorted(threshold_res)
345
+
346
+ self.grid_sizes = self._calc_grids()
347
+
348
+ def _calc_grids(self):
349
+ width = self.bbox[2] - self.bbox[0]
350
+ height = self.bbox[3] - self.bbox[1]
351
+ grids = []
352
+ for idx, res in self.resolutions.iteritems():
353
+ x = max(math.ceil(width // res / self.tile_size[0]), 1)
354
+ y = max(math.ceil(height // res / self.tile_size[1]), 1)
355
+ grids.append((idx, (int(x), int(y))))
356
+ return NamedGridList(grids)
357
+
358
+ def _calc_bbox(self):
359
+ if self.is_geodetic:
360
+ return (-180.0, -90.0, 180.0, 90.0)
361
+ else:
362
+ circum = 2 * math.pi * self.spheroid_a
363
+ offset = circum / 2.0
364
+ return (-offset, -offset, offset, offset)
365
+
366
+ def _calc_res(self, factor=None):
367
+ width = self.bbox[2] - self.bbox[0]
368
+ height = self.bbox[3] - self.bbox[1]
369
+ initial_res = max(width/self.tile_size[0], height/self.tile_size[1])
370
+ if factor is None:
371
+ return pyramid_res_level(initial_res, levels=self.levels)
372
+ else:
373
+ return pyramid_res_level(initial_res, factor, levels=self.levels)
374
+
375
+ def resolution(self, level):
376
+ """
377
+ Returns the resolution of the `level` in units/pixel.
378
+
379
+ :param level: the zoom level index (zero is top)
380
+
381
+ >>> grid = TileGrid(SRS(900913))
382
+ >>> '%.5f' % grid.resolution(0)
383
+ '156543.03393'
384
+ >>> '%.5f' % grid.resolution(1)
385
+ '78271.51696'
386
+ >>> '%.5f' % grid.resolution(4)
387
+ '9783.93962'
388
+ """
389
+ return self.resolutions[level]
390
+
391
+ def closest_level(self, res):
392
+ """
393
+ Returns the level index that offers the required resolution.
394
+
395
+ :param res: the required resolution
396
+ :returns: the level with the requested or higher resolution
397
+
398
+ >>> grid = TileGrid(SRS(900913))
399
+ >>> grid.stretch_factor = 1.1
400
+ >>> l1_res = grid.resolution(1)
401
+ >>> [grid.closest_level(x) for x in (320000.0, 160000.0, l1_res+50, l1_res, \
402
+ l1_res-50, l1_res*0.91, l1_res*0.89, 8000.0)]
403
+ [0, 0, 1, 1, 1, 1, 2, 5]
404
+ """
405
+ prev_l_res = self.resolutions[0]
406
+ threshold = None
407
+ thresholds = []
408
+ if self.threshold_res:
409
+ thresholds = self.threshold_res[:]
410
+ threshold = thresholds.pop()
411
+ # skip thresholds above first res
412
+ while threshold > prev_l_res and thresholds:
413
+ threshold = thresholds.pop()
414
+
415
+ threshold_result = None
416
+ for level, l_res in enumerate(self.resolutions):
417
+ if threshold and prev_l_res > threshold >= l_res:
418
+ if res > threshold:
419
+ return level-1
420
+ elif res >= l_res:
421
+ return level
422
+ threshold = thresholds.pop() if thresholds else None
423
+
424
+ if threshold_result is not None:
425
+ # Use previous level that was within stretch_factor,
426
+ # but only if this level res is smaller then res.
427
+ # This fixes selection for resolutions that are closer together then stretch_factor.
428
+ #
429
+ if l_res < res:
430
+ return threshold_result
431
+
432
+ if l_res <= res*self.stretch_factor:
433
+ # l_res within stretch_factor
434
+ # remember this level, check for thresholds or better res in next loop
435
+ threshold_result = level
436
+ prev_l_res = l_res
437
+ return level
438
+
439
+ def tile(self, x, y, level):
440
+ """
441
+ Returns the tile id for the given point.
442
+
443
+ >>> grid = TileGrid(SRS(900913))
444
+ >>> grid.tile(1000, 1000, 0)
445
+ (0, 0, 0)
446
+ >>> grid.tile(1000, 1000, 1)
447
+ (1, 1, 1)
448
+ >>> grid = TileGrid(SRS(900913), tile_size=(512, 512))
449
+ >>> grid.tile(1000, 1000, 2)
450
+ (2, 2, 2)
451
+ """
452
+ res = self.resolution(level)
453
+ x = x - self.bbox[0]
454
+ if self.flipped_y_axis:
455
+ y = self.bbox[3] - y
456
+ else:
457
+ y = y - self.bbox[1]
458
+ tile_x = x/float(res*self.tile_size[0])
459
+ tile_y = y/float(res*self.tile_size[1])
460
+ return (int(math.floor(tile_x)), int(math.floor(tile_y)), level)
461
+
462
+ def flip_tile_coord(self, tile_coord):
463
+ """
464
+ Flip the tile coord on the y-axis. (Switch between bottom-left and top-left
465
+ origin.)
466
+
467
+ >>> grid = TileGrid(SRS(900913))
468
+ >>> grid.flip_tile_coord((0, 1, 1))
469
+ (0, 0, 1)
470
+ >>> grid.flip_tile_coord((1, 3, 2))
471
+ (1, 0, 2)
472
+ """
473
+ (x, y, z) = tile_coord
474
+ return (x, self.grid_sizes[z][1]-1-y, z)
475
+
476
+ def supports_access_with_origin(self, origin):
477
+ if origin_from_string(origin) == self.origin:
478
+ return True
479
+
480
+ # check for each level if the top and bottom coordinates of the tiles
481
+ # match the bbox of the grid. only in this case we can flip y-axis
482
+ # without any issues
483
+
484
+ # allow for some rounding errors in the _tiles_bbox calculations
485
+ delta = max(abs(self.bbox[1]), abs(self.bbox[3])) / 1e12
486
+
487
+ for level, grid_size in enumerate(self.grid_sizes):
488
+ level_bbox = self._tiles_bbox([(0, 0, level),
489
+ (grid_size[0] - 1, grid_size[1] - 1, level)])
490
+
491
+ if abs(self.bbox[1] - level_bbox[1]) > delta or abs(self.bbox[3] - level_bbox[3]) > delta:
492
+ return False
493
+ return True
494
+
495
+ def origin_tile(self, level, origin):
496
+ assert self.supports_access_with_origin(origin), 'tile origins are incompatible'
497
+ tile = (0, 0, level)
498
+
499
+ if origin_from_string(origin) == self.origin:
500
+ return tile
501
+
502
+ return self.flip_tile_coord(tile)
503
+
504
+ def get_affected_tiles(self, bbox, size, req_srs=None):
505
+ """
506
+ Get a list with all affected tiles for a bbox and output size.
507
+
508
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
509
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
510
+
511
+ >>> grid = TileGrid()
512
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
513
+ >>> tile_size = (256, 256)
514
+ >>> grid.get_affected_tiles(bbox, tile_size)
515
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
516
+ ((-20037508.342789244, -20037508.342789244,\
517
+ 20037508.342789244, 20037508.342789244), (1, 1),\
518
+ <generator object ...>)
519
+ """
520
+ src_bbox, level = self.get_affected_bbox_and_level(bbox, size, req_srs=req_srs)
521
+ return self.get_affected_level_tiles(src_bbox, level)
522
+
523
+ def get_affected_bbox_and_level(self, bbox, size, req_srs=None):
524
+ if req_srs and req_srs != self.srs:
525
+ src_bbox = req_srs.transform_bbox_to(self.srs, bbox)
526
+ else:
527
+ src_bbox = bbox
528
+
529
+ if not bbox_intersects(self.bbox, src_bbox):
530
+ raise NoTiles()
531
+
532
+ res = get_resolution(src_bbox, size)
533
+ level = self.closest_level(res)
534
+
535
+ if res > self.resolutions[0]*self.max_shrink_factor:
536
+ raise NoTiles()
537
+
538
+ return src_bbox, level
539
+
540
+ def get_affected_level_tiles(self, bbox, level):
541
+ """
542
+ Get a list with all affected tiles for a `bbox` in the given `level`.
543
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
544
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
545
+
546
+ >>> grid = TileGrid()
547
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
548
+ >>> grid.get_affected_level_tiles(bbox, 0)
549
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
550
+ ((-20037508.342789244, -20037508.342789244,\
551
+ 20037508.342789244, 20037508.342789244), (1, 1),\
552
+ <generator object ...>)
553
+ """
554
+ # remove 1/10 of a pixel so we don't get a tiles we only touch
555
+ delta = self.resolutions[level] / 10.0
556
+ x0, y0, _ = self.tile(bbox[0]+delta, bbox[1]+delta, level)
557
+ x1, y1, _ = self.tile(bbox[2]-delta, bbox[3]-delta, level)
558
+ try:
559
+ return self._tile_iter(x0, y0, x1, y1, level)
560
+ except IndexError:
561
+ raise GridError('Invalid BBOX')
562
+
563
+ def _tile_iter(self, x0, y0, x1, y1, level):
564
+ xs = list(range(x0, x1+1))
565
+ if self.flipped_y_axis:
566
+ y0, y1 = y1, y0
567
+ ys = list(range(y0, y1+1))
568
+ else:
569
+ ys = list(range(y1, y0-1, -1))
570
+
571
+ ll = (xs[0], ys[-1], level)
572
+ ur = (xs[-1], ys[0], level)
573
+
574
+ abbox = self._tiles_bbox([ll, ur])
575
+ return (abbox, (len(xs), len(ys)),
576
+ _create_tile_list(xs, ys, level, self.grid_sizes[level]))
577
+
578
+ def _tiles_bbox(self, tiles):
579
+ """
580
+ Returns the bbox of multiple tiles.
581
+ The tiles should be ordered row-wise, bottom-up.
582
+
583
+ :param tiles: ordered list of tiles
584
+ :returns: the bbox of all tiles
585
+ """
586
+ ll_bbox = self.tile_bbox(tiles[0])
587
+ ur_bbox = self.tile_bbox(tiles[-1])
588
+ return merge_bbox(ll_bbox, ur_bbox)
589
+
590
+ def tile_bbox(self, tile_coord, limit=False):
591
+ """
592
+ Returns the bbox of the given tile.
593
+
594
+ >>> grid = TileGrid(SRS(900913))
595
+ >>> [round(x, 2) for x in grid.tile_bbox((0, 0, 0))]
596
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
597
+ >>> [round(x, 2) for x in grid.tile_bbox((1, 1, 1))]
598
+ [0.0, 0.0, 20037508.34, 20037508.34]
599
+ """
600
+ x, y, z = tile_coord
601
+ res = self.resolution(z)
602
+
603
+ x0 = self.bbox[0] + round(x * res * self.tile_size[0], 12)
604
+ x1 = x0 + round(res * self.tile_size[0], 12)
605
+
606
+ if self.flipped_y_axis:
607
+ y1 = self.bbox[3] - round(y * res * self.tile_size[1], 12)
608
+ y0 = y1 - round(res * self.tile_size[1], 12)
609
+ else:
610
+ y0 = self.bbox[1] + round(y * res * self.tile_size[1], 12)
611
+ y1 = y0 + round(res * self.tile_size[1], 12)
612
+
613
+ if limit:
614
+ return (
615
+ max(x0, self.bbox[0]),
616
+ max(y0, self.bbox[1]),
617
+ min(x1, self.bbox[2]),
618
+ min(y1, self.bbox[3])
619
+ )
620
+
621
+ return x0, y0, x1, y1
622
+
623
+ def limit_tile(self, tile_coord):
624
+ """
625
+ Check if the `tile_coord` is in the grid.
626
+
627
+ :returns: the `tile_coord` if it is within the ``grid``,
628
+ otherwise ``None``.
629
+
630
+ >>> grid = TileGrid(SRS(900913))
631
+ >>> grid.limit_tile((-1, 0, 2)) is None
632
+ True
633
+ >>> grid.limit_tile((1, 2, 1)) is None
634
+ True
635
+ >>> grid.limit_tile((1, 2, 2))
636
+ (1, 2, 2)
637
+ """
638
+ x, y, z = tile_coord
639
+ if isinstance(z, str):
640
+ if z not in self.grid_sizes:
641
+ return None
642
+ elif z < 0 or z >= self.levels:
643
+ return None
644
+ grid = self.grid_sizes[z]
645
+ if x < 0 or y < 0 or x >= grid[0] or y >= grid[1]:
646
+ return None
647
+ return x, y, z
648
+
649
+ def __repr__(self):
650
+ return '%s(%r, (%.4f, %.4f, %.4f, %.4f),...)' % (
651
+ self.__class__.__name__, self.srs, self.bbox[0], self.bbox[1], self.bbox[2], self.bbox[3])
652
+
653
+ def is_subset_of(self, other):
654
+ """
655
+ Returns ``True`` if every tile in `self` is present in `other`.
656
+ Tile coordinates might differ and `other` may contain more
657
+ tiles (more levels, larger bbox).
658
+ """
659
+ if self.srs != other.srs:
660
+ return False
661
+
662
+ if self.tile_size != other.tile_size:
663
+ return False
664
+
665
+ # check if all level tiles from self align with (affected)
666
+ # tiles from other
667
+ for self_level, self_level_res in self.resolutions.iteritems():
668
+ level_size = (
669
+ self.grid_sizes[self_level][0] * self.tile_size[0],
670
+ self.grid_sizes[self_level][1] * self.tile_size[1]
671
+ )
672
+ level_bbox = self._tiles_bbox([
673
+ (0, 0, self_level),
674
+ (self.grid_sizes[self_level][0] - 1, self.grid_sizes[self_level][1] - 1, self_level)
675
+ ])
676
+
677
+ try:
678
+ bbox, level = other.get_affected_bbox_and_level(level_bbox, level_size)
679
+ except NoTiles:
680
+ return False
681
+ try:
682
+ bbox, grid_size, tiles = other.get_affected_level_tiles(level_bbox, level)
683
+ except GridError:
684
+ return False
685
+
686
+ if other.resolution(level) != self_level_res:
687
+ return False
688
+ if not bbox_equals(bbox, level_bbox):
689
+ return False
690
+
691
+ return True
692
+
693
+
694
+ def _create_tile_list(xs, ys, level, grid_size):
695
+ """
696
+ Returns an iterator tile_coords for the given tile ranges (`xs` and `ys`).
697
+ If the one tile_coord is negative or out of the `grid_size` bound,
698
+ the coord is None.
699
+ """
700
+ x_limit = grid_size[0]
701
+ y_limit = grid_size[1]
702
+ for y in ys:
703
+ for x in xs:
704
+ if x < 0 or y < 0 or x >= x_limit or y >= y_limit:
705
+ yield None
706
+ else:
707
+ yield x, y, level
708
+
709
+
710
+ def is_float(x):
711
+ try:
712
+ float(x)
713
+ return True
714
+ except TypeError:
715
+ return False
716
+
717
+
718
+ def pyramid_res_level(initial_res, factor=2.0, levels=20):
719
+ """
720
+ Return resolutions of an image pyramid.
721
+
722
+ :param initial_res: the resolution of the top level (0)
723
+ :param factor: the factor between each level, for tms access 2
724
+ :param levels: number of resolutions to generate
725
+
726
+ >>> list(pyramid_res_level(10000, levels=5))
727
+ [10000.0, 5000.0, 2500.0, 1250.0, 625.0]
728
+ >>> [round(x, 4) for x in
729
+ ... pyramid_res_level(10000, factor=1/0.75, levels=5)]
730
+ [10000.0, 7500.0, 5625.0, 4218.75, 3164.0625]
731
+ """
732
+ return [initial_res/factor**n for n in range(levels)]
733
+
734
+
735
+ class MetaGrid(object):
736
+ """
737
+ This class contains methods to calculate bbox, etc. of metatiles.
738
+
739
+ :param grid: the grid to use for the metatiles
740
+ :param meta_size: the number of tiles a metatile consist
741
+ :type meta_size: ``(x_size, y_size)``
742
+ :param meta_buffer: the buffer size in pixel that is added to each metatile.
743
+ the number is added to all four borders.
744
+ this buffer may improve the handling of lables overlapping (meta)tile borders.
745
+ :type meta_buffer: pixel
746
+ """
747
+
748
+ def __init__(self, grid, meta_size, meta_buffer=0):
749
+ self.grid = grid
750
+ self.meta_size = meta_size or 0
751
+ self.meta_buffer = meta_buffer
752
+
753
+ def _meta_bbox(self, tile_coord=None, tiles=None, limit_to_bbox=True):
754
+ """
755
+ Returns the bbox of the metatile that contains `tile_coord`.
756
+
757
+ :type tile_coord: ``(x, y, z)``
758
+
759
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
760
+ >>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 2))[0]]
761
+ [-20037508.34, -20037508.34, 0.0, 0.0]
762
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
763
+ >>> [round(x, 2) for x in mgrid._meta_bbox((0, 0, 0))[0]]
764
+ [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
765
+ """
766
+ if tiles:
767
+ assert tile_coord is None
768
+ level = tiles[0][2]
769
+ bbox = self.grid._tiles_bbox(tiles)
770
+ else:
771
+ level = tile_coord[2]
772
+ bbox = self.unbuffered_meta_bbox(tile_coord)
773
+ return self._buffered_bbox(bbox, level, limit_to_bbox)
774
+
775
+ def unbuffered_meta_bbox(self, tile_coord):
776
+ x, y, z = tile_coord
777
+
778
+ meta_size = self._meta_size(z)
779
+
780
+ return self.grid._tiles_bbox([(tile_coord),
781
+ (x+meta_size[0]-1, y+meta_size[1]-1, z)])
782
+
783
+ def _buffered_bbox(self, bbox, level, limit_to_grid_bbox=True):
784
+ minx, miny, maxx, maxy = bbox
785
+
786
+ buffers = (0, 0, 0, 0)
787
+ if self.meta_buffer > 0:
788
+ res = self.grid.resolution(level)
789
+ minx -= self.meta_buffer * res
790
+ miny -= self.meta_buffer * res
791
+ maxx += self.meta_buffer * res
792
+ maxy += self.meta_buffer * res
793
+ buffers = [self.meta_buffer, self.meta_buffer, self.meta_buffer, self.meta_buffer]
794
+
795
+ if limit_to_grid_bbox:
796
+ if self.grid.bbox[0] > minx:
797
+ delta = self.grid.bbox[0] - minx
798
+ buffers[0] = buffers[0] - int(round(delta / res, 5))
799
+ minx = self.grid.bbox[0]
800
+ if self.grid.bbox[1] > miny:
801
+ delta = self.grid.bbox[1] - miny
802
+ buffers[1] = buffers[1] - int(round(delta / res, 5))
803
+ miny = self.grid.bbox[1]
804
+ if self.grid.bbox[2] < maxx:
805
+ delta = maxx - self.grid.bbox[2]
806
+ buffers[2] = buffers[2] - int(round(delta / res, 5))
807
+ maxx = self.grid.bbox[2]
808
+ if self.grid.bbox[3] < maxy:
809
+ delta = maxy - self.grid.bbox[3]
810
+ buffers[3] = buffers[3] - int(round(delta / res, 5))
811
+ maxy = self.grid.bbox[3]
812
+ return (minx, miny, maxx, maxy), tuple(buffers)
813
+
814
+ def meta_tile(self, tile_coord):
815
+ """
816
+ Returns the meta tile for `tile_coord`.
817
+ """
818
+ tile_coord = self.main_tile(tile_coord)
819
+ level = tile_coord[2]
820
+ bbox, buffers = self._meta_bbox(tile_coord)
821
+ grid_size = self._meta_size(level)
822
+ size = self._size_from_buffered_bbox(bbox, level)
823
+
824
+ tile_patterns = self._tiles_pattern(tile=tile_coord, grid_size=grid_size, buffers=buffers)
825
+
826
+ return MetaTile(bbox=bbox, size=size, tile_patterns=tile_patterns,
827
+ grid_size=grid_size
828
+ )
829
+
830
+ def minimal_meta_tile(self, tiles):
831
+ """
832
+ Returns a MetaTile that contains all `tiles` plus ``meta_buffer``,
833
+ but nothing more.
834
+ """
835
+
836
+ tiles, grid_size, bounds = self._full_tile_list(tiles)
837
+ tiles = list(tiles)
838
+ bbox, buffers = self._meta_bbox(tiles=bounds)
839
+
840
+ level = tiles[0][2]
841
+ size = self._size_from_buffered_bbox(bbox, level)
842
+
843
+ tile_pattern = self._tiles_pattern(tiles=tiles, grid_size=grid_size, buffers=buffers)
844
+
845
+ return MetaTile(
846
+ bbox=bbox,
847
+ size=size,
848
+ tile_patterns=tile_pattern,
849
+ grid_size=grid_size,
850
+ )
851
+
852
+ def _size_from_buffered_bbox(self, bbox, level):
853
+ # meta_size * tile_size + 2*buffer does not work,
854
+ # since the buffer can get truncated at the grid border
855
+ res = self.grid.resolution(level)
856
+ width = int(round((bbox[2] - bbox[0]) / res))
857
+ height = int(round((bbox[3] - bbox[1]) / res))
858
+ return width, height
859
+
860
+ def _full_tile_list(self, tiles):
861
+ """
862
+ Return a complete list of all tiles that a minimal meta tile with `tiles` contains.
863
+
864
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
865
+ >>> mgrid._full_tile_list([(0, 0, 2), (1, 1, 2)])
866
+ ([(0, 1, 2), (1, 1, 2), (0, 0, 2), (1, 0, 2)], (2, 2), ((0, 0, 2), (1, 1, 2)))
867
+ """
868
+ tile = tiles.pop()
869
+ z = tile[2]
870
+ minx = maxx = tile[0]
871
+ miny = maxy = tile[1]
872
+
873
+ for tile in tiles:
874
+ x, y = tile[:2]
875
+ minx = min(minx, x)
876
+ maxx = max(maxx, x)
877
+ miny = min(miny, y)
878
+ maxy = max(maxy, y)
879
+
880
+ grid_size = 1+maxx-minx, 1+maxy-miny
881
+
882
+ if self.grid.flipped_y_axis:
883
+ ys = range(miny, maxy+1)
884
+ else:
885
+ ys = range(maxy, miny-1, -1)
886
+ xs = range(minx, maxx+1)
887
+
888
+ bounds = (minx, miny, z), (maxx, maxy, z)
889
+
890
+ return list(_create_tile_list(xs, ys, z, (maxx+1, maxy+1))), grid_size, bounds
891
+
892
+ def main_tile(self, tile_coord):
893
+ x, y, z = tile_coord
894
+
895
+ meta_size = self._meta_size(z)
896
+
897
+ x0 = x//meta_size[0] * meta_size[0]
898
+ y0 = y//meta_size[1] * meta_size[1]
899
+
900
+ return x0, y0, z
901
+
902
+ def tile_list(self, main_tile):
903
+ tile_grid = self._meta_size(main_tile[2])
904
+ return self._meta_tile_list(main_tile, tile_grid)
905
+
906
+ def _meta_tile_list(self, main_tile, tile_grid):
907
+ """
908
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
909
+ >>> mgrid._meta_tile_list((0, 1, 3), (2, 2))
910
+ [(0, 1, 3), (1, 1, 3), (0, 0, 3), (1, 0, 3)]
911
+ """
912
+ minx, miny, z = self.main_tile(main_tile)
913
+ maxx = minx + tile_grid[0] - 1
914
+ maxy = miny + tile_grid[1] - 1
915
+ if self.grid.flipped_y_axis:
916
+ ys = range(miny, maxy+1)
917
+ else:
918
+ ys = range(maxy, miny-1, -1)
919
+ xs = range(minx, maxx+1)
920
+
921
+ return list(_create_tile_list(xs, ys, z, self.grid.grid_sizes[z]))
922
+
923
+ def _tiles_pattern(self, grid_size, buffers, tile=None, tiles=None):
924
+ """
925
+ Returns the tile pattern for the given list of tiles.
926
+ The result contains for each tile the ``tile_coord`` and the upper-left
927
+ pixel coordinate of the tile in the meta tile image.
928
+
929
+ >>> mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2))
930
+ >>> tiles = list(mgrid._tiles_pattern(tiles=[(0, 1, 2), (1, 1, 2)],
931
+ ... grid_size=(2, 1),
932
+ ... buffers=(0, 0, 10, 10)))
933
+ >>> tiles[0], tiles[-1]
934
+ (((0, 1, 2), (0, 10)), ((1, 1, 2), (256, 10)))
935
+
936
+ >>> tiles = list(mgrid._tiles_pattern(tile=(1, 1, 2),
937
+ ... grid_size=(2, 2),
938
+ ... buffers=(10, 20, 30, 40)))
939
+ >>> tiles[0], tiles[-1]
940
+ (((0, 1, 2), (10, 40)), ((1, 0, 2), (266, 296)))
941
+
942
+ """
943
+ if tile:
944
+ tiles = self._meta_tile_list(tile, grid_size)
945
+
946
+ for i in range(grid_size[1]):
947
+ for j in range(grid_size[0]):
948
+ yield tiles[j+i*grid_size[0]], (
949
+ j*self.grid.tile_size[0] + buffers[0],
950
+ i*self.grid.tile_size[1] + buffers[3])
951
+
952
+ def _meta_size(self, level):
953
+ grid_size = self.grid.grid_sizes[level]
954
+ return min(self.meta_size[0], grid_size[0]), min(self.meta_size[1], grid_size[1])
955
+
956
+ def get_affected_level_tiles(self, bbox, level):
957
+ """
958
+ Get a list with all affected tiles for a `bbox` in the given `level`.
959
+
960
+ :returns: the bbox, the size and a list with tile coordinates, sorted row-wise
961
+ :rtype: ``bbox, (xs, yz), [(x, y, z), ...]``
962
+
963
+ >>> grid = MetaGrid(TileGrid(), (2, 2))
964
+ >>> bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34)
965
+ >>> grid.get_affected_level_tiles(bbox, 0)
966
+ ... #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
967
+ ((-20037508.342789244, -20037508.342789244,\
968
+ 20037508.342789244, 20037508.342789244), (1, 1),\
969
+ <generator object ...>)
970
+ """
971
+
972
+ # remove 1/10 of a pixel so we don't get a tiles we only touch
973
+ delta = self.grid.resolutions[level] / 10.0
974
+ x0, y0, _ = self.grid.tile(bbox[0]+delta, bbox[1]+delta, level)
975
+ x1, y1, _ = self.grid.tile(bbox[2]-delta, bbox[3]-delta, level)
976
+
977
+ meta_size = self._meta_size(level)
978
+
979
+ x0 = x0//meta_size[0] * meta_size[0]
980
+ x1 = x1//meta_size[0] * meta_size[0]
981
+ y0 = y0//meta_size[1] * meta_size[1]
982
+ y1 = y1//meta_size[1] * meta_size[1]
983
+
984
+ try:
985
+ return self._tile_iter(x0, y0, x1, y1, level)
986
+ except IndexError:
987
+ raise GridError('Invalid BBOX')
988
+
989
+ def _tile_iter(self, x0, y0, x1, y1, level):
990
+ meta_size = self._meta_size(level)
991
+
992
+ xs = list(range(x0, x1+1, meta_size[0]))
993
+ if self.grid.flipped_y_axis:
994
+ y0, y1 = y1, y0
995
+ ys = list(range(y0, y1+1, meta_size[1]))
996
+ else:
997
+ ys = list(range(y1, y0-1, -meta_size[1]))
998
+
999
+ ll = (xs[0], ys[-1], level)
1000
+ ur = (xs[-1], ys[0], level)
1001
+ # add meta_size to get full affected bbox
1002
+ ur = ur[0]+meta_size[0]-1, ur[1]+meta_size[1]-1, ur[2]
1003
+ abbox = self.grid._tiles_bbox([ll, ur])
1004
+ return (abbox, (len(xs), len(ys)),
1005
+ _create_tile_list(xs, ys, level, self.grid.grid_sizes[level]))
1006
+
1007
+
1008
+ class MetaTile(object):
1009
+ def __init__(self, bbox, size, tile_patterns, grid_size):
1010
+ self.bbox = bbox
1011
+ self.size = size
1012
+ self.tile_patterns = list(tile_patterns)
1013
+ self.grid_size = grid_size
1014
+
1015
+ @property
1016
+ def tiles(self):
1017
+ return [t[0] for t in self.tile_patterns]
1018
+
1019
+ @property
1020
+ def main_tile_coord(self):
1021
+ """
1022
+ Returns the "main" tile of the meta tile. This tile(coord) can be used
1023
+ for locking.
1024
+
1025
+ >>> t = MetaTile(None, None, [((0, 0, 0), (0, 0)), ((1, 0, 0), (100, 0))], (2, 1))
1026
+ >>> t.main_tile_coord
1027
+ (0, 0, 0)
1028
+ >>> t = MetaTile(None, None, [(None, None), ((1, 0, 0), (100, 0))], (2, 1))
1029
+ >>> t.main_tile_coord
1030
+ (1, 0, 0)
1031
+ """
1032
+ for t in self.tiles:
1033
+ if t is not None:
1034
+ return t
1035
+
1036
+ def __repr__(self):
1037
+ return "MetaTile(%r, %r, %r, %r)" % (self.bbox, self.size, self.grid_size,
1038
+ self.tile_patterns)
1039
+
1040
+
1041
+ def bbox_intersects(one, two):
1042
+ a_x0, a_y0, a_x1, a_y1 = one
1043
+ b_x0, b_y0, b_x1, b_y1 = two
1044
+
1045
+ if (
1046
+ a_x0 < b_x1 and
1047
+ a_x1 > b_x0 and
1048
+ a_y0 < b_y1 and
1049
+ a_y1 > b_y0
1050
+ ):
1051
+ return True
1052
+
1053
+ return False
1054
+
1055
+
1056
+ def bbox_contains(one, two):
1057
+ """
1058
+ Returns ``True`` if `one` contains `two`.
1059
+
1060
+ >>> bbox_contains([0, 0, 10, 10], [2, 2, 4, 4])
1061
+ True
1062
+ >>> bbox_contains([0, 0, 10, 10], [0, 0, 11, 10])
1063
+ False
1064
+
1065
+ Allow tiny rounding errors:
1066
+
1067
+ >>> bbox_contains([0, 0, 10, 10], [0.000001, 0.0000001, 10.000001, 10.000001])
1068
+ False
1069
+ >>> bbox_contains([0, 0, 10, 10], [0.0000000000001, 0.0000000000001, 10.0000000000001, 10.0000000000001])
1070
+ True
1071
+ """
1072
+ a_x0, a_y0, a_x1, a_y1 = one
1073
+ b_x0, b_y0, b_x1, b_y1 = two
1074
+
1075
+ x_delta = abs(a_x1 - a_x0) / 10e12
1076
+ y_delta = abs(a_y1 - a_y0) / 10e12
1077
+
1078
+ if (
1079
+ a_x0 <= b_x0 + x_delta and
1080
+ a_x1 >= b_x1 - x_delta and
1081
+ a_y0 <= b_y0 + y_delta and
1082
+ a_y1 >= b_y1 - y_delta
1083
+ ):
1084
+ return True
1085
+
1086
+ return False
1087
+
1088
+
1089
+ def deg_to_m(deg):
1090
+ return deg * (6378137 * 2 * math.pi) / 360
1091
+
1092
+
1093
+ OGC_PIXEL_SIZE = 0.00028 # m/px
1094
+
1095
+
1096
+ def ogc_scale_to_res(scale):
1097
+ return scale * OGC_PIXEL_SIZE
1098
+
1099
+
1100
+ def res_to_ogc_scale(res):
1101
+ return res / OGC_PIXEL_SIZE
1102
+
1103
+
1104
+ def resolution_range(min_res=None, max_res=None, max_scale=None, min_scale=None):
1105
+ if min_scale == max_scale == min_res == max_res is None:
1106
+ return None
1107
+ if min_res or max_res:
1108
+ if not max_scale and not min_scale:
1109
+ return ResolutionRange(min_res, max_res)
1110
+ elif max_scale or min_scale:
1111
+ if not min_res and not max_res:
1112
+ min_res = ogc_scale_to_res(max_scale)
1113
+ max_res = ogc_scale_to_res(min_scale)
1114
+ return ResolutionRange(min_res, max_res)
1115
+
1116
+ raise ValueError('requires either min_res/max_res or max_scale/min_scale')
1117
+
1118
+
1119
+ class ResolutionRange(object):
1120
+ def __init__(self, min_res, max_res):
1121
+ self.min_res = min_res
1122
+ self.max_res = max_res
1123
+
1124
+ if min_res and max_res:
1125
+ assert min_res > max_res
1126
+
1127
+ def scale_denominator(self):
1128
+ min_scale = res_to_ogc_scale(self.max_res) if self.max_res else None
1129
+ max_scale = res_to_ogc_scale(self.min_res) if self.min_res else None
1130
+ return min_scale, max_scale
1131
+
1132
+ def scale_hint(self):
1133
+ """
1134
+ Returns the min and max diagonal resolution.
1135
+ """
1136
+ min_res = self.min_res
1137
+ max_res = self.max_res
1138
+ if min_res:
1139
+ min_res = math.sqrt(2*min_res**2)
1140
+ if max_res:
1141
+ max_res = math.sqrt(2*max_res**2)
1142
+ return min_res, max_res
1143
+
1144
+ def contains(self, bbox, size, srs):
1145
+ width, height = bbox_size(bbox)
1146
+ if srs.is_latlong:
1147
+ width = deg_to_m(width)
1148
+ height = deg_to_m(height)
1149
+
1150
+ x_res = width/size[0]
1151
+ y_res = height/size[1]
1152
+
1153
+ if self.min_res:
1154
+ min_res = self.min_res + 1e-6
1155
+ if min_res <= x_res or min_res <= y_res:
1156
+ return False
1157
+ if self.max_res:
1158
+ max_res = self.max_res
1159
+ if max_res > x_res or max_res > y_res:
1160
+ return False
1161
+
1162
+ return True
1163
+
1164
+ def __eq__(self, other):
1165
+ if not isinstance(other, ResolutionRange):
1166
+ return NotImplemented
1167
+
1168
+ return (self.min_res == other.min_res
1169
+ and self.max_res == other.max_res)
1170
+
1171
+ def __ne__(self, other):
1172
+ if not isinstance(other, ResolutionRange):
1173
+ return NotImplemented
1174
+ return not self == other
1175
+
1176
+ def __repr__(self):
1177
+ return '<ResolutionRange(min_res=%.3f, max_res=%.3f)>' % (
1178
+ self.min_res or 9e99, self.max_res or 0)
1179
+
1180
+
1181
+ def max_with_none(a, b):
1182
+ if a is None or b is None:
1183
+ return None
1184
+ else:
1185
+ return max(a, b)
1186
+
1187
+
1188
+ def min_with_none(a, b):
1189
+ if a is None or b is None:
1190
+ return None
1191
+ else:
1192
+ return min(a, b)
1193
+
1194
+
1195
+ def merge_resolution_range(a, b):
1196
+ if a and b:
1197
+ return resolution_range(min_res=max_with_none(a.min_res, b.min_res),
1198
+ max_res=min_with_none(a.max_res, b.max_res))
1199
+ return None