MapProxy 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. MapProxy-2.1.0.dist-info/AUTHORS.txt +33 -0
  2. MapProxy-2.1.0.dist-info/COPYING.txt +60 -0
  3. MapProxy-2.1.0.dist-info/LICENSE.txt +202 -0
  4. MapProxy-2.1.0.dist-info/METADATA +165 -0
  5. MapProxy-2.1.0.dist-info/RECORD +459 -0
  6. MapProxy-2.1.0.dist-info/WHEEL +5 -0
  7. MapProxy-2.1.0.dist-info/entry_points.txt +4 -0
  8. MapProxy-2.1.0.dist-info/top_level.txt +1 -0
  9. mapproxy/__init__.py +0 -0
  10. mapproxy/cache/__init__.py +36 -0
  11. mapproxy/cache/azureblob.py +145 -0
  12. mapproxy/cache/base.py +120 -0
  13. mapproxy/cache/compact.py +665 -0
  14. mapproxy/cache/couchdb.py +301 -0
  15. mapproxy/cache/dummy.py +36 -0
  16. mapproxy/cache/file.py +200 -0
  17. mapproxy/cache/geopackage.py +647 -0
  18. mapproxy/cache/legend.py +87 -0
  19. mapproxy/cache/mbtiles.py +411 -0
  20. mapproxy/cache/meta.py +80 -0
  21. mapproxy/cache/path.py +261 -0
  22. mapproxy/cache/redis.py +152 -0
  23. mapproxy/cache/renderd.py +100 -0
  24. mapproxy/cache/riak.py +206 -0
  25. mapproxy/cache/s3.py +209 -0
  26. mapproxy/cache/tile.py +736 -0
  27. mapproxy/client/__init__.py +0 -0
  28. mapproxy/client/arcgis.py +82 -0
  29. mapproxy/client/cgi.py +141 -0
  30. mapproxy/client/http.py +295 -0
  31. mapproxy/client/log.py +33 -0
  32. mapproxy/client/tile.py +158 -0
  33. mapproxy/client/wms.py +255 -0
  34. mapproxy/compat/__init__.py +0 -0
  35. mapproxy/compat/image.py +86 -0
  36. mapproxy/config/__init__.py +22 -0
  37. mapproxy/config/config-schema.json +813 -0
  38. mapproxy/config/config.py +213 -0
  39. mapproxy/config/coverage.py +108 -0
  40. mapproxy/config/defaults.py +102 -0
  41. mapproxy/config/loader.py +2399 -0
  42. mapproxy/config/spec.py +657 -0
  43. mapproxy/config/validator.py +242 -0
  44. mapproxy/config_template/__init__.py +0 -0
  45. mapproxy/config_template/base_config/config.wsgi +10 -0
  46. mapproxy/config_template/base_config/full_example.yaml +598 -0
  47. mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
  48. mapproxy/config_template/base_config/log.ini +35 -0
  49. mapproxy/config_template/base_config/mapproxy.yaml +60 -0
  50. mapproxy/config_template/base_config/seed.yaml +27 -0
  51. mapproxy/exception.py +149 -0
  52. mapproxy/featureinfo.py +251 -0
  53. mapproxy/grid.py +1199 -0
  54. mapproxy/image/__init__.py +549 -0
  55. mapproxy/image/fonts/DejaVuSans.ttf +0 -0
  56. mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
  57. mapproxy/image/fonts/LICENSE +99 -0
  58. mapproxy/image/fonts/__init__.py +0 -0
  59. mapproxy/image/mask.py +79 -0
  60. mapproxy/image/merge.py +323 -0
  61. mapproxy/image/message.py +357 -0
  62. mapproxy/image/opts.py +185 -0
  63. mapproxy/image/tile.py +171 -0
  64. mapproxy/image/transform.py +350 -0
  65. mapproxy/layer.py +489 -0
  66. mapproxy/multiapp.py +230 -0
  67. mapproxy/proj.py +309 -0
  68. mapproxy/request/__init__.py +18 -0
  69. mapproxy/request/arcgis.py +268 -0
  70. mapproxy/request/base.py +466 -0
  71. mapproxy/request/tile.py +131 -0
  72. mapproxy/request/wms/__init__.py +829 -0
  73. mapproxy/request/wms/exception.py +107 -0
  74. mapproxy/request/wmts.py +442 -0
  75. mapproxy/response.py +237 -0
  76. mapproxy/script/__init__.py +0 -0
  77. mapproxy/script/conf/__init__.py +0 -0
  78. mapproxy/script/conf/app.py +222 -0
  79. mapproxy/script/conf/caches.py +44 -0
  80. mapproxy/script/conf/geopackage.py +136 -0
  81. mapproxy/script/conf/layers.py +54 -0
  82. mapproxy/script/conf/seeds.py +36 -0
  83. mapproxy/script/conf/sources.py +88 -0
  84. mapproxy/script/conf/utils.py +148 -0
  85. mapproxy/script/defrag.py +187 -0
  86. mapproxy/script/export.py +337 -0
  87. mapproxy/script/grids.py +198 -0
  88. mapproxy/script/scales.py +134 -0
  89. mapproxy/script/util.py +410 -0
  90. mapproxy/script/wms_capabilities.py +160 -0
  91. mapproxy/seed/__init__.py +0 -0
  92. mapproxy/seed/cachelock.py +127 -0
  93. mapproxy/seed/cleanup.py +191 -0
  94. mapproxy/seed/config.py +481 -0
  95. mapproxy/seed/script.py +391 -0
  96. mapproxy/seed/seeder.py +551 -0
  97. mapproxy/seed/spec.py +66 -0
  98. mapproxy/seed/util.py +266 -0
  99. mapproxy/service/__init__.py +14 -0
  100. mapproxy/service/base.py +45 -0
  101. mapproxy/service/demo.py +364 -0
  102. mapproxy/service/kml.py +333 -0
  103. mapproxy/service/ows.py +39 -0
  104. mapproxy/service/template_helper.py +55 -0
  105. mapproxy/service/templates/demo/capabilities_demo.html +18 -0
  106. mapproxy/service/templates/demo/demo.html +181 -0
  107. mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
  108. mapproxy/service/templates/demo/static/img/blank.gif +0 -0
  109. mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
  110. mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
  111. mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
  112. mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
  113. mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
  114. mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
  115. mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
  116. mapproxy/service/templates/demo/static/logo.png +0 -0
  117. mapproxy/service/templates/demo/static/ol.css +345 -0
  118. mapproxy/service/templates/demo/static/ol.js +4 -0
  119. mapproxy/service/templates/demo/static/proj4.min.js +1 -0
  120. mapproxy/service/templates/demo/static/proj4defs.js +1 -0
  121. mapproxy/service/templates/demo/static/site.css +137 -0
  122. mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
  123. mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
  124. mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
  125. mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
  126. mapproxy/service/templates/demo/static.html +34 -0
  127. mapproxy/service/templates/demo/tms_demo.html +117 -0
  128. mapproxy/service/templates/demo/wms_demo.html +144 -0
  129. mapproxy/service/templates/demo/wmts_demo.html +118 -0
  130. mapproxy/service/templates/tms_capabilities.xml +13 -0
  131. mapproxy/service/templates/tms_exception.xml +4 -0
  132. mapproxy/service/templates/tms_root_resource.xml +7 -0
  133. mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
  134. mapproxy/service/templates/wms100capabilities.xml +112 -0
  135. mapproxy/service/templates/wms100exception.xml +4 -0
  136. mapproxy/service/templates/wms110capabilities.xml +152 -0
  137. mapproxy/service/templates/wms110exception.xml +5 -0
  138. mapproxy/service/templates/wms111capabilities.xml +183 -0
  139. mapproxy/service/templates/wms111exception.xml +5 -0
  140. mapproxy/service/templates/wms130capabilities.xml +326 -0
  141. mapproxy/service/templates/wms130exception.xml +8 -0
  142. mapproxy/service/templates/wmts100capabilities.xml +155 -0
  143. mapproxy/service/templates/wmts100exception.xml +9 -0
  144. mapproxy/service/tile.py +540 -0
  145. mapproxy/service/wms.py +868 -0
  146. mapproxy/service/wmts.py +387 -0
  147. mapproxy/source/__init__.py +83 -0
  148. mapproxy/source/arcgis.py +39 -0
  149. mapproxy/source/error.py +40 -0
  150. mapproxy/source/mapnik.py +262 -0
  151. mapproxy/source/tile.py +97 -0
  152. mapproxy/source/wms.py +273 -0
  153. mapproxy/srs.py +734 -0
  154. mapproxy/template.py +54 -0
  155. mapproxy/test/__init__.py +0 -0
  156. mapproxy/test/conftest.py +8 -0
  157. mapproxy/test/helper.py +255 -0
  158. mapproxy/test/http.py +511 -0
  159. mapproxy/test/image.py +219 -0
  160. mapproxy/test/mocker.py +2291 -0
  161. mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
  162. mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
  163. mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
  164. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
  165. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
  166. mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
  167. mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
  168. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
  169. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
  170. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
  171. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
  172. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
  173. mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
  174. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
  175. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
  176. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
  177. mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
  178. mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
  179. mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
  180. mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
  181. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
  182. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
  183. mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
  184. mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
  185. mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
  186. mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
  187. mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
  188. mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
  189. mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
  190. mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
  191. mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
  192. mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
  193. mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
  194. mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
  195. mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
  196. mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
  197. mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
  198. mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
  199. mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
  200. mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
  201. mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
  202. mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
  203. mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
  204. mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
  205. mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
  206. mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
  207. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
  208. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
  209. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
  210. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
  211. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
  212. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
  213. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
  214. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
  215. mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
  216. mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  217. mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
  218. mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  219. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
  220. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
  221. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
  222. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
  223. mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
  224. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
  225. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
  226. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
  227. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
  228. mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
  229. mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  230. mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
  231. mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  232. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
  233. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
  234. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
  235. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
  236. mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
  237. mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
  238. mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
  239. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
  240. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
  241. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
  242. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
  243. mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
  244. mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
  245. mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
  246. mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
  247. mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
  248. mapproxy/test/schemas/xml.xsd +287 -0
  249. mapproxy/test/system/__init__.py +98 -0
  250. mapproxy/test/system/fixture/arcgis.yaml +57 -0
  251. mapproxy/test/system/fixture/auth.yaml +70 -0
  252. mapproxy/test/system/fixture/cache.mbtiles +0 -0
  253. mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
  254. mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
  255. mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
  256. mapproxy/test/system/fixture/cache_coverage.yaml +84 -0
  257. mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
  258. mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
  259. mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
  260. mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
  261. mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
  262. mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
  263. mapproxy/test/system/fixture/cache_s3.yaml +58 -0
  264. mapproxy/test/system/fixture/cache_source.yaml +81 -0
  265. mapproxy/test/system/fixture/combined_sources.yaml +130 -0
  266. mapproxy/test/system/fixture/coverage.yaml +77 -0
  267. mapproxy/test/system/fixture/demo.yaml +135 -0
  268. mapproxy/test/system/fixture/dimension.yaml +59 -0
  269. mapproxy/test/system/fixture/disable_storage.yaml +25 -0
  270. mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
  271. mapproxy/test/system/fixture/formats.yaml +72 -0
  272. mapproxy/test/system/fixture/inspire.yaml +101 -0
  273. mapproxy/test/system/fixture/inspire_full.yaml +124 -0
  274. mapproxy/test/system/fixture/kml_layer.yaml +66 -0
  275. mapproxy/test/system/fixture/layer.yaml +260 -0
  276. mapproxy/test/system/fixture/layergroups.yaml +57 -0
  277. mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
  278. mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
  279. mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
  280. mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
  281. mapproxy/test/system/fixture/mapserver.yaml +23 -0
  282. mapproxy/test/system/fixture/minimal_cgi.py +16 -0
  283. mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
  284. mapproxy/test/system/fixture/multi_cache_layers.yaml +111 -0
  285. mapproxy/test/system/fixture/multiapp1.yaml +20 -0
  286. mapproxy/test/system/fixture/multiapp2.yaml +19 -0
  287. mapproxy/test/system/fixture/renderd_client.yaml +55 -0
  288. mapproxy/test/system/fixture/scalehints.yaml +70 -0
  289. mapproxy/test/system/fixture/seed.yaml +94 -0
  290. mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
  291. mapproxy/test/system/fixture/seed_old.yaml +12 -0
  292. mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
  293. mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
  294. mapproxy/test/system/fixture/seedonly.yaml +51 -0
  295. mapproxy/test/system/fixture/sld.yaml +35 -0
  296. mapproxy/test/system/fixture/source_errors.yaml +84 -0
  297. mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
  298. mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
  299. mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
  300. mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
  301. mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
  302. mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
  303. mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
  304. mapproxy/test/system/fixture/util_grids.yaml +29 -0
  305. mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
  306. mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
  307. mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
  308. mapproxy/test/system/fixture/watermark.yaml +50 -0
  309. mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
  310. mapproxy/test/system/fixture/wms_versions.yaml +38 -0
  311. mapproxy/test/system/fixture/wmts.yaml +134 -0
  312. mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
  313. mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
  314. mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
  315. mapproxy/test/system/test_arcgis.py +156 -0
  316. mapproxy/test/system/test_auth.py +1133 -0
  317. mapproxy/test/system/test_behind_proxy.py +75 -0
  318. mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
  319. mapproxy/test/system/test_cache_azureblob.py +127 -0
  320. mapproxy/test/system/test_cache_band_merge.py +103 -0
  321. mapproxy/test/system/test_cache_coverage.py +168 -0
  322. mapproxy/test/system/test_cache_geopackage.py +144 -0
  323. mapproxy/test/system/test_cache_grid_names.py +89 -0
  324. mapproxy/test/system/test_cache_mbtiles.py +85 -0
  325. mapproxy/test/system/test_cache_s3.py +115 -0
  326. mapproxy/test/system/test_cache_source.py +146 -0
  327. mapproxy/test/system/test_combined_sources.py +335 -0
  328. mapproxy/test/system/test_coverage.py +140 -0
  329. mapproxy/test/system/test_decorate_img.py +214 -0
  330. mapproxy/test/system/test_demo.py +56 -0
  331. mapproxy/test/system/test_demo_with_extra_service.py +57 -0
  332. mapproxy/test/system/test_dimensions.py +279 -0
  333. mapproxy/test/system/test_disable_storage.py +42 -0
  334. mapproxy/test/system/test_formats.py +219 -0
  335. mapproxy/test/system/test_inspire_vs.py +173 -0
  336. mapproxy/test/system/test_kml.py +264 -0
  337. mapproxy/test/system/test_layergroups.py +160 -0
  338. mapproxy/test/system/test_legendgraphic.py +308 -0
  339. mapproxy/test/system/test_mapnik.py +162 -0
  340. mapproxy/test/system/test_mapserver.py +83 -0
  341. mapproxy/test/system/test_mixed_mode_format.py +201 -0
  342. mapproxy/test/system/test_multi_cache_layers.py +169 -0
  343. mapproxy/test/system/test_multiapp.py +92 -0
  344. mapproxy/test/system/test_refresh.py +206 -0
  345. mapproxy/test/system/test_renderd_client.py +304 -0
  346. mapproxy/test/system/test_response_headers.py +54 -0
  347. mapproxy/test/system/test_scalehints.py +140 -0
  348. mapproxy/test/system/test_seed.py +425 -0
  349. mapproxy/test/system/test_seed_only.py +93 -0
  350. mapproxy/test/system/test_sld.py +120 -0
  351. mapproxy/test/system/test_source_errors.py +377 -0
  352. mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
  353. mapproxy/test/system/test_tms.py +277 -0
  354. mapproxy/test/system/test_tms_origin.py +46 -0
  355. mapproxy/test/system/test_util_conf.py +434 -0
  356. mapproxy/test/system/test_util_export.py +210 -0
  357. mapproxy/test/system/test_util_grids.py +88 -0
  358. mapproxy/test/system/test_util_wms_capabilities.py +182 -0
  359. mapproxy/test/system/test_watermark.py +91 -0
  360. mapproxy/test/system/test_wms.py +1616 -0
  361. mapproxy/test/system/test_wms_srs_extent.py +165 -0
  362. mapproxy/test/system/test_wms_version.py +85 -0
  363. mapproxy/test/system/test_wmsc.py +116 -0
  364. mapproxy/test/system/test_wmts.py +334 -0
  365. mapproxy/test/system/test_wmts_dimensions.py +206 -0
  366. mapproxy/test/system/test_wmts_restful.py +198 -0
  367. mapproxy/test/system/test_xslt_featureinfo.py +423 -0
  368. mapproxy/test/test_http_helper.py +217 -0
  369. mapproxy/test/unit/__init__.py +0 -0
  370. mapproxy/test/unit/epsg +2 -0
  371. mapproxy/test/unit/polygons/polygons.dbf +0 -0
  372. mapproxy/test/unit/polygons/polygons.shp +0 -0
  373. mapproxy/test/unit/polygons/polygons.shx +0 -0
  374. mapproxy/test/unit/test_async.py +242 -0
  375. mapproxy/test/unit/test_auth.py +430 -0
  376. mapproxy/test/unit/test_cache.py +1356 -0
  377. mapproxy/test/unit/test_cache_azureblob.py +97 -0
  378. mapproxy/test/unit/test_cache_compact.py +324 -0
  379. mapproxy/test/unit/test_cache_couchdb.py +118 -0
  380. mapproxy/test/unit/test_cache_geopackage.py +256 -0
  381. mapproxy/test/unit/test_cache_redis.py +123 -0
  382. mapproxy/test/unit/test_cache_riak.py +80 -0
  383. mapproxy/test/unit/test_cache_s3.py +93 -0
  384. mapproxy/test/unit/test_cache_tile.py +477 -0
  385. mapproxy/test/unit/test_client.py +488 -0
  386. mapproxy/test/unit/test_client_arcgis.py +74 -0
  387. mapproxy/test/unit/test_client_cgi.py +140 -0
  388. mapproxy/test/unit/test_collections.py +116 -0
  389. mapproxy/test/unit/test_concat_legends.py +37 -0
  390. mapproxy/test/unit/test_conf_loader.py +1267 -0
  391. mapproxy/test/unit/test_conf_validator.py +427 -0
  392. mapproxy/test/unit/test_config.py +118 -0
  393. mapproxy/test/unit/test_decorate_img.py +185 -0
  394. mapproxy/test/unit/test_exceptions.py +270 -0
  395. mapproxy/test/unit/test_featureinfo.py +313 -0
  396. mapproxy/test/unit/test_file_lock_load.py +49 -0
  397. mapproxy/test/unit/test_geom.py +512 -0
  398. mapproxy/test/unit/test_grid.py +1279 -0
  399. mapproxy/test/unit/test_image.py +1051 -0
  400. mapproxy/test/unit/test_image_mask.py +181 -0
  401. mapproxy/test/unit/test_image_messages.py +209 -0
  402. mapproxy/test/unit/test_image_options.py +160 -0
  403. mapproxy/test/unit/test_isodate.py +118 -0
  404. mapproxy/test/unit/test_multiapp.py +163 -0
  405. mapproxy/test/unit/test_ogr_reader.py +51 -0
  406. mapproxy/test/unit/test_request.py +745 -0
  407. mapproxy/test/unit/test_request_wmts.py +178 -0
  408. mapproxy/test/unit/test_response.py +78 -0
  409. mapproxy/test/unit/test_seed.py +365 -0
  410. mapproxy/test/unit/test_seed_cachelock.py +91 -0
  411. mapproxy/test/unit/test_srs.py +215 -0
  412. mapproxy/test/unit/test_tiled_source.py +122 -0
  413. mapproxy/test/unit/test_tilefilter.py +31 -0
  414. mapproxy/test/unit/test_times.py +25 -0
  415. mapproxy/test/unit/test_timeutils.py +50 -0
  416. mapproxy/test/unit/test_util_conf_utils.py +75 -0
  417. mapproxy/test/unit/test_utils.py +476 -0
  418. mapproxy/test/unit/test_wms_capabilities.py +44 -0
  419. mapproxy/test/unit/test_wms_layer.py +113 -0
  420. mapproxy/test/unit/test_yaml.py +68 -0
  421. mapproxy/tilefilter.py +61 -0
  422. mapproxy/util/__init__.py +0 -0
  423. mapproxy/util/async_.py +229 -0
  424. mapproxy/util/collections.py +134 -0
  425. mapproxy/util/coverage.py +337 -0
  426. mapproxy/util/ext/__init__.py +14 -0
  427. mapproxy/util/ext/dictspec/__init__.py +1 -0
  428. mapproxy/util/ext/dictspec/spec.py +131 -0
  429. mapproxy/util/ext/dictspec/test/__init__.py +0 -0
  430. mapproxy/util/ext/dictspec/test/test_validator.py +278 -0
  431. mapproxy/util/ext/dictspec/validator.py +194 -0
  432. mapproxy/util/ext/local.py +198 -0
  433. mapproxy/util/ext/lockfile.py +140 -0
  434. mapproxy/util/ext/odict.py +321 -0
  435. mapproxy/util/ext/serving.py +491 -0
  436. mapproxy/util/ext/tempita/__init__.py +1093 -0
  437. mapproxy/util/ext/tempita/_looper.py +163 -0
  438. mapproxy/util/ext/tempita/string_utils.py +24 -0
  439. mapproxy/util/ext/wmsparse/__init__.py +3 -0
  440. mapproxy/util/ext/wmsparse/duration.py +600 -0
  441. mapproxy/util/ext/wmsparse/parse.py +307 -0
  442. mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
  443. mapproxy/util/ext/wmsparse/test/test_parse.py +111 -0
  444. mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
  445. mapproxy/util/ext/wmsparse/test/wms-example-111.xml +90 -0
  446. mapproxy/util/ext/wmsparse/test/wms-example-130.xml +120 -0
  447. mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
  448. mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
  449. mapproxy/util/ext/wmsparse/util.py +189 -0
  450. mapproxy/util/fs.py +164 -0
  451. mapproxy/util/geom.py +307 -0
  452. mapproxy/util/lib.py +117 -0
  453. mapproxy/util/lock.py +171 -0
  454. mapproxy/util/ogr.py +247 -0
  455. mapproxy/util/py.py +75 -0
  456. mapproxy/util/times.py +78 -0
  457. mapproxy/util/yaml.py +58 -0
  458. mapproxy/version.py +33 -0
  459. mapproxy/wsgiapp.py +167 -0
@@ -0,0 +1,357 @@
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
+ from __future__ import division
17
+
18
+ import logging
19
+ import os
20
+ import importlib_resources
21
+
22
+ from mapproxy.config import base_config, abspath
23
+ from mapproxy.compat.image import Image, ImageColor, ImageDraw, ImageFont
24
+ from mapproxy.image import ImageSource
25
+ from mapproxy.image.opts import create_image, ImageOptions
26
+
27
+ _pil_ttf_support = True
28
+
29
+
30
+ log_system = logging.getLogger('mapproxy.system')
31
+
32
+
33
+ def message_image(message, size, image_opts, bgcolor='#ffffff',
34
+ transparent=False):
35
+ """
36
+ Creates an image with text (`message`). This can be used
37
+ to create in_image exceptions.
38
+
39
+ For dark `bgcolor` the font color is white, otherwise black.
40
+
41
+ :param message: the message to put in the image
42
+ :param size: the size of the output image
43
+ :param format: the output format of the image
44
+ :param bgcolor: the background color of the image
45
+ :param transparent: if True and the `format` supports it,
46
+ return a transparent image
47
+ :rtype: `ImageSource`
48
+ """
49
+ eimg = ExceptionImage(message, image_opts=image_opts)
50
+ return eimg.draw(size=size)
51
+
52
+
53
+ def attribution_image(message, size, image_opts=None, inverse=False):
54
+ """
55
+ Creates an image with text attribution (`message`).
56
+
57
+ :param message: the message to put in the image
58
+ :param size: the size of the output image
59
+ :param format: the output format of the image
60
+ :param inverse: if true, write white text
61
+ :param transparent: if True and the `format` supports it,
62
+ return a transparent image
63
+ :rtype: `ImageSource`
64
+ """
65
+ if image_opts is None:
66
+ image_opts = ImageOptions(transparent=True)
67
+ aimg = AttributionImage(message, image_opts=image_opts,
68
+ inverse=inverse)
69
+ return aimg.draw(size=size)
70
+
71
+
72
+ class MessageImage(object):
73
+ """
74
+ Base class for text rendering in images (for watermarks, exception images, etc.)
75
+
76
+ :ivar font_name: the font name for the text
77
+ :ivar font_size: the font size of the text
78
+ :ivar font_color: the color of the font as a tuple
79
+ :ivar box_color: the color of the box behind the text.
80
+ color as a tuple or ``None``
81
+ """
82
+ font_name = 'DejaVu Sans Mono'
83
+ font_size = 10
84
+ font_color = ImageColor.getrgb('black')
85
+ box_color = None
86
+ linespacing = 5
87
+ padding = 3
88
+ placement = 'ul'
89
+
90
+ def __init__(self, message, image_opts):
91
+ self.message = message
92
+ self.image_opts = image_opts
93
+ self._font = None
94
+
95
+ @property
96
+ def font(self):
97
+ global _pil_ttf_support
98
+ if self._font is None:
99
+ if self.font_name != 'default' and _pil_ttf_support:
100
+ try:
101
+ self._font = ImageFont.truetype(font_file(self.font_name),
102
+ self.font_size)
103
+ except ImportError:
104
+ _pil_ttf_support = False
105
+ log_system.warning("Couldn't load TrueType fonts, "
106
+ "PIL needs to be build with freetype support.")
107
+ except IOError:
108
+ _pil_ttf_support = False
109
+ log_system.warning("Couldn't load find TrueType font ", self.font_name)
110
+ if self._font is None:
111
+ self._font = ImageFont.load_default()
112
+ return self._font
113
+
114
+ def new_image(self, size):
115
+ return Image.new('RGBA', size)
116
+
117
+ def draw(self, img=None, size=None, in_place=True):
118
+ """
119
+ Create the message image. Either draws on top of `img` or creates a
120
+ new image with the given `size`.
121
+ """
122
+ if not ((img and not size) or (size and not img)):
123
+ raise TypeError('need either img or size argument')
124
+
125
+ if img is None:
126
+ base_img = self.new_image(size)
127
+ elif not in_place:
128
+ size = img.size
129
+ base_img = self.new_image(size)
130
+ else:
131
+ base_img = img.as_image()
132
+ size = base_img.size
133
+
134
+ if not self.message:
135
+ if img is not None:
136
+ return img
137
+ return ImageSource(base_img, size=size, image_opts=self.image_opts)
138
+
139
+ draw = ImageDraw.Draw(base_img)
140
+ self.draw_msg(draw, size)
141
+ image_opts = self.image_opts
142
+ if not in_place and img:
143
+ image_opts = image_opts or img.image_opts
144
+ img = img.as_image()
145
+ converted = False
146
+ if len(self.font_color) == 4 and img.mode != 'RGBA':
147
+ # we need RGBA to keep transparency from text
148
+ converted = img.mode
149
+ img = img.convert('RGBA')
150
+ img.paste(base_img, (0, 0), base_img)
151
+ if converted == 'RGB':
152
+ # convert image back
153
+ img = img.convert('RGB')
154
+ base_img = img
155
+
156
+ return ImageSource(base_img, size=size, image_opts=image_opts)
157
+
158
+ def draw_msg(self, draw, size):
159
+ td = TextDraw(self.message, font=self.font, bg_color=self.box_color,
160
+ font_color=self.font_color, placement=self.placement,
161
+ linespacing=self.linespacing, padding=self.padding)
162
+ td.draw(draw, size)
163
+
164
+
165
+ class ExceptionImage(MessageImage):
166
+ """
167
+ Image for exceptions.
168
+ """
169
+ font_name = 'default'
170
+ font_size = 9
171
+
172
+ def __init__(self, message, image_opts):
173
+ MessageImage.__init__(self, message, image_opts=image_opts.copy())
174
+ if not self.image_opts.bgcolor:
175
+ self.image_opts.bgcolor = '#ffffff'
176
+
177
+ def new_image(self, size):
178
+ return create_image(size, self.image_opts)
179
+
180
+ @property
181
+ def font_color(self):
182
+ if self.image_opts.transparent:
183
+ return ImageColor.getrgb('black')
184
+ if _luminance(ImageColor.getrgb(self.image_opts.bgcolor)) < 128:
185
+ return ImageColor.getrgb('white')
186
+ return ImageColor.getrgb('black')
187
+
188
+
189
+ class WatermarkImage(MessageImage):
190
+ """
191
+ Image with large, faded message.
192
+ """
193
+ font_name = 'DejaVu Sans'
194
+ font_size = 24
195
+ font_color = (128, 128, 128)
196
+
197
+ def __init__(self, message, image_opts, placement='c', opacity=None, font_color=None, font_size=None):
198
+ MessageImage.__init__(self, message, image_opts=image_opts)
199
+ if opacity is None:
200
+ opacity = 30
201
+ if font_size:
202
+ self.font_size = font_size
203
+ if font_color:
204
+ self.font_color = font_color
205
+ self.font_color = self.font_color + tuple([opacity])
206
+ self.placement = placement
207
+
208
+ def draw_msg(self, draw, size):
209
+ td = TextDraw(self.message, self.font, self.font_color)
210
+ if self.placement in ('l', 'b'):
211
+ td.placement = 'cL'
212
+ td.draw(draw, size)
213
+ if self.placement in ('r', 'b'):
214
+ td.placement = 'cR'
215
+ td.draw(draw, size)
216
+ if self.placement == 'c':
217
+ td.placement = 'cc'
218
+ td.draw(draw, size)
219
+
220
+
221
+ class AttributionImage(MessageImage):
222
+ """
223
+ Image with attribution information.
224
+ """
225
+ font_name = 'DejaVu Sans'
226
+ font_size = 10
227
+ placement = 'lr'
228
+
229
+ def __init__(self, message, image_opts, inverse=False):
230
+ MessageImage.__init__(self, message, image_opts=image_opts)
231
+ self.inverse = inverse
232
+
233
+ @property
234
+ def font_color(self):
235
+ if self.inverse:
236
+ return ImageColor.getrgb('white')
237
+ else:
238
+ return ImageColor.getrgb('black')
239
+
240
+ @property
241
+ def box_color(self):
242
+ if self.inverse:
243
+ return (0, 0, 0, 100)
244
+ else:
245
+ return (255, 255, 255, 120)
246
+
247
+
248
+ class TextDraw(object):
249
+ def __init__(self, text, font, font_color=None, bg_color=None,
250
+ placement='ul', padding=5, linespacing=3):
251
+ if isinstance(text, str):
252
+ text = text.split('\n')
253
+ self.text = text
254
+ self.font = font
255
+ self.bg_color = bg_color
256
+ self.font_color = font_color
257
+ self.placement = placement
258
+ self.padding = (padding, padding, padding, padding)
259
+ self.linespacing = linespacing
260
+
261
+ def text_boxes(self, draw, size):
262
+ try:
263
+ total_bbox, boxes = self._relative_text_boxes(draw)
264
+ except UnicodeEncodeError:
265
+ # raised if font does not support unicode
266
+ self.text = [x.encode('ascii', 'replace') for x in self.text]
267
+ total_bbox, boxes = self._relative_text_boxes(draw)
268
+ return self._place_boxes(total_bbox, boxes, size)
269
+
270
+ def draw(self, draw, size):
271
+ total_bbox, boxes = self.text_boxes(draw, size)
272
+ if self.bg_color:
273
+ draw.rectangle(
274
+ (total_bbox[0]-self.padding[0],
275
+ total_bbox[1]-self.padding[1],
276
+ total_bbox[2]+self.padding[2],
277
+ total_bbox[3]+self.padding[3]),
278
+ fill=self.bg_color)
279
+
280
+ for text, box in zip(self.text, boxes):
281
+ draw.text((box[0], box[1]), text, font=self.font, fill=self.font_color)
282
+
283
+ def _relative_text_boxes(self, draw):
284
+ total_bbox = (1e9, 1e9, -1e9, -1e9)
285
+ boxes = []
286
+ y_offset = 0
287
+ for i, line in enumerate(self.text):
288
+ try:
289
+ text_box = draw.textbbox((0, y_offset), line, font=self.font)
290
+ y_offset = text_box[3] + self.linespacing
291
+ except AttributeError:
292
+ # Pillow < 8
293
+ text_size = draw.textsize(line, font=self.font)
294
+ text_box = (0, y_offset, text_size[0], text_size[1]+y_offset)
295
+ y_offset += text_size[1] + self.linespacing
296
+ boxes.append(text_box)
297
+ total_bbox = (min(total_bbox[0], text_box[0]),
298
+ min(total_bbox[1], text_box[1]),
299
+ max(total_bbox[2], text_box[2]),
300
+ max(total_bbox[3], text_box[3]),
301
+ )
302
+
303
+ return total_bbox, boxes
304
+
305
+ def _move_bboxes(self, boxes, offsets):
306
+ result = []
307
+ for box in boxes:
308
+ box = box[0]+offsets[0], box[1]+offsets[1], box[2]+offsets[0], box[3]+offsets[1]
309
+ result.append(tuple(int(x) for x in box))
310
+ return result
311
+
312
+ def _place_boxes(self, total_bbox, boxes, size):
313
+ x_offset = y_offset = None
314
+ text_size = (total_bbox[2] - total_bbox[0]), (total_bbox[3] - total_bbox[1])
315
+
316
+ if self.placement[0] == 'u':
317
+ y_offset = self.padding[1]
318
+ elif self.placement[0] == 'l':
319
+ y_offset = size[1] - self.padding[3] - text_size[1]
320
+ elif self.placement[0] == 'c':
321
+ y_offset = size[1] // 2 - text_size[1] // 2
322
+
323
+ if self.placement[1] == 'l':
324
+ x_offset = self.padding[0]
325
+ if self.placement[1] == 'L':
326
+ x_offset = -text_size[0] // 2
327
+ elif self.placement[1] == 'r':
328
+ x_offset = size[0] - self.padding[1] - text_size[0]
329
+ elif self.placement[1] == 'R':
330
+ x_offset = size[0] - text_size[0] // 2
331
+ elif self.placement[1] == 'c':
332
+ x_offset = size[0] // 2 - text_size[0] // 2
333
+
334
+ if x_offset is None or y_offset is None:
335
+ raise ValueError('placement %r not supported' % self.placement)
336
+
337
+ offsets = x_offset, y_offset
338
+ return self._move_bboxes([total_bbox], offsets)[0], self._move_bboxes(boxes, offsets)
339
+
340
+
341
+ def font_file(font_name):
342
+ font_dir = base_config().image.font_dir
343
+ font_name = font_name.replace(' ', '')
344
+ if font_dir:
345
+ abspath(font_dir)
346
+ path = os.path.join(font_dir, font_name + '.ttf')
347
+ else:
348
+ path = str(importlib_resources.files(__package__).joinpath('fonts').joinpath(font_name + '.ttf'))
349
+ return path
350
+
351
+
352
+ def _luminance(color):
353
+ """
354
+ Returns the luminance of a RGB tuple. Uses ITU-R 601-2 luma transform.
355
+ """
356
+ r, g, b = color
357
+ return r * 299/1000 + g * 587/1000 + b * 114/1000
mapproxy/image/opts.py ADDED
@@ -0,0 +1,185 @@
1
+ # -:- encoding: utf-8 -:-
2
+ # This file is part of the MapProxy project.
3
+ # Copyright (C) 2011 Omniscale <http://omniscale.de>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import copy
18
+
19
+
20
+ class ImageOptions(object):
21
+ def __init__(self, mode=None, transparent=None, opacity=None, resampling=None,
22
+ format=None, bgcolor=None, colors=None, encoding_options=None):
23
+ self.transparent = transparent
24
+ self.opacity = opacity
25
+ self.resampling = resampling
26
+ if format is not None:
27
+ format = ImageFormat(format)
28
+ self.format = format
29
+ self.mode = mode
30
+ self.bgcolor = bgcolor
31
+ self.colors = colors
32
+ self.encoding_options = encoding_options or {}
33
+
34
+ def __repr__(self):
35
+ options = []
36
+ for k in dir(self):
37
+ if k.startswith('_'):
38
+ continue
39
+ v = getattr(self, k)
40
+ if v is not None and not hasattr(v, 'im_func') and not hasattr(v, '__func__'):
41
+ options.append('%s=%r' % (k, v))
42
+ return 'ImageOptions(%s)' % (', '.join(options), )
43
+
44
+ def __eq__(self, other):
45
+ if not isinstance(other, ImageOptions):
46
+ return NotImplemented
47
+
48
+ return (
49
+ self.transparent == other.transparent
50
+ and self.opacity == other.opacity
51
+ and self.resampling == other.resampling
52
+ and self.format == other.format
53
+ and self.mode == other.mode
54
+ and self.bgcolor == other.bgcolor
55
+ and self.colors == other.colors
56
+ and self.encoding_options == other.encoding_options
57
+ )
58
+
59
+ def copy(self):
60
+ return copy.copy(self)
61
+
62
+
63
+ class ImageFormat(str):
64
+ def __new__(cls, value, *args, **keywargs):
65
+ if isinstance(value, ImageFormat):
66
+ return value
67
+ return str.__new__(cls, value)
68
+
69
+ @property
70
+ def mime_type(self):
71
+ if self.startswith('image/'):
72
+ return self
73
+ return 'image/' + self
74
+
75
+ @property
76
+ def ext(self):
77
+ ext = self
78
+ if '/' in ext:
79
+ ext = ext.split('/', 1)[1]
80
+ if ';' in ext:
81
+ ext = ext.split(';', 1)[0]
82
+
83
+ return ext.strip()
84
+
85
+ def __eq__(self, other):
86
+ if isinstance(other, str):
87
+ other = ImageFormat(other)
88
+ elif not isinstance(other, ImageFormat):
89
+ return NotImplemented
90
+
91
+ return self.ext == other.ext
92
+
93
+ def __hash__(self):
94
+ return hash(str(self))
95
+
96
+ def __ne__(self, other):
97
+ return not (self == other)
98
+
99
+
100
+ def create_image(size, image_opts=None):
101
+ """
102
+ Create a new image that is compatible with the given `image_opts`.
103
+ Takes into account mode, transparent, bgcolor.
104
+ """
105
+ from mapproxy.compat.image import Image, ImageColor
106
+
107
+ if image_opts is None:
108
+ mode = 'RGB'
109
+ bgcolor = (255, 255, 255)
110
+ else:
111
+ mode = image_opts.mode
112
+ if mode in (None, 'P'):
113
+ if image_opts.transparent:
114
+ mode = 'RGBA'
115
+ else:
116
+ mode = 'RGB'
117
+
118
+ bgcolor = image_opts.bgcolor or (255, 255, 255)
119
+
120
+ if isinstance(bgcolor, str):
121
+ bgcolor = ImageColor.getrgb(bgcolor)
122
+
123
+ if image_opts.transparent and len(bgcolor) == 3:
124
+ bgcolor = bgcolor + (0, )
125
+
126
+ if image_opts.mode == 'I':
127
+ bgcolor = bgcolor[0]
128
+
129
+ return Image.new(mode, size, bgcolor)
130
+
131
+
132
+ class ImageFormats(object):
133
+ def __init__(self):
134
+ self.format_options = {}
135
+
136
+ def add(self, opts):
137
+ assert opts.format is not None
138
+ self.format_options[opts.format] = opts
139
+
140
+ def options(self, format):
141
+ opts = self.format_options.get(format)
142
+ if not opts:
143
+ opts = ImageOptions(transparent=False, format=format)
144
+ return opts
145
+
146
+
147
+ def compatible_image_options(img_opts, base_opts=None):
148
+ """
149
+ Return ImageOptions that is compatible with all given `img_opts`.
150
+
151
+ """
152
+ if any(True for o in img_opts if o.colors == 0):
153
+ colors = 0
154
+ else:
155
+ colors = max(o.colors or 0 for o in img_opts)
156
+
157
+ transparent = None
158
+ for o in img_opts:
159
+ if o.transparent is False:
160
+ transparent = False
161
+ break
162
+ if o.transparent is True:
163
+ transparent = True
164
+
165
+ if any(True for o in img_opts if o.mode):
166
+ # I < P < RGB < RGBA :)
167
+ mode = max(o.mode for o in img_opts if o.mode)
168
+ else:
169
+ mode = None
170
+
171
+ if base_opts:
172
+ options = base_opts.copy()
173
+ if options.colors is None:
174
+ options.colors = colors
175
+ if options.mode is None:
176
+ options.mode = mode
177
+ if options.transparent is None:
178
+ options.transparent = transparent
179
+ else:
180
+ options = img_opts[0].copy()
181
+ options.colors = colors
182
+ options.transparent = transparent
183
+ options.mode = mode
184
+
185
+ return options
mapproxy/image/tile.py ADDED
@@ -0,0 +1,171 @@
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
+ import os
17
+ from mapproxy.image import ImageSource
18
+ from mapproxy.image.transform import ImageTransformer
19
+ from mapproxy.image.opts import create_image
20
+
21
+ import logging
22
+ log = logging.getLogger(__name__)
23
+
24
+
25
+ class TileMerger(object):
26
+ """
27
+ Merge multiple tiles into one image.
28
+ """
29
+
30
+ def __init__(self, tile_grid, tile_size):
31
+ """
32
+ :param tile_grid: the grid size
33
+ :type tile_grid: ``(int(x_tiles), int(y_tiles))``
34
+ :param tile_size: the size of each tile
35
+ """
36
+ self.tile_grid = tile_grid
37
+ self.tile_size = tile_size
38
+
39
+ def merge(self, ordered_tiles, image_opts):
40
+ """
41
+ Merge all tiles into one image.
42
+
43
+ :param ordered_tiles: list of tiles, sorted row-wise (top to bottom)
44
+ :rtype: `ImageSource`
45
+ """
46
+ if self.tile_grid == (1, 1):
47
+ assert len(ordered_tiles) == 1
48
+ if ordered_tiles[0] is not None:
49
+ tile = ordered_tiles.pop()
50
+ return tile
51
+ src_size = self._src_size()
52
+
53
+ result = create_image(src_size, image_opts)
54
+
55
+ cacheable = True
56
+
57
+ for i, source in enumerate(ordered_tiles):
58
+ if source is None:
59
+ continue
60
+ try:
61
+ if not source.cacheable:
62
+ cacheable = False
63
+ tile = source.as_image()
64
+ pos = self._tile_offset(i)
65
+ tile.draft(image_opts.mode, self.tile_size)
66
+ result.paste(tile, pos)
67
+ source.close_buffers()
68
+ except IOError as e:
69
+ if e.errno is None: # PIL error
70
+ log.warning('unable to load tile %s, removing it (reason was: %s)'
71
+ % (source, str(e)))
72
+ if getattr(source, 'filename'):
73
+ if os.path.exists(source.filename):
74
+ os.remove(source.filename)
75
+ else:
76
+ raise
77
+ return ImageSource(result, size=src_size, image_opts=image_opts, cacheable=cacheable)
78
+
79
+ def _src_size(self):
80
+ width = self.tile_grid[0]*self.tile_size[0]
81
+ height = self.tile_grid[1]*self.tile_size[1]
82
+ return width, height
83
+
84
+ def _tile_offset(self, i):
85
+ """
86
+ Return the image offset (upper-left coord) of the i-th tile,
87
+ where the tiles are ordered row-wise, top to bottom.
88
+ """
89
+ return (i % self.tile_grid[0]*self.tile_size[0],
90
+ i//self.tile_grid[0]*self.tile_size[1])
91
+
92
+
93
+ class TileSplitter(object):
94
+ """
95
+ Splits a large image into multiple tiles.
96
+ """
97
+
98
+ def __init__(self, meta_tile, image_opts):
99
+ self.meta_img = meta_tile.as_image()
100
+ self.image_opts = image_opts
101
+
102
+ def get_tile(self, crop_coord, tile_size):
103
+ """
104
+ Return the cropped tile.
105
+ :param crop_coord: the upper left pixel coord to start
106
+ :param tile_size: width and height of the new tile
107
+ :rtype: `ImageSource`
108
+ """
109
+ minx, miny = crop_coord
110
+ maxx = minx + tile_size[0]
111
+ maxy = miny + tile_size[1]
112
+
113
+ if (minx < 0 or miny < 0 or maxx > self.meta_img.size[0]
114
+ or maxy > self.meta_img.size[1]):
115
+
116
+ crop = self.meta_img.crop((
117
+ max(minx, 0),
118
+ max(miny, 0),
119
+ min(maxx, self.meta_img.size[0]),
120
+ min(maxy, self.meta_img.size[1])))
121
+ result = create_image(tile_size, self.image_opts)
122
+ result.paste(crop, (abs(min(minx, 0)), abs(min(miny, 0))))
123
+ crop = result
124
+ else:
125
+ crop = self.meta_img.crop((minx, miny, maxx, maxy))
126
+ return ImageSource(crop, size=tile_size, image_opts=self.image_opts)
127
+
128
+
129
+ class TiledImage(object):
130
+ """
131
+ An image built-up from multiple tiles.
132
+ """
133
+
134
+ def __init__(self, tiles, tile_grid, tile_size, src_bbox, src_srs):
135
+ """
136
+ :param tiles: all tiles (sorted row-wise, top to bottom)
137
+ :param tile_grid: the tile grid size
138
+ :type tile_grid: ``(int(x_tiles), int(y_tiles))``
139
+ :param tile_size: the size of each tile
140
+ :param src_bbox: the bbox of all tiles
141
+ :param src_srs: the srs of the bbox
142
+ :param transparent: if the sources are transparent
143
+ """
144
+ self.tiles = tiles
145
+ self.tile_grid = tile_grid
146
+ self.tile_size = tile_size
147
+ self.src_bbox = src_bbox
148
+ self.src_srs = src_srs
149
+
150
+ def image(self, image_opts):
151
+ """
152
+ Return the tiles as one merged image.
153
+
154
+ :rtype: `ImageSource`
155
+ """
156
+ tm = TileMerger(self.tile_grid, self.tile_size)
157
+ return tm.merge(self.tiles, image_opts=image_opts)
158
+
159
+ def transform(self, req_bbox, req_srs, out_size, image_opts):
160
+ """
161
+ Return the the tiles as one merged and transformed image.
162
+
163
+ :param req_bbox: the bbox of the output image
164
+ :param req_srs: the srs of the req_bbox
165
+ :param out_size: the size in pixel of the output image
166
+ :rtype: `ImageSource`
167
+ """
168
+ transformer = ImageTransformer(self.src_srs, req_srs)
169
+ src_img = self.image(image_opts)
170
+ return transformer.transform(src_img, self.src_bbox, out_size, req_bbox,
171
+ image_opts)