MapProxy 1.16.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. mapproxy/__init__.py +0 -0
  2. mapproxy/cache/__init__.py +36 -0
  3. mapproxy/cache/azureblob.py +145 -0
  4. mapproxy/cache/base.py +111 -0
  5. mapproxy/cache/compact.py +664 -0
  6. mapproxy/cache/couchdb.py +295 -0
  7. mapproxy/cache/dummy.py +34 -0
  8. mapproxy/cache/file.py +185 -0
  9. mapproxy/cache/geopackage.py +609 -0
  10. mapproxy/cache/legend.py +83 -0
  11. mapproxy/cache/mbtiles.py +392 -0
  12. mapproxy/cache/meta.py +78 -0
  13. mapproxy/cache/path.py +250 -0
  14. mapproxy/cache/redis.py +88 -0
  15. mapproxy/cache/renderd.py +95 -0
  16. mapproxy/cache/riak.py +202 -0
  17. mapproxy/cache/s3.py +177 -0
  18. mapproxy/cache/tile.py +699 -0
  19. mapproxy/client/__init__.py +0 -0
  20. mapproxy/client/arcgis.py +79 -0
  21. mapproxy/client/cgi.py +139 -0
  22. mapproxy/client/http.py +315 -0
  23. mapproxy/client/log.py +33 -0
  24. mapproxy/client/tile.py +150 -0
  25. mapproxy/client/wms.py +254 -0
  26. mapproxy/compat/__init__.py +46 -0
  27. mapproxy/compat/image.py +79 -0
  28. mapproxy/compat/itertools.py +29 -0
  29. mapproxy/compat/modules.py +13 -0
  30. mapproxy/config/__init__.py +22 -0
  31. mapproxy/config/config.py +201 -0
  32. mapproxy/config/coverage.py +107 -0
  33. mapproxy/config/defaults.py +98 -0
  34. mapproxy/config/loader.py +2286 -0
  35. mapproxy/config/spec.py +644 -0
  36. mapproxy/config/validator.py +239 -0
  37. mapproxy/config_template/__init__.py +0 -0
  38. mapproxy/config_template/base_config/config.wsgi +10 -0
  39. mapproxy/config_template/base_config/full_example.yaml +593 -0
  40. mapproxy/config_template/base_config/full_seed_example.yaml +79 -0
  41. mapproxy/config_template/base_config/log.ini +35 -0
  42. mapproxy/config_template/base_config/mapproxy.yaml +60 -0
  43. mapproxy/config_template/base_config/seed.yaml +27 -0
  44. mapproxy/exception.py +142 -0
  45. mapproxy/featureinfo.py +252 -0
  46. mapproxy/grid.py +1170 -0
  47. mapproxy/image/__init__.py +536 -0
  48. mapproxy/image/fonts/DejaVuSans.ttf +0 -0
  49. mapproxy/image/fonts/DejaVuSansMono.ttf +0 -0
  50. mapproxy/image/fonts/LICENSE +99 -0
  51. mapproxy/image/fonts/__init__.py +0 -0
  52. mapproxy/image/mask.py +75 -0
  53. mapproxy/image/merge.py +316 -0
  54. mapproxy/image/message.py +347 -0
  55. mapproxy/image/opts.py +182 -0
  56. mapproxy/image/tile.py +167 -0
  57. mapproxy/image/transform.py +350 -0
  58. mapproxy/layer.py +470 -0
  59. mapproxy/multiapp.py +231 -0
  60. mapproxy/proj.py +302 -0
  61. mapproxy/request/__init__.py +18 -0
  62. mapproxy/request/arcgis.py +259 -0
  63. mapproxy/request/base.py +476 -0
  64. mapproxy/request/tile.py +128 -0
  65. mapproxy/request/wms/__init__.py +793 -0
  66. mapproxy/request/wms/exception.py +99 -0
  67. mapproxy/request/wmts.py +436 -0
  68. mapproxy/response.py +237 -0
  69. mapproxy/script/__init__.py +0 -0
  70. mapproxy/script/conf/__init__.py +0 -0
  71. mapproxy/script/conf/app.py +195 -0
  72. mapproxy/script/conf/caches.py +45 -0
  73. mapproxy/script/conf/layers.py +54 -0
  74. mapproxy/script/conf/seeds.py +37 -0
  75. mapproxy/script/conf/sources.py +86 -0
  76. mapproxy/script/conf/utils.py +143 -0
  77. mapproxy/script/defrag.py +184 -0
  78. mapproxy/script/export.py +333 -0
  79. mapproxy/script/grids.py +188 -0
  80. mapproxy/script/scales.py +126 -0
  81. mapproxy/script/util.py +406 -0
  82. mapproxy/script/wms_capabilities.py +152 -0
  83. mapproxy/seed/__init__.py +0 -0
  84. mapproxy/seed/cachelock.py +121 -0
  85. mapproxy/seed/cleanup.py +187 -0
  86. mapproxy/seed/config.py +469 -0
  87. mapproxy/seed/script.py +388 -0
  88. mapproxy/seed/seeder.py +538 -0
  89. mapproxy/seed/spec.py +64 -0
  90. mapproxy/seed/util.py +254 -0
  91. mapproxy/service/__init__.py +14 -0
  92. mapproxy/service/base.py +46 -0
  93. mapproxy/service/demo.py +356 -0
  94. mapproxy/service/kml.py +331 -0
  95. mapproxy/service/ows.py +38 -0
  96. mapproxy/service/template_helper.py +53 -0
  97. mapproxy/service/templates/demo/capabilities_demo.html +16 -0
  98. mapproxy/service/templates/demo/demo.html +181 -0
  99. mapproxy/service/templates/demo/openlayers-demo.cfg +16 -0
  100. mapproxy/service/templates/demo/static/img/blank.gif +0 -0
  101. mapproxy/service/templates/demo/static/img/east-mini.png +0 -0
  102. mapproxy/service/templates/demo/static/img/north-mini.png +0 -0
  103. mapproxy/service/templates/demo/static/img/south-mini.png +0 -0
  104. mapproxy/service/templates/demo/static/img/west-mini.png +0 -0
  105. mapproxy/service/templates/demo/static/img/zoom-minus-mini.png +0 -0
  106. mapproxy/service/templates/demo/static/img/zoom-plus-mini.png +0 -0
  107. mapproxy/service/templates/demo/static/img/zoom-world-mini.png +0 -0
  108. mapproxy/service/templates/demo/static/logo.png +0 -0
  109. mapproxy/service/templates/demo/static/ol.css +345 -0
  110. mapproxy/service/templates/demo/static/ol.js +4 -0
  111. mapproxy/service/templates/demo/static/proj4.min.js +1 -0
  112. mapproxy/service/templates/demo/static/proj4defs.js +1 -0
  113. mapproxy/service/templates/demo/static/site.css +137 -0
  114. mapproxy/service/templates/demo/static/theme/default/framedCloud.css +0 -0
  115. mapproxy/service/templates/demo/static/theme/default/google.css +17 -0
  116. mapproxy/service/templates/demo/static/theme/default/ie6-style.css +10 -0
  117. mapproxy/service/templates/demo/static/theme/default/style.css +482 -0
  118. mapproxy/service/templates/demo/static.html +34 -0
  119. mapproxy/service/templates/demo/tms_demo.html +103 -0
  120. mapproxy/service/templates/demo/wms_demo.html +140 -0
  121. mapproxy/service/templates/demo/wmts_demo.html +110 -0
  122. mapproxy/service/templates/tms_capabilities.xml +13 -0
  123. mapproxy/service/templates/tms_exception.xml +4 -0
  124. mapproxy/service/templates/tms_root_resource.xml +7 -0
  125. mapproxy/service/templates/tms_tilemap_capabilities.xml +14 -0
  126. mapproxy/service/templates/wms100capabilities.xml +112 -0
  127. mapproxy/service/templates/wms100exception.xml +4 -0
  128. mapproxy/service/templates/wms110capabilities.xml +152 -0
  129. mapproxy/service/templates/wms110exception.xml +5 -0
  130. mapproxy/service/templates/wms111capabilities.xml +183 -0
  131. mapproxy/service/templates/wms111exception.xml +5 -0
  132. mapproxy/service/templates/wms130capabilities.xml +326 -0
  133. mapproxy/service/templates/wms130exception.xml +8 -0
  134. mapproxy/service/templates/wmts100capabilities.xml +155 -0
  135. mapproxy/service/templates/wmts100exception.xml +9 -0
  136. mapproxy/service/tile.py +536 -0
  137. mapproxy/service/wms.py +851 -0
  138. mapproxy/service/wmts.py +381 -0
  139. mapproxy/source/__init__.py +75 -0
  140. mapproxy/source/arcgis.py +39 -0
  141. mapproxy/source/error.py +39 -0
  142. mapproxy/source/mapnik.py +259 -0
  143. mapproxy/source/tile.py +96 -0
  144. mapproxy/source/wms.py +270 -0
  145. mapproxy/srs.py +726 -0
  146. mapproxy/template.py +54 -0
  147. mapproxy/test/__init__.py +0 -0
  148. mapproxy/test/conftest.py +7 -0
  149. mapproxy/test/helper.py +247 -0
  150. mapproxy/test/http.py +494 -0
  151. mapproxy/test/image.py +210 -0
  152. mapproxy/test/mocker.py +2268 -0
  153. mapproxy/test/schemas/inspire/common/1.0/common.xsd +1461 -0
  154. mapproxy/test/schemas/inspire/common/1.0/enums/enum_bul.xsd +108 -0
  155. mapproxy/test/schemas/inspire/common/1.0/enums/enum_cze.xsd +108 -0
  156. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dan.xsd +108 -0
  157. mapproxy/test/schemas/inspire/common/1.0/enums/enum_dut.xsd +108 -0
  158. mapproxy/test/schemas/inspire/common/1.0/enums/enum_eng.xsd +155 -0
  159. mapproxy/test/schemas/inspire/common/1.0/enums/enum_est.xsd +108 -0
  160. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fin.xsd +108 -0
  161. mapproxy/test/schemas/inspire/common/1.0/enums/enum_fre.xsd +108 -0
  162. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ger.xsd +108 -0
  163. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gle.xsd +109 -0
  164. mapproxy/test/schemas/inspire/common/1.0/enums/enum_gre.xsd +108 -0
  165. mapproxy/test/schemas/inspire/common/1.0/enums/enum_hun.xsd +108 -0
  166. mapproxy/test/schemas/inspire/common/1.0/enums/enum_ita.xsd +108 -0
  167. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lav.xsd +108 -0
  168. mapproxy/test/schemas/inspire/common/1.0/enums/enum_lit.xsd +108 -0
  169. mapproxy/test/schemas/inspire/common/1.0/enums/enum_mlt.xsd +108 -0
  170. mapproxy/test/schemas/inspire/common/1.0/enums/enum_pol.xsd +108 -0
  171. mapproxy/test/schemas/inspire/common/1.0/enums/enum_por.xsd +108 -0
  172. mapproxy/test/schemas/inspire/common/1.0/enums/enum_rum.xsd +108 -0
  173. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slo.xsd +108 -0
  174. mapproxy/test/schemas/inspire/common/1.0/enums/enum_slv.xsd +108 -0
  175. mapproxy/test/schemas/inspire/common/1.0/enums/enum_spa.xsd +108 -0
  176. mapproxy/test/schemas/inspire/common/1.0/enums/enum_swe.xsd +108 -0
  177. mapproxy/test/schemas/inspire/common/1.0/network.xsd +521 -0
  178. mapproxy/test/schemas/inspire/inspire_vs/1.0/inspire_vs.xsd +19 -0
  179. mapproxy/test/schemas/kml/2.2.0/ReadMe.txt +14 -0
  180. mapproxy/test/schemas/kml/2.2.0/atom-author-link.xsd +66 -0
  181. mapproxy/test/schemas/kml/2.2.0/ogckml22.xsd +1646 -0
  182. mapproxy/test/schemas/kml/2.2.0/xAL.xsd +1680 -0
  183. mapproxy/test/schemas/ows/1.1.0/ReadMe.txt +87 -0
  184. mapproxy/test/schemas/ows/1.1.0/ows19115subset.xsd +235 -0
  185. mapproxy/test/schemas/ows/1.1.0/owsAll.xsd +23 -0
  186. mapproxy/test/schemas/ows/1.1.0/owsCommon.xsd +157 -0
  187. mapproxy/test/schemas/ows/1.1.0/owsContents.xsd +86 -0
  188. mapproxy/test/schemas/ows/1.1.0/owsDataIdentification.xsd +127 -0
  189. mapproxy/test/schemas/ows/1.1.0/owsDomainType.xsd +279 -0
  190. mapproxy/test/schemas/ows/1.1.0/owsExceptionReport.xsd +76 -0
  191. mapproxy/test/schemas/ows/1.1.0/owsGetCapabilities.xsd +112 -0
  192. mapproxy/test/schemas/ows/1.1.0/owsGetResourceByID.xsd +51 -0
  193. mapproxy/test/schemas/ows/1.1.0/owsInputOutputData.xsd +59 -0
  194. mapproxy/test/schemas/ows/1.1.0/owsManifest.xsd +125 -0
  195. mapproxy/test/schemas/ows/1.1.0/owsOperationsMetadata.xsd +140 -0
  196. mapproxy/test/schemas/ows/1.1.0/owsServiceIdentification.xsd +60 -0
  197. mapproxy/test/schemas/ows/1.1.0/owsServiceProvider.xsd +47 -0
  198. mapproxy/test/schemas/sld/1.1.0/sld_capabilities.xsd +27 -0
  199. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.dtd +353 -0
  200. mapproxy/test/schemas/wms/1.0.0/capabilities_1_0_0.xml +188 -0
  201. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.dtd +524 -0
  202. mapproxy/test/schemas/wms/1.0.7/capabilities_1_0_7.xml +260 -0
  203. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.dtd +273 -0
  204. mapproxy/test/schemas/wms/1.1.0/capabilities_1_1_0.xml +303 -0
  205. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.dtd +6 -0
  206. mapproxy/test/schemas/wms/1.1.0/exception_1_1_0.xml +33 -0
  207. mapproxy/test/schemas/wms/1.1.1/OGC-exception.xsd +68 -0
  208. mapproxy/test/schemas/wms/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  209. mapproxy/test/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd +274 -0
  210. mapproxy/test/schemas/wms/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  211. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.dtd +276 -0
  212. mapproxy/test/schemas/wms/1.1.1/capabilities_1_1_1.xml +303 -0
  213. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.dtd +6 -0
  214. mapproxy/test/schemas/wms/1.1.1/exception_1_1_1.xml +33 -0
  215. mapproxy/test/schemas/wms/1.3.0/ReadMe.txt +8 -0
  216. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xml +277 -0
  217. mapproxy/test/schemas/wms/1.3.0/capabilities_1_3_0.xsd +611 -0
  218. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xml +34 -0
  219. mapproxy/test/schemas/wms/1.3.0/exceptions_1_3_0.xsd +28 -0
  220. mapproxy/test/schemas/wmsc/1.1.1/OGC-exception.xsd +68 -0
  221. mapproxy/test/schemas/wmsc/1.1.1/WMS_DescribeLayerResponse.dtd +22 -0
  222. mapproxy/test/schemas/wmsc/1.1.1/WMS_MS_Capabilities.dtd +283 -0
  223. mapproxy/test/schemas/wmsc/1.1.1/WMS_exception_1_1_1.dtd +5 -0
  224. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.dtd +276 -0
  225. mapproxy/test/schemas/wmsc/1.1.1/capabilities_1_1_1.xml +303 -0
  226. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.dtd +6 -0
  227. mapproxy/test/schemas/wmsc/1.1.1/exception_1_1_1.xml +33 -0
  228. mapproxy/test/schemas/wmts/1.0/ReadMe.txt +32 -0
  229. mapproxy/test/schemas/wmts/1.0/wmts.xsd +28 -0
  230. mapproxy/test/schemas/wmts/1.0/wmtsAbstract.wsdl +151 -0
  231. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_request.xsd +38 -0
  232. mapproxy/test/schemas/wmts/1.0/wmtsGetCapabilities_response.xsd +564 -0
  233. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_request.xsd +57 -0
  234. mapproxy/test/schemas/wmts/1.0/wmtsGetFeatureInfo_response.xsd +72 -0
  235. mapproxy/test/schemas/wmts/1.0/wmtsGetTile_request.xsd +91 -0
  236. mapproxy/test/schemas/wmts/1.0/wmtsKVP.xsd +76 -0
  237. mapproxy/test/schemas/wmts/1.0/wmtsPayload_response.xsd +70 -0
  238. mapproxy/test/schemas/xlink/1.0.0/ReadMe.txt +6 -0
  239. mapproxy/test/schemas/xlink/1.0.0/xlinks.xsd +122 -0
  240. mapproxy/test/schemas/xml.xsd +287 -0
  241. mapproxy/test/system/__init__.py +98 -0
  242. mapproxy/test/system/fixture/arcgis.yaml +57 -0
  243. mapproxy/test/system/fixture/auth.yaml +70 -0
  244. mapproxy/test/system/fixture/cache.mbtiles +0 -0
  245. mapproxy/test/system/fixture/cache_azureblob.yaml +59 -0
  246. mapproxy/test/system/fixture/cache_band_merge.yaml +73 -0
  247. mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml +24 -0
  248. mapproxy/test/system/fixture/cache_data/dop_cache_EPSG3857/00/000/000/000/000/000/000.png +0 -0
  249. mapproxy/test/system/fixture/cache_data/wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg +0 -0
  250. mapproxy/test/system/fixture/cache_data/wms_cache_transparent_EPSG900913/01/000/000/000/000/000/001.png +0 -0
  251. mapproxy/test/system/fixture/cache_geopackage.yaml +56 -0
  252. mapproxy/test/system/fixture/cache_grid_names.yaml +50 -0
  253. mapproxy/test/system/fixture/cache_mbtiles.yaml +28 -0
  254. mapproxy/test/system/fixture/cache_s3.yaml +58 -0
  255. mapproxy/test/system/fixture/cache_source.yaml +81 -0
  256. mapproxy/test/system/fixture/combined_sources.yaml +130 -0
  257. mapproxy/test/system/fixture/coverage.yaml +77 -0
  258. mapproxy/test/system/fixture/demo.yaml +135 -0
  259. mapproxy/test/system/fixture/dimension.yaml +59 -0
  260. mapproxy/test/system/fixture/disable_storage.yaml +25 -0
  261. mapproxy/test/system/fixture/empty_ogrdata.geojson +1 -0
  262. mapproxy/test/system/fixture/formats.yaml +72 -0
  263. mapproxy/test/system/fixture/inspire.yaml +101 -0
  264. mapproxy/test/system/fixture/inspire_full.yaml +124 -0
  265. mapproxy/test/system/fixture/kml_layer.yaml +66 -0
  266. mapproxy/test/system/fixture/layer.yaml +260 -0
  267. mapproxy/test/system/fixture/layergroups.yaml +57 -0
  268. mapproxy/test/system/fixture/layergroups_root.yaml +106 -0
  269. mapproxy/test/system/fixture/legendgraphic.yaml +93 -0
  270. mapproxy/test/system/fixture/mapnik_source.yaml +66 -0
  271. mapproxy/test/system/fixture/mapproxy_export.yaml +12 -0
  272. mapproxy/test/system/fixture/mapserver.yaml +23 -0
  273. mapproxy/test/system/fixture/minimal_cgi.py +16 -0
  274. mapproxy/test/system/fixture/mixed_mode.yaml +49 -0
  275. mapproxy/test/system/fixture/multi_cache_layers.yaml +100 -0
  276. mapproxy/test/system/fixture/multiapp1.yaml +20 -0
  277. mapproxy/test/system/fixture/multiapp2.yaml +19 -0
  278. mapproxy/test/system/fixture/renderd_client.yaml +55 -0
  279. mapproxy/test/system/fixture/scalehints.yaml +70 -0
  280. mapproxy/test/system/fixture/seed.yaml +94 -0
  281. mapproxy/test/system/fixture/seed_mapproxy.yaml +39 -0
  282. mapproxy/test/system/fixture/seed_old.yaml +12 -0
  283. mapproxy/test/system/fixture/seed_timeouts.yaml +12 -0
  284. mapproxy/test/system/fixture/seed_timeouts_mapproxy.yaml +27 -0
  285. mapproxy/test/system/fixture/seedonly.yaml +51 -0
  286. mapproxy/test/system/fixture/sld.yaml +35 -0
  287. mapproxy/test/system/fixture/source_errors.yaml +84 -0
  288. mapproxy/test/system/fixture/source_errors_raise.yaml +82 -0
  289. mapproxy/test/system/fixture/tileservice_origin.yaml +26 -0
  290. mapproxy/test/system/fixture/tileservice_refresh.yaml +59 -0
  291. mapproxy/test/system/fixture/tilesource_minmax_res.yaml +22 -0
  292. mapproxy/test/system/fixture/util-conf-base-grids.yaml +5 -0
  293. mapproxy/test/system/fixture/util-conf-overwrite.yaml +13 -0
  294. mapproxy/test/system/fixture/util-conf-wms-111-cap.xml +90 -0
  295. mapproxy/test/system/fixture/util_grids.yaml +30 -0
  296. mapproxy/test/system/fixture/util_wms_capabilities111.xml +130 -0
  297. mapproxy/test/system/fixture/util_wms_capabilities130.xml +100 -0
  298. mapproxy/test/system/fixture/util_wms_capabilities_service_exception.xml +5 -0
  299. mapproxy/test/system/fixture/watermark.yaml +50 -0
  300. mapproxy/test/system/fixture/wms_srs_extent.yaml +39 -0
  301. mapproxy/test/system/fixture/wms_versions.yaml +38 -0
  302. mapproxy/test/system/fixture/wmts.yaml +134 -0
  303. mapproxy/test/system/fixture/wmts_dimensions.yaml +57 -0
  304. mapproxy/test/system/fixture/xslt_featureinfo.yaml +54 -0
  305. mapproxy/test/system/fixture/xslt_featureinfo_input.yaml +51 -0
  306. mapproxy/test/system/test_arcgis.py +156 -0
  307. mapproxy/test/system/test_auth.py +1134 -0
  308. mapproxy/test/system/test_behind_proxy.py +75 -0
  309. mapproxy/test/system/test_bulk_meta_tiles.py +106 -0
  310. mapproxy/test/system/test_cache_azureblob.py +127 -0
  311. mapproxy/test/system/test_cache_band_merge.py +103 -0
  312. mapproxy/test/system/test_cache_geopackage.py +144 -0
  313. mapproxy/test/system/test_cache_grid_names.py +89 -0
  314. mapproxy/test/system/test_cache_mbtiles.py +85 -0
  315. mapproxy/test/system/test_cache_s3.py +115 -0
  316. mapproxy/test/system/test_cache_source.py +146 -0
  317. mapproxy/test/system/test_combined_sources.py +335 -0
  318. mapproxy/test/system/test_coverage.py +140 -0
  319. mapproxy/test/system/test_decorate_img.py +214 -0
  320. mapproxy/test/system/test_demo.py +106 -0
  321. mapproxy/test/system/test_demo_with_extra_service.py +53 -0
  322. mapproxy/test/system/test_dimensions.py +278 -0
  323. mapproxy/test/system/test_disable_storage.py +42 -0
  324. mapproxy/test/system/test_formats.py +219 -0
  325. mapproxy/test/system/test_inspire_vs.py +173 -0
  326. mapproxy/test/system/test_kml.py +262 -0
  327. mapproxy/test/system/test_layergroups.py +160 -0
  328. mapproxy/test/system/test_legendgraphic.py +308 -0
  329. mapproxy/test/system/test_mapnik.py +161 -0
  330. mapproxy/test/system/test_mapserver.py +81 -0
  331. mapproxy/test/system/test_mixed_mode_format.py +195 -0
  332. mapproxy/test/system/test_multi_cache_layers.py +167 -0
  333. mapproxy/test/system/test_multiapp.py +92 -0
  334. mapproxy/test/system/test_refresh.py +207 -0
  335. mapproxy/test/system/test_renderd_client.py +304 -0
  336. mapproxy/test/system/test_response_headers.py +54 -0
  337. mapproxy/test/system/test_scalehints.py +140 -0
  338. mapproxy/test/system/test_seed.py +422 -0
  339. mapproxy/test/system/test_seed_only.py +93 -0
  340. mapproxy/test/system/test_sld.py +120 -0
  341. mapproxy/test/system/test_source_errors.py +377 -0
  342. mapproxy/test/system/test_tilesource_minmax_res.py +54 -0
  343. mapproxy/test/system/test_tms.py +276 -0
  344. mapproxy/test/system/test_tms_origin.py +46 -0
  345. mapproxy/test/system/test_util_conf.py +304 -0
  346. mapproxy/test/system/test_util_export.py +210 -0
  347. mapproxy/test/system/test_util_grids.py +88 -0
  348. mapproxy/test/system/test_util_wms_capabilities.py +182 -0
  349. mapproxy/test/system/test_watermark.py +91 -0
  350. mapproxy/test/system/test_wms.py +1611 -0
  351. mapproxy/test/system/test_wms_srs_extent.py +165 -0
  352. mapproxy/test/system/test_wms_version.py +85 -0
  353. mapproxy/test/system/test_wmsc.py +116 -0
  354. mapproxy/test/system/test_wmts.py +334 -0
  355. mapproxy/test/system/test_wmts_dimensions.py +206 -0
  356. mapproxy/test/system/test_wmts_restful.py +198 -0
  357. mapproxy/test/system/test_xslt_featureinfo.py +425 -0
  358. mapproxy/test/test_http_helper.py +219 -0
  359. mapproxy/test/unit/__init__.py +0 -0
  360. mapproxy/test/unit/epsg +2 -0
  361. mapproxy/test/unit/polygons/polygons.dbf +0 -0
  362. mapproxy/test/unit/polygons/polygons.shp +0 -0
  363. mapproxy/test/unit/polygons/polygons.shx +0 -0
  364. mapproxy/test/unit/test_async.py +245 -0
  365. mapproxy/test/unit/test_auth.py +419 -0
  366. mapproxy/test/unit/test_cache.py +1193 -0
  367. mapproxy/test/unit/test_cache_azureblob.py +94 -0
  368. mapproxy/test/unit/test_cache_compact.py +319 -0
  369. mapproxy/test/unit/test_cache_couchdb.py +114 -0
  370. mapproxy/test/unit/test_cache_geopackage.py +221 -0
  371. mapproxy/test/unit/test_cache_redis.py +67 -0
  372. mapproxy/test/unit/test_cache_riak.py +76 -0
  373. mapproxy/test/unit/test_cache_s3.py +84 -0
  374. mapproxy/test/unit/test_cache_tile.py +427 -0
  375. mapproxy/test/unit/test_client.py +479 -0
  376. mapproxy/test/unit/test_client_arcgis.py +73 -0
  377. mapproxy/test/unit/test_client_cgi.py +136 -0
  378. mapproxy/test/unit/test_collections.py +116 -0
  379. mapproxy/test/unit/test_concat_legends.py +37 -0
  380. mapproxy/test/unit/test_conf_loader.py +1061 -0
  381. mapproxy/test/unit/test_conf_validator.py +416 -0
  382. mapproxy/test/unit/test_config.py +117 -0
  383. mapproxy/test/unit/test_decorate_img.py +185 -0
  384. mapproxy/test/unit/test_exceptions.py +258 -0
  385. mapproxy/test/unit/test_featureinfo.py +291 -0
  386. mapproxy/test/unit/test_file_lock_load.py +49 -0
  387. mapproxy/test/unit/test_geom.py +503 -0
  388. mapproxy/test/unit/test_grid.py +1258 -0
  389. mapproxy/test/unit/test_image.py +1053 -0
  390. mapproxy/test/unit/test_image_mask.py +181 -0
  391. mapproxy/test/unit/test_image_messages.py +197 -0
  392. mapproxy/test/unit/test_image_options.py +160 -0
  393. mapproxy/test/unit/test_isodate.py +122 -0
  394. mapproxy/test/unit/test_multiapp.py +163 -0
  395. mapproxy/test/unit/test_ogr_reader.py +50 -0
  396. mapproxy/test/unit/test_request.py +745 -0
  397. mapproxy/test/unit/test_request_wmts.py +178 -0
  398. mapproxy/test/unit/test_response.py +79 -0
  399. mapproxy/test/unit/test_seed.py +365 -0
  400. mapproxy/test/unit/test_seed_cachelock.py +90 -0
  401. mapproxy/test/unit/test_srs.py +215 -0
  402. mapproxy/test/unit/test_tiled_source.py +122 -0
  403. mapproxy/test/unit/test_tilefilter.py +31 -0
  404. mapproxy/test/unit/test_times.py +25 -0
  405. mapproxy/test/unit/test_timeutils.py +50 -0
  406. mapproxy/test/unit/test_util_conf_utils.py +75 -0
  407. mapproxy/test/unit/test_utils.py +476 -0
  408. mapproxy/test/unit/test_wms_capabilities.py +44 -0
  409. mapproxy/test/unit/test_wms_layer.py +113 -0
  410. mapproxy/test/unit/test_yaml.py +69 -0
  411. mapproxy/tilefilter.py +59 -0
  412. mapproxy/util/__init__.py +0 -0
  413. mapproxy/util/async_.py +227 -0
  414. mapproxy/util/collections.py +132 -0
  415. mapproxy/util/coverage.py +329 -0
  416. mapproxy/util/escape.py +10 -0
  417. mapproxy/util/ext/__init__.py +14 -0
  418. mapproxy/util/ext/dictspec/__init__.py +1 -0
  419. mapproxy/util/ext/dictspec/spec.py +124 -0
  420. mapproxy/util/ext/dictspec/test/__init__.py +0 -0
  421. mapproxy/util/ext/dictspec/test/test_validator.py +274 -0
  422. mapproxy/util/ext/dictspec/validator.py +189 -0
  423. mapproxy/util/ext/local.py +196 -0
  424. mapproxy/util/ext/lockfile.py +138 -0
  425. mapproxy/util/ext/odict.py +330 -0
  426. mapproxy/util/ext/serving.py +508 -0
  427. mapproxy/util/ext/tempita/__init__.py +1174 -0
  428. mapproxy/util/ext/tempita/_looper.py +163 -0
  429. mapproxy/util/ext/tempita/compat3.py +46 -0
  430. mapproxy/util/ext/wmsparse/__init__.py +3 -0
  431. mapproxy/util/ext/wmsparse/duration.py +597 -0
  432. mapproxy/util/ext/wmsparse/parse.py +305 -0
  433. mapproxy/util/ext/wmsparse/test/__init__.py +0 -0
  434. mapproxy/util/ext/wmsparse/test/test_parse.py +162 -0
  435. mapproxy/util/ext/wmsparse/test/test_util.py +23 -0
  436. mapproxy/util/ext/wmsparse/test/wms-large-111.xml +2114 -0
  437. mapproxy/util/ext/wmsparse/test/wms-omniscale-111.xml +90 -0
  438. mapproxy/util/ext/wmsparse/test/wms-omniscale-130.xml +120 -0
  439. mapproxy/util/ext/wmsparse/test/wms_nasa_cap.xml +386 -0
  440. mapproxy/util/ext/wmsparse/util.py +187 -0
  441. mapproxy/util/fs.py +156 -0
  442. mapproxy/util/geom.py +295 -0
  443. mapproxy/util/lib.py +115 -0
  444. mapproxy/util/lock.py +163 -0
  445. mapproxy/util/ogr.py +231 -0
  446. mapproxy/util/py.py +81 -0
  447. mapproxy/util/times.py +75 -0
  448. mapproxy/util/yaml.py +56 -0
  449. mapproxy/version.py +31 -0
  450. mapproxy/wsgiapp.py +164 -0
  451. mapproxy-1.16.1.dist-info/METADATA +151 -0
  452. mapproxy-1.16.1.dist-info/RECORD +458 -0
  453. mapproxy-1.16.1.dist-info/WHEEL +5 -0
  454. mapproxy-1.16.1.dist-info/entry_points.txt +3 -0
  455. mapproxy-1.16.1.dist-info/licenses/AUTHORS.txt +33 -0
  456. mapproxy-1.16.1.dist-info/licenses/COPYING.txt +60 -0
  457. mapproxy-1.16.1.dist-info/licenses/LICENSE.txt +202 -0
  458. mapproxy-1.16.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2286 @@
1
+ # This file is part of the MapProxy project.
2
+ # Copyright (C) 2010-2016 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
+ Configuration loading and system initializing.
18
+ """
19
+ from __future__ import division
20
+
21
+ import os
22
+ import sys
23
+ import hashlib
24
+ import warnings
25
+ from copy import deepcopy, copy
26
+ from functools import partial
27
+
28
+ import logging
29
+ log = logging.getLogger('mapproxy.config')
30
+
31
+ from mapproxy.config import load_default_config, finish_base_config, defaults
32
+ from mapproxy.config.validator import validate_references
33
+ from mapproxy.config.spec import validate_options, add_source_to_mapproxy_yaml_spec, add_service_to_mapproxy_yaml_spec
34
+ from mapproxy.util.py import memoize
35
+ from mapproxy.util.ext.odict import odict
36
+ from mapproxy.util.yaml import load_yaml_file, YAMLError
37
+ from mapproxy.util.fs import find_exec
38
+ from mapproxy.compat.modules import urlparse
39
+ from mapproxy.compat import string_type, iteritems
40
+
41
+ class ConfigurationError(Exception):
42
+ pass
43
+
44
+
45
+ class ProxyConfiguration(object):
46
+ def __init__(self, conf, conf_base_dir=None, seed=False, renderd=False):
47
+ self.configuration = conf
48
+ self.seed = seed
49
+ self.renderd = renderd
50
+
51
+ if conf_base_dir is None:
52
+ conf_base_dir = os.getcwd()
53
+
54
+ self.load_globals(conf_base_dir=conf_base_dir)
55
+ self.load_grids()
56
+ self.load_caches()
57
+ self.load_sources()
58
+ self.load_wms_root_layer()
59
+ self.load_tile_layers()
60
+ self.load_services()
61
+
62
+ def load_globals(self, conf_base_dir):
63
+ self.globals = GlobalConfiguration(conf_base_dir=conf_base_dir,
64
+ conf=self.configuration.get('globals') or {},
65
+ context=self)
66
+
67
+ def load_grids(self):
68
+ self.grids = {}
69
+ grid_configs = dict(defaults.grids)
70
+ grid_configs.update(self.configuration.get('grids') or {})
71
+ for grid_name, grid_conf in iteritems(grid_configs):
72
+ grid_conf.setdefault('name', grid_name)
73
+ self.grids[grid_name] = GridConfiguration(grid_conf, context=self)
74
+
75
+ def load_caches(self):
76
+ self.caches = odict()
77
+ caches_conf = self.configuration.get('caches')
78
+ if not caches_conf: return
79
+ if isinstance(caches_conf, list):
80
+ caches_conf = list_of_dicts_to_ordered_dict(caches_conf)
81
+ for cache_name, cache_conf in iteritems(caches_conf):
82
+ cache_conf['name'] = cache_name
83
+ self.caches[cache_name] = CacheConfiguration(conf=cache_conf, context=self)
84
+
85
+ def load_sources(self):
86
+ self.sources = SourcesCollection()
87
+ for source_name, source_conf in iteritems((self.configuration.get('sources') or {})):
88
+ source_conf['name'] = source_name
89
+ self.sources[source_name] = SourceConfiguration.load(conf=source_conf, context=self)
90
+
91
+ def load_tile_layers(self):
92
+ self.layers = odict()
93
+ layers_conf = deepcopy(self._layers_conf_dict())
94
+ if layers_conf is None: return
95
+ layers = self._flatten_layers_conf_dict(layers_conf)
96
+ for layer_name, layer_conf in iteritems(layers):
97
+ layer_conf['name'] = layer_name
98
+ self.layers[layer_name] = LayerConfiguration(conf=layer_conf, context=self)
99
+
100
+ def _legacy_layers_conf_dict(self):
101
+ """
102
+ Read old style layer configuration with a dictionary where
103
+ the key is the layer name. Optionally: a list an each layer
104
+ is wrapped in such dictionary.
105
+
106
+ ::
107
+ layers:
108
+ foo:
109
+ title: xxx
110
+ sources: []
111
+ bar:
112
+ title: xxx
113
+ sources: []
114
+
115
+ or
116
+
117
+ ::
118
+
119
+ layers:
120
+ - foo:
121
+ title: xxx
122
+ sources: []
123
+ - bar:
124
+ title: xxx
125
+ sources: []
126
+
127
+ """
128
+ warnings.warn('old layer configuration syntax is deprecated since 1.4.0. '
129
+ 'use list of dictionaries as documented', RuntimeWarning)
130
+ layers = []
131
+ layers_conf = self.configuration.get('layers')
132
+ if not layers_conf: return None # TODO config error
133
+ if isinstance(layers_conf, list):
134
+ layers_conf = list_of_dicts_to_ordered_dict(layers_conf)
135
+ for layer_name, layer_conf in iteritems(layers_conf):
136
+ layer_conf['name'] = layer_name
137
+ layers.append(layer_conf)
138
+ return dict(title=None, layers=layers)
139
+
140
+
141
+ def _layers_conf_dict(self):
142
+ """
143
+ Returns (recursive) layer configuration as a dictionary
144
+ in unified structure:
145
+
146
+ ::
147
+ {
148
+ title: 'xxx', # required, might be None
149
+ name: 'xxx', # optional
150
+ # sources or layers or both are required
151
+ sources: [],
152
+ layers: [
153
+ {..., ...} # more layers like this
154
+ ]
155
+ }
156
+
157
+ Multiple layers will be wrapped in an unnamed root layer, if the
158
+ first level starts with multiple layers.
159
+ """
160
+ layers_conf = self.configuration.get('layers')
161
+ if layers_conf is None: return
162
+
163
+ if isinstance(layers_conf, list):
164
+ if isinstance(layers_conf[0], dict) and len(layers_conf[0].keys()) == 1:
165
+ # looks like ordered legacy config
166
+ layers_conf = self._legacy_layers_conf_dict()
167
+ elif len(layers_conf) == 1 and (
168
+ 'layers' in layers_conf[0]
169
+ or 'sources' in layers_conf[0]
170
+ or 'tile_sources' in layers_conf[0]):
171
+ # single root layer in list -> remove list
172
+ layers_conf = layers_conf[0]
173
+ else:
174
+ # layer list without root -> wrap in root layer
175
+ layers_conf = dict(title=None, layers=layers_conf)
176
+
177
+ if len(set(layers_conf.keys()) &
178
+ set('layers name title sources'.split())) < 2:
179
+ # looks like unordered legacy config
180
+ layers_conf = self._legacy_layers_conf_dict()
181
+
182
+ return layers_conf
183
+
184
+ def _flatten_layers_conf_dict(self, layers_conf, _layers=None):
185
+ """
186
+ Returns a dictionary with all layers that have a name and sources.
187
+ Flattens the layer tree.
188
+ """
189
+ layers = _layers if _layers is not None else odict()
190
+
191
+ if 'layers' in layers_conf:
192
+ for layer in layers_conf.pop('layers'):
193
+ self._flatten_layers_conf_dict(layer, layers)
194
+
195
+ if 'name' in layers_conf and ('sources' in layers_conf or 'tile_sources' in layers_conf):
196
+ layers[layers_conf['name']] = layers_conf
197
+
198
+ return layers
199
+
200
+
201
+ def load_wms_root_layer(self):
202
+ self.wms_root_layer = None
203
+
204
+ layers_conf = self._layers_conf_dict()
205
+ if layers_conf is None: return
206
+ self.wms_root_layer = WMSLayerConfiguration(layers_conf, context=self)
207
+
208
+ def load_services(self):
209
+ self.services = ServiceConfiguration(self.configuration.get('services', {}), context=self)
210
+
211
+ def configured_services(self):
212
+ with self:
213
+ return self.services.services()
214
+
215
+ def __enter__(self):
216
+ # push local base_config onto config stack
217
+ import mapproxy.config.config
218
+ mapproxy.config.config._config.push(self.base_config)
219
+
220
+ def __exit__(self, type, value, traceback):
221
+ # pop local base_config from config stack
222
+ import mapproxy.config.config
223
+ mapproxy.config.config._config.pop()
224
+
225
+ @property
226
+ def base_config(self):
227
+ return self.globals.base_config
228
+
229
+ def config_files(self):
230
+ """
231
+ Returns a dictionary with all configuration filenames and there timestamps.
232
+ Contains any included files as well (see `base` option).
233
+ """
234
+ return self.configuration.get('__config_files__', {})
235
+
236
+ def list_of_dicts_to_ordered_dict(dictlist):
237
+ """
238
+ >>> d = list_of_dicts_to_ordered_dict([{'a': 1}, {'b': 2}, {'c': 3}])
239
+ >>> list(d.items())
240
+ [('a', 1), ('b', 2), ('c', 3)]
241
+ """
242
+
243
+ result = odict()
244
+ for d in dictlist:
245
+ for k, v in iteritems(d):
246
+ result[k] = v
247
+ return result
248
+
249
+ class ConfigurationBase(object):
250
+ """
251
+ Base class for all configurations.
252
+ """
253
+ defaults = {}
254
+
255
+ def __init__(self, conf, context):
256
+ """
257
+ :param conf: the configuration part for this configurator
258
+ :param context: the complete proxy configuration
259
+ :type context: ProxyConfiguration
260
+ """
261
+ self.conf = conf
262
+ self.context = context
263
+ for k, v in iteritems(self.defaults):
264
+ if k not in self.conf:
265
+ self.conf[k] = v
266
+
267
+ class GridConfiguration(ConfigurationBase):
268
+ @memoize
269
+ def tile_grid(self):
270
+ from mapproxy.grid import tile_grid
271
+
272
+ if 'base' in self.conf:
273
+ base_grid_name = self.conf['base']
274
+ if not base_grid_name in self.context.grids:
275
+ raise ConfigurationError('unknown base %s for grid %s' % (base_grid_name, self.conf['name']))
276
+ conf = self.context.grids[base_grid_name].conf.copy()
277
+ conf.update(self.conf)
278
+ conf.pop('base')
279
+ self.conf = conf
280
+ else:
281
+ conf = self.conf
282
+ align_with = None
283
+ if 'align_resolutions_with' in self.conf:
284
+ align_with_grid_name = self.conf['align_resolutions_with']
285
+ align_with = self.context.grids[align_with_grid_name].tile_grid()
286
+
287
+ tile_size = self.context.globals.get_value('tile_size', conf,
288
+ global_key='grid.tile_size')
289
+ conf['tile_size'] = tuple(tile_size)
290
+ tile_size = tuple(tile_size)
291
+
292
+ stretch_factor = self.context.globals.get_value('stretch_factor', conf,
293
+ global_key='image.stretch_factor')
294
+ max_shrink_factor = self.context.globals.get_value('max_shrink_factor', conf,
295
+ global_key='image.max_shrink_factor')
296
+
297
+ if conf.get('origin') is None:
298
+ log.warning('grid %s does not have an origin. default origin will change from sw (south/west) to nw (north-west) with MapProxy 2.0',
299
+ conf['name'],
300
+ )
301
+
302
+ grid = tile_grid(
303
+ name=conf['name'],
304
+ srs=conf.get('srs'),
305
+ tile_size=tile_size,
306
+ min_res=conf.get('min_res'),
307
+ max_res=conf.get('max_res'),
308
+ res=conf.get('res'),
309
+ res_factor=conf.get('res_factor', 2.0),
310
+ threshold_res=conf.get('threshold_res'),
311
+ bbox=conf.get('bbox'),
312
+ bbox_srs=conf.get('bbox_srs'),
313
+ num_levels=conf.get('num_levels'),
314
+ stretch_factor=stretch_factor,
315
+ max_shrink_factor=max_shrink_factor,
316
+ align_with=align_with,
317
+ origin=conf.get('origin')
318
+ )
319
+
320
+ return grid
321
+
322
+
323
+ def preferred_srs(conf):
324
+ from mapproxy.srs import SRS, PreferredSrcSRS
325
+
326
+ preferred_conf = conf.get('preferred_src_proj', {})
327
+
328
+ if not preferred_conf:
329
+ return
330
+
331
+ preferred = PreferredSrcSRS()
332
+
333
+ for target, preferred_srcs in preferred_conf.items():
334
+ preferred.add(SRS(target), [SRS(s) for s in preferred_srcs])
335
+
336
+ return preferred
337
+
338
+
339
+ class GlobalConfiguration(ConfigurationBase):
340
+ def __init__(self, conf_base_dir, conf, context):
341
+ ConfigurationBase.__init__(self, conf, context)
342
+ self.base_config = load_default_config()
343
+ self._copy_conf_values(self.conf, self.base_config)
344
+ self.base_config.conf_base_dir = conf_base_dir
345
+ finish_base_config(self.base_config)
346
+
347
+ self.image_options = ImageOptionsConfiguration(self.conf.get('image', {}), context)
348
+ self.preferred_srs = preferred_srs(self.conf.get('srs', {}))
349
+ self.renderd_address = self.get_value('renderd.address')
350
+
351
+ def _copy_conf_values(self, d, target):
352
+ for k, v in iteritems(d):
353
+ if v is None: continue
354
+ if (hasattr(v, 'iteritems') or hasattr(v, 'items')) and k in target:
355
+ self._copy_conf_values(v, target[k])
356
+ else:
357
+ target[k] = v
358
+
359
+ def get_value(self, key, local={}, global_key=None, default_key=None):
360
+ result = dotted_dict_get(key, local)
361
+ if result is None:
362
+ result = dotted_dict_get(global_key or key, self.conf)
363
+
364
+ if result is None:
365
+ result = dotted_dict_get(default_key or global_key or key, self.base_config)
366
+
367
+ return result
368
+
369
+ def get_path(self, key, local, global_key=None, default_key=None):
370
+ value = self.get_value(key, local, global_key, default_key)
371
+ if value is not None:
372
+ value = self.abspath(value)
373
+ return value
374
+
375
+ def abspath(self, path):
376
+ return os.path.join(self.base_config.conf_base_dir, path)
377
+
378
+
379
+
380
+ default_image_options = {
381
+ }
382
+
383
+ class ImageOptionsConfiguration(ConfigurationBase):
384
+ def __init__(self, conf, context):
385
+ ConfigurationBase.__init__(self, conf, context)
386
+ self._init_formats()
387
+
388
+ def _init_formats(self):
389
+ self.formats = {}
390
+
391
+ formats_config = default_image_options.copy()
392
+ for format, conf in iteritems(self.conf.get('formats', {})):
393
+ if format in formats_config:
394
+ tmp = formats_config[format].copy()
395
+ tmp.update(conf)
396
+ conf = tmp
397
+ if 'resampling_method' in conf:
398
+ conf['resampling'] = conf.pop('resampling_method')
399
+ if 'encoding_options' in conf:
400
+ self._check_encoding_options(conf['encoding_options'])
401
+ if 'merge_method' in conf:
402
+ warnings.warn('merge_method now defaults to composite. option no longer required',
403
+ DeprecationWarning)
404
+ formats_config[format] = conf
405
+ for format, conf in iteritems(formats_config):
406
+ if 'format' not in conf and format.startswith('image/'):
407
+ conf['format'] = format
408
+ self.formats[format] = conf
409
+
410
+ def _check_encoding_options(self, options):
411
+ if not options:
412
+ return
413
+ options = options.copy()
414
+ jpeg_quality = options.pop('jpeg_quality', None)
415
+ if jpeg_quality and not isinstance(jpeg_quality, int):
416
+ raise ConfigurationError('jpeg_quality is not an integer')
417
+
418
+ tiff_compression = options.pop('tiff_compression', None)
419
+ if tiff_compression and tiff_compression not in ('raw', 'tiff_lzw', 'jpeg'):
420
+ raise ConfigurationError('unknown tiff_compression')
421
+
422
+ quantizer = options.pop('quantizer', None)
423
+ if quantizer and quantizer not in ('fastoctree', 'mediancut'):
424
+ raise ConfigurationError('unknown quantizer')
425
+
426
+ if options:
427
+ raise ConfigurationError('unknown encoding_options: %r' % options)
428
+
429
+ def image_opts(self, image_conf, format):
430
+ from mapproxy.image.opts import ImageOptions
431
+ if not image_conf:
432
+ image_conf = {}
433
+
434
+ conf = {}
435
+ if format in self.formats:
436
+ conf = self.formats[format].copy()
437
+
438
+ resampling = image_conf.get('resampling_method') or conf.get('resampling')
439
+ if resampling is None:
440
+ resampling = self.context.globals.get_value('image.resampling_method', {})
441
+ transparent = image_conf.get('transparent')
442
+ opacity = image_conf.get('opacity')
443
+ img_format = image_conf.get('format')
444
+ colors = image_conf.get('colors')
445
+ mode = image_conf.get('mode')
446
+ encoding_options = image_conf.get('encoding_options')
447
+ if 'merge_method' in image_conf:
448
+ warnings.warn('merge_method now defaults to composite. option no longer required',
449
+ DeprecationWarning)
450
+
451
+ self._check_encoding_options(encoding_options)
452
+
453
+ # only overwrite default if it is not None
454
+ for k, v in iteritems(dict(transparent=transparent, opacity=opacity, resampling=resampling,
455
+ format=img_format, colors=colors, mode=mode, encoding_options=encoding_options,
456
+ )):
457
+ if v is not None:
458
+ conf[k] = v
459
+
460
+ if 'format' not in conf and format and format.startswith('image/'):
461
+ conf['format'] = format
462
+
463
+ # caches shall be able to store png and jpeg tiles with mixed format
464
+ if format == 'mixed':
465
+ conf['format'] = format
466
+ conf['transparent'] = True
467
+
468
+ # force 256 colors for image.paletted for backwards compat
469
+ paletted = self.context.globals.get_value('image.paletted', self.conf)
470
+ if conf.get('colors') is None and 'png' in conf.get('format', '') and paletted:
471
+ conf['colors'] = 256
472
+
473
+ opts = ImageOptions(**conf)
474
+ return opts
475
+
476
+
477
+ def dotted_dict_get(key, d):
478
+ """
479
+ >>> dotted_dict_get('foo', {'foo': {'bar': 1}})
480
+ {'bar': 1}
481
+ >>> dotted_dict_get('foo.bar', {'foo': {'bar': 1}})
482
+ 1
483
+ >>> dotted_dict_get('bar', {'foo': {'bar': 1}})
484
+ """
485
+ parts = key.split('.')
486
+ try:
487
+ while parts and d:
488
+ d = d[parts.pop(0)]
489
+ except KeyError:
490
+ return None
491
+ if parts: # not completely resolved
492
+ return None
493
+ return d
494
+
495
+
496
+ class SourcesCollection(dict):
497
+ """
498
+ Collection of SourceConfigurations.
499
+ Allows access to tagged WMS sources, e.g.
500
+ ``sc['source_name:lyr,lyr2']`` will return the source with ``source_name``
501
+ and set ``req.layers`` to ``lyr1,lyr2``.
502
+ """
503
+ def __getitem__(self, key):
504
+ layers = None
505
+ source_name = key
506
+ if ':' in source_name:
507
+ source_name, layers = source_name.split(':', 1)
508
+ source = dict.__getitem__(self, source_name)
509
+ if not layers:
510
+ return source
511
+
512
+ if source.conf.get('type') not in ('wms', 'mapserver', 'mapnik'):
513
+ raise ConfigurationError("found ':' in: '%s'."
514
+ " tagged sources only supported for WMS/Mapserver/Mapnik" % key)
515
+
516
+ uses_req = source.conf.get('type') != 'mapnik'
517
+
518
+ source = copy(source)
519
+ source.conf = deepcopy(source.conf)
520
+
521
+ if uses_req:
522
+ supported_layers = source.conf['req'].get('layers', [])
523
+ else:
524
+ supported_layers = source.conf.get('layers', [])
525
+ supported_layer_set = SourcesCollection.layer_set(supported_layers)
526
+ layer_set = SourcesCollection.layer_set(layers)
527
+
528
+ if supported_layer_set and not layer_set.issubset(supported_layer_set):
529
+ raise ConfigurationError('layers (%s) not supported by source (%s)' % (
530
+ layers, ','.join(supported_layer_set)))
531
+
532
+ if uses_req:
533
+ source.conf['req']['layers'] = layers
534
+ else:
535
+ source.conf['layers'] = layers
536
+
537
+ return source
538
+
539
+ def __contains__(self, key):
540
+ source_name = key
541
+ if ':' in source_name:
542
+ source_name, _ = source_name.split(':', 1)
543
+ return dict.__contains__(self, source_name)
544
+
545
+ @staticmethod
546
+ def layer_set(layers):
547
+ if isinstance(layers, (list, tuple)):
548
+ return set(layers)
549
+ return set(layers.split(','))
550
+
551
+
552
+ class SourceConfiguration(ConfigurationBase):
553
+
554
+ supports_meta_tiles = True
555
+
556
+ @classmethod
557
+ def load(cls, conf, context):
558
+ source_type = conf['type']
559
+
560
+ subclass = source_configuration_types.get(source_type)
561
+ if not subclass:
562
+ raise ConfigurationError("unknown source type '%s'" % source_type)
563
+
564
+ return subclass(conf, context)
565
+
566
+ @memoize
567
+ def coverage(self):
568
+ if not 'coverage' in self.conf: return None
569
+ from mapproxy.config.coverage import load_coverage
570
+ return load_coverage(self.conf['coverage'])
571
+
572
+ def image_opts(self, format=None):
573
+ if 'transparent' in self.conf:
574
+ self.conf.setdefault('image', {})['transparent'] = self.conf['transparent']
575
+ return self.context.globals.image_options.image_opts(self.conf.get('image', {}), format)
576
+
577
+ def supported_srs(self):
578
+ from mapproxy.srs import SRS, SupportedSRS
579
+
580
+ supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])]
581
+ if not supported_srs:
582
+ return None
583
+ return SupportedSRS(supported_srs, self.context.globals.preferred_srs)
584
+
585
+ def http_client(self, url):
586
+ from mapproxy.client.http import auth_data_from_url, HTTPClient
587
+
588
+ http_client = None
589
+ url, (username, password) = auth_data_from_url(url)
590
+ insecure = ssl_ca_certs = None
591
+ if 'https' in url:
592
+ insecure = self.context.globals.get_value('http.ssl_no_cert_checks', self.conf)
593
+ ssl_ca_certs = self.context.globals.get_path('http.ssl_ca_certs', self.conf)
594
+
595
+ timeout = self.context.globals.get_value('http.client_timeout', self.conf)
596
+ headers = self.context.globals.get_value('http.headers', self.conf)
597
+ hide_error_details = self.context.globals.get_value('http.hide_error_details', self.conf)
598
+ manage_cookies = self.context.globals.get_value('http.manage_cookies', self.conf)
599
+
600
+ http_client = HTTPClient(url, username, password, insecure=insecure,
601
+ ssl_ca_certs=ssl_ca_certs, timeout=timeout,
602
+ headers=headers, hide_error_details=hide_error_details,
603
+ manage_cookies=manage_cookies)
604
+ return http_client, url
605
+
606
+ @memoize
607
+ def on_error_handler(self):
608
+ if not 'on_error' in self.conf: return None
609
+ from mapproxy.source.error import HTTPSourceErrorHandler
610
+
611
+ error_handler = HTTPSourceErrorHandler()
612
+ for status_code, response_conf in iteritems(self.conf['on_error']):
613
+ if not isinstance(status_code, int) and status_code != 'other':
614
+ raise ConfigurationError("invalid error code %r in on_error", status_code)
615
+ cacheable = response_conf.get('cache', False)
616
+ color = response_conf.get('response', 'transparent')
617
+ authorize_stale = response_conf.get('authorize_stale', False)
618
+ if color == 'transparent':
619
+ color = (255, 255, 255, 0)
620
+ else:
621
+ color = parse_color(color)
622
+ error_handler.add_handler(status_code, color, cacheable, authorize_stale)
623
+
624
+ return error_handler
625
+
626
+ def resolution_range(conf):
627
+ from mapproxy.grid import resolution_range as _resolution_range
628
+ if 'min_res' in conf or 'max_res' in conf:
629
+ return _resolution_range(min_res=conf.get('min_res'),
630
+ max_res=conf.get('max_res'))
631
+ if 'min_scale' in conf or 'max_scale' in conf:
632
+ return _resolution_range(min_scale=conf.get('min_scale'),
633
+ max_scale=conf.get('max_scale'))
634
+
635
+
636
+ class ArcGISSourceConfiguration(SourceConfiguration):
637
+ source_type = ('arcgis',)
638
+ def __init__(self, conf, context):
639
+ SourceConfiguration.__init__(self, conf, context)
640
+
641
+ def source(self, params=None):
642
+ from mapproxy.client.arcgis import ArcGISClient
643
+ from mapproxy.source.arcgis import ArcGISSource
644
+ from mapproxy.srs import SRS
645
+ from mapproxy.request.arcgis import create_request
646
+
647
+ if not self.conf.get('opts', {}).get('map', True):
648
+ return None
649
+
650
+ if not self.context.seed and self.conf.get('seed_only'):
651
+ from mapproxy.source import DummySource
652
+ return DummySource(coverage=self.coverage())
653
+
654
+ supported_formats = [file_ext(f) for f in self.conf.get("supported_formats", [])]
655
+
656
+ # Construct the parameters
657
+ if params is None:
658
+ params = {}
659
+
660
+ request_format = self.conf['req'].get('format')
661
+ if request_format:
662
+ params['format'] = request_format
663
+
664
+ request = create_request(self.conf["req"], params)
665
+ http_client, request.url = self.http_client(request.url)
666
+ coverage = self.coverage()
667
+ res_range = resolution_range(self.conf)
668
+
669
+ client = ArcGISClient(request, http_client)
670
+ image_opts = self.image_opts(format=params.get('format'))
671
+ return ArcGISSource(client, image_opts=image_opts, coverage=coverage,
672
+ res_range=res_range,
673
+ supported_srs=self.supported_srs(),
674
+ supported_formats=supported_formats or None,
675
+ error_handler=self.on_error_handler())
676
+
677
+ @memoize
678
+ def fi_source(self, params=None):
679
+ from mapproxy.client.arcgis import ArcGISInfoClient
680
+ from mapproxy.request.arcgis import create_identify_request
681
+ from mapproxy.source.arcgis import ArcGISInfoSource
682
+ from mapproxy.srs import SRS
683
+
684
+ if params is None: params = {}
685
+ request_format = self.conf['req'].get('format')
686
+ if request_format:
687
+ params['format'] = request_format
688
+ fi_source = None
689
+ if self.conf.get('opts', {}).get('featureinfo', False):
690
+ opts = self.conf['opts']
691
+ tolerance = opts.get('featureinfo_tolerance', 5)
692
+ return_geometries = opts.get('featureinfo_return_geometries', False)
693
+
694
+ fi_request = create_identify_request(self.conf['req'], params)
695
+
696
+
697
+ http_client, fi_request.url = self.http_client(fi_request.url)
698
+ fi_client = ArcGISInfoClient(fi_request,
699
+ supported_srs=self.supported_srs(),
700
+ http_client=http_client,
701
+ tolerance=tolerance,
702
+ return_geometries=return_geometries,
703
+ )
704
+ fi_source = ArcGISInfoSource(fi_client)
705
+ return fi_source
706
+
707
+
708
+ class WMSSourceConfiguration(SourceConfiguration):
709
+ source_type = ('wms',)
710
+
711
+ @staticmethod
712
+ def static_legend_source(url, context):
713
+ from mapproxy.cache.legend import LegendCache
714
+ from mapproxy.client.wms import WMSLegendURLClient
715
+ from mapproxy.source.wms import WMSLegendSource
716
+
717
+ cache_dir = os.path.join(context.globals.get_path('cache.base_dir', {}),
718
+ 'legends')
719
+ if url.startswith('file://') and not url.startswith('file:///'):
720
+ prefix = 'file://'
721
+ url = prefix + context.globals.abspath(url[7:])
722
+ lg_client = WMSLegendURLClient(url)
723
+ legend_cache = LegendCache(cache_dir=cache_dir)
724
+ return WMSLegendSource([lg_client], legend_cache, static=True)
725
+
726
+ def fi_xslt_transformer(self, conf, context):
727
+ from mapproxy.featureinfo import XSLTransformer, has_xslt_support
728
+ fi_transformer = None
729
+ fi_xslt = conf.get('featureinfo_xslt')
730
+ if fi_xslt:
731
+ if not has_xslt_support:
732
+ raise ValueError('featureinfo_xslt requires lxml. Please install.')
733
+ fi_xslt = context.globals.abspath(fi_xslt)
734
+ fi_format = conf.get('featureinfo_out_format')
735
+ if not fi_format:
736
+ fi_format = conf.get('featureinfo_format')
737
+ fi_transformer = XSLTransformer(fi_xslt, fi_format)
738
+ return fi_transformer
739
+
740
+ def image_opts(self, format=None):
741
+ if 'transparent' not in (self.conf.get('image') or {}):
742
+ transparent = self.conf['req'].get('transparent')
743
+ if transparent is not None:
744
+ transparent = bool(str(transparent).lower() == 'true')
745
+ self.conf.setdefault('image', {})['transparent'] = transparent
746
+ return SourceConfiguration.image_opts(self, format=format)
747
+
748
+ def source(self, params=None):
749
+ from mapproxy.client.wms import WMSClient
750
+ from mapproxy.request.wms import create_request
751
+ from mapproxy.source.wms import WMSSource
752
+ from mapproxy.srs import SRS, SupportedSRS
753
+
754
+ if not self.conf.get('wms_opts', {}).get('map', True):
755
+ return None
756
+
757
+ if not self.context.seed and self.conf.get('seed_only'):
758
+ from mapproxy.source import DummySource
759
+ return DummySource(coverage=self.coverage())
760
+
761
+ if params is None: params = {}
762
+
763
+ request_format = self.conf['req'].get('format')
764
+ if request_format:
765
+ params['format'] = request_format
766
+
767
+ image_opts = self.image_opts(format=params.get('format'))
768
+
769
+ supported_formats = [file_ext(f) for f in self.conf.get('supported_formats', [])]
770
+ version = self.conf.get('wms_opts', {}).get('version', '1.1.1')
771
+
772
+ lock = None
773
+ concurrent_requests = self.context.globals.get_value('concurrent_requests', self.conf,
774
+ global_key='http.concurrent_requests')
775
+ if concurrent_requests:
776
+ from mapproxy.util.lock import SemLock
777
+ lock_dir = self.context.globals.get_path('cache.lock_dir', self.conf)
778
+ lock_timeout = self.context.globals.get_value('http.client_timeout', self.conf)
779
+ url = urlparse.urlparse(self.conf['req']['url'])
780
+ md5 = hashlib.md5(url.netloc.encode('ascii'))
781
+ lock_file = os.path.join(lock_dir, md5.hexdigest() + '.lck')
782
+ lock = lambda: SemLock(lock_file, concurrent_requests, timeout=lock_timeout)
783
+
784
+ coverage = self.coverage()
785
+ res_range = resolution_range(self.conf)
786
+
787
+ transparent_color = (self.conf.get('image') or {}).get('transparent_color')
788
+ transparent_color_tolerance = self.context.globals.get_value(
789
+ 'image.transparent_color_tolerance', self.conf)
790
+ if transparent_color:
791
+ transparent_color = parse_color(transparent_color)
792
+
793
+ http_method = self.context.globals.get_value('http.method', self.conf)
794
+
795
+ fwd_req_params = set(self.conf.get('forward_req_params', []))
796
+
797
+ request = create_request(self.conf['req'], params, version=version,
798
+ abspath=self.context.globals.abspath)
799
+ http_client, request.url = self.http_client(request.url)
800
+ client = WMSClient(request, http_client=http_client,
801
+ http_method=http_method, lock=lock,
802
+ fwd_req_params=fwd_req_params)
803
+ return WMSSource(client, image_opts=image_opts, coverage=coverage,
804
+ res_range=res_range, transparent_color=transparent_color,
805
+ transparent_color_tolerance=transparent_color_tolerance,
806
+ supported_srs=self.supported_srs(),
807
+ supported_formats=supported_formats or None,
808
+ fwd_req_params=fwd_req_params,
809
+ error_handler=self.on_error_handler())
810
+
811
+ def fi_source(self, params=None):
812
+ from mapproxy.client.wms import WMSInfoClient
813
+ from mapproxy.request.wms import create_request
814
+ from mapproxy.source.wms import WMSInfoSource
815
+ from mapproxy.srs import SRS
816
+
817
+ if params is None: params = {}
818
+ request_format = self.conf['req'].get('format')
819
+ if request_format:
820
+ params['format'] = request_format
821
+ fi_source = None
822
+ if self.conf.get('wms_opts', {}).get('featureinfo', False):
823
+ wms_opts = self.conf['wms_opts']
824
+ version = wms_opts.get('version', '1.1.1')
825
+ if 'featureinfo_format' in wms_opts:
826
+ params['info_format'] = wms_opts['featureinfo_format']
827
+ fi_request = create_request(self.conf['req'], params,
828
+ req_type='featureinfo', version=version,
829
+ abspath=self.context.globals.abspath)
830
+
831
+ fi_transformer = self.fi_xslt_transformer(self.conf.get('wms_opts', {}),
832
+ self.context)
833
+
834
+ http_client, fi_request.url = self.http_client(fi_request.url)
835
+ fi_client = WMSInfoClient(fi_request, supported_srs=self.supported_srs(),
836
+ http_client=http_client)
837
+ coverage = self.coverage()
838
+ fi_source = WMSInfoSource(fi_client, fi_transformer=fi_transformer,
839
+ coverage=coverage)
840
+ return fi_source
841
+
842
+ def lg_source(self, params=None):
843
+ from mapproxy.cache.legend import LegendCache
844
+ from mapproxy.client.wms import WMSLegendClient
845
+ from mapproxy.request.wms import create_request
846
+ from mapproxy.source.wms import WMSLegendSource
847
+
848
+ if params is None: params = {}
849
+ request_format = self.conf['req'].get('format')
850
+ if request_format:
851
+ params['format'] = request_format
852
+ lg_source = None
853
+ cache_dir = os.path.join(self.context.globals.get_path('cache.base_dir', {}),
854
+ 'legends')
855
+
856
+ if self.conf.get('wms_opts', {}).get('legendurl', False):
857
+ lg_url = self.conf.get('wms_opts', {}).get('legendurl')
858
+ lg_source = WMSSourceConfiguration.static_legend_source(lg_url, self.context)
859
+ elif self.conf.get('wms_opts', {}).get('legendgraphic', False):
860
+ version = self.conf.get('wms_opts', {}).get('version', '1.1.1')
861
+ lg_req = self.conf['req'].copy()
862
+ lg_clients = []
863
+ lg_layers = str(lg_req['layers']).split(',')
864
+ del lg_req['layers']
865
+ for lg_layer in lg_layers:
866
+ lg_req['layer'] = lg_layer
867
+ lg_request = create_request(lg_req, params,
868
+ req_type='legendgraphic', version=version,
869
+ abspath=self.context.globals.abspath)
870
+ http_client, lg_request.url = self.http_client(lg_request.url)
871
+ lg_client = WMSLegendClient(lg_request, http_client=http_client)
872
+ lg_clients.append(lg_client)
873
+ legend_cache = LegendCache(cache_dir=cache_dir)
874
+ lg_source = WMSLegendSource(lg_clients, legend_cache)
875
+ return lg_source
876
+
877
+
878
+ class MapServerSourceConfiguration(WMSSourceConfiguration):
879
+ source_type = ('mapserver',)
880
+
881
+ def __init__(self, conf, context):
882
+ WMSSourceConfiguration.__init__(self, conf, context)
883
+ self.script = self.context.globals.get_path('mapserver.binary',
884
+ self.conf)
885
+ if not self.script:
886
+ self.script = find_exec('mapserv')
887
+
888
+ if not self.script or not os.path.isfile(self.script):
889
+ raise ConfigurationError('could not find mapserver binary (%r)' %
890
+ (self.script, ))
891
+
892
+ # set url to dummy script name, required as identifier
893
+ # for concurrent_request
894
+ self.conf['req']['url'] = 'mapserver://' + self.script
895
+
896
+ mapfile = self.context.globals.abspath(self.conf['req']['map'])
897
+ self.conf['req']['map'] = mapfile
898
+
899
+ def http_client(self, url):
900
+ working_dir = self.context.globals.get_path('mapserver.working_dir', self.conf)
901
+ if working_dir and not os.path.isdir(working_dir):
902
+ raise ConfigurationError('could not find mapserver working_dir (%r)' % (working_dir, ))
903
+
904
+ from mapproxy.client.cgi import CGIClient
905
+ client = CGIClient(script=self.script, working_directory=working_dir)
906
+ return client, url
907
+
908
+
909
+ class MapnikSourceConfiguration(SourceConfiguration):
910
+ source_type = ('mapnik',)
911
+
912
+ def source(self, params=None):
913
+ if not self.context.seed and self.conf.get('seed_only'):
914
+ from mapproxy.source import DummySource
915
+ return DummySource(coverage=self.coverage())
916
+
917
+ image_opts = self.image_opts()
918
+
919
+ lock = None
920
+ concurrent_requests = self.context.globals.get_value('concurrent_requests', self.conf,
921
+ global_key='http.concurrent_requests')
922
+ if concurrent_requests:
923
+ from mapproxy.util.lock import SemLock
924
+ lock_dir = self.context.globals.get_path('cache.lock_dir', self.conf)
925
+ mapfile = self.conf['mapfile']
926
+ md5 = hashlib.md5(mapfile.encode('utf-8'))
927
+ lock_file = os.path.join(lock_dir, md5.hexdigest() + '.lck')
928
+ lock = lambda: SemLock(lock_file, concurrent_requests)
929
+
930
+ coverage = self.coverage()
931
+ res_range = resolution_range(self.conf)
932
+
933
+ scale_factor = self.conf.get('scale_factor', None)
934
+ multithreaded = self.conf.get('multithreaded', False)
935
+
936
+ layers = self.conf.get('layers', None)
937
+ if isinstance(layers, string_type):
938
+ layers = layers.split(',')
939
+
940
+ mapfile = self.context.globals.abspath(self.conf['mapfile'])
941
+
942
+ if self.conf.get('use_mapnik2', False):
943
+ warnings.warn('use_mapnik2 option is no longer needed for Mapnik 2 support',
944
+ DeprecationWarning)
945
+
946
+ from mapproxy.source.mapnik import MapnikSource, mapnik as mapnik_api
947
+ if mapnik_api is None:
948
+ raise ConfigurationError('Could not import Mapnik, please verify it is installed!')
949
+
950
+ if self.context.renderd:
951
+ # only renderd guarantees that we have a single proc/thread
952
+ # that accesses the same mapnik map object
953
+ reuse_map_objects = True
954
+ else:
955
+ reuse_map_objects = False
956
+
957
+ return MapnikSource(mapfile, layers=layers, image_opts=image_opts,
958
+ coverage=coverage, res_range=res_range, lock=lock,
959
+ reuse_map_objects=reuse_map_objects, scale_factor=scale_factor,
960
+ multithreaded=multithreaded)
961
+
962
+ class TileSourceConfiguration(SourceConfiguration):
963
+ supports_meta_tiles = False
964
+ source_type = ('tile',)
965
+ defaults = {}
966
+
967
+ def source(self, params=None):
968
+ from mapproxy.client.tile import TileClient, TileURLTemplate
969
+ from mapproxy.source.tile import TiledSource
970
+
971
+ if not self.context.seed and self.conf.get('seed_only'):
972
+ from mapproxy.source import DummySource
973
+ return DummySource(coverage=self.coverage())
974
+
975
+ if params is None: params = {}
976
+
977
+ url = self.conf['url']
978
+
979
+ if self.conf.get('origin'):
980
+ warnings.warn('origin for tile sources is deprecated since 1.3.0 '
981
+ 'and will be ignored. use grid with correct origin.', RuntimeWarning)
982
+
983
+ http_client, url = self.http_client(url)
984
+
985
+ grid_name = self.conf.get('grid')
986
+ if grid_name is None:
987
+ log.warning("tile source for %s does not have a grid configured and defaults to GLOBAL_MERCATOR. default will change with MapProxy 2.0", url)
988
+ grid_name = "GLOBAL_MERCATOR"
989
+
990
+ grid = self.context.grids[grid_name].tile_grid()
991
+ coverage = self.coverage()
992
+ res_range = resolution_range(self.conf)
993
+
994
+ image_opts = self.image_opts()
995
+ error_handler = self.on_error_handler()
996
+
997
+ format = file_ext(params['format'])
998
+ client = TileClient(TileURLTemplate(url, format=format), http_client=http_client, grid=grid)
999
+ return TiledSource(grid, client, coverage=coverage, image_opts=image_opts,
1000
+ error_handler=error_handler, res_range=res_range)
1001
+
1002
+
1003
+ def file_ext(mimetype):
1004
+ from mapproxy.request.base import split_mime_type
1005
+ _mime_class, format, _options = split_mime_type(mimetype)
1006
+ return format
1007
+
1008
+ class DebugSourceConfiguration(SourceConfiguration):
1009
+ source_type = ('debug',)
1010
+ required_keys = set('type'.split())
1011
+
1012
+ def source(self, params=None):
1013
+ from mapproxy.source import DebugSource
1014
+ return DebugSource()
1015
+
1016
+
1017
+ source_configuration_types = {
1018
+ 'wms': WMSSourceConfiguration,
1019
+ 'arcgis': ArcGISSourceConfiguration,
1020
+ 'tile': TileSourceConfiguration,
1021
+ 'debug': DebugSourceConfiguration,
1022
+ 'mapserver': MapServerSourceConfiguration,
1023
+ 'mapnik': MapnikSourceConfiguration,
1024
+ }
1025
+
1026
+
1027
+ def register_source_configuration(config_name, config_class,
1028
+ yaml_spec_source_name = None, yaml_spec_source_def = None):
1029
+ """ Method used by plugins to register a new source configuration.
1030
+
1031
+ :param config_name: Name of the source configuration
1032
+ :type config_name: str
1033
+ :param config_class: Class of the source configuration
1034
+ :type config_name: SourceConfiguration
1035
+ :param yaml_spec_source_name: Name of the source in the YAML configuration file
1036
+ :type yaml_spec_source_name: str
1037
+ :param yaml_spec_source_def: Definition of the source in the YAML configuration file
1038
+ :type yaml_spec_source_def: dict
1039
+
1040
+ Example:
1041
+ register_source_configuration('hips', HIPSSourceConfiguration,
1042
+ 'hips', { required('url'): str(),
1043
+ 'resampling_method': str(),
1044
+ 'image': image_opts,
1045
+ })
1046
+ """
1047
+ log.info('Registering configuration for plugin source %s' % config_name)
1048
+ source_configuration_types[config_name] = config_class
1049
+ if yaml_spec_source_name is not None and yaml_spec_source_def is not None:
1050
+ add_source_to_mapproxy_yaml_spec(yaml_spec_source_name, yaml_spec_source_def)
1051
+
1052
+
1053
+ class CacheConfiguration(ConfigurationBase):
1054
+ defaults = {'format': 'image/png'}
1055
+
1056
+ @memoize
1057
+ def cache_dir(self):
1058
+ cache_dir = self.conf.get('cache', {}).get('directory')
1059
+ if cache_dir:
1060
+ if self.conf.get('cache_dir'):
1061
+ log.warning('found cache.directory and cache_dir option for %s, ignoring cache_dir',
1062
+ self.conf['name'])
1063
+ return self.context.globals.abspath(cache_dir)
1064
+
1065
+ return self.context.globals.get_path('cache_dir', self.conf,
1066
+ global_key='cache.base_dir')
1067
+
1068
+ @memoize
1069
+ def has_multiple_grids(self):
1070
+ return len(self.grid_confs()) > 1
1071
+
1072
+ def lock_dir(self):
1073
+ lock_dir = self.context.globals.get_path('cache.tile_lock_dir', self.conf)
1074
+ if not lock_dir:
1075
+ lock_dir = os.path.join(self.cache_dir(), 'tile_locks')
1076
+ return lock_dir
1077
+
1078
+ def _file_cache(self, grid_conf, file_ext):
1079
+ from mapproxy.cache.file import FileCache
1080
+
1081
+ cache_dir = self.cache_dir()
1082
+ directory_layout = self.conf.get('cache', {}).get('directory_layout', 'tc')
1083
+ if self.conf.get('cache', {}).get('directory'):
1084
+ if self.has_multiple_grids():
1085
+ raise ConfigurationError(
1086
+ "using single directory for cache with multiple grids in %s" %
1087
+ (self.conf['name']),
1088
+ )
1089
+ pass
1090
+ elif self.conf.get('cache', {}).get('use_grid_names'):
1091
+ cache_dir = os.path.join(cache_dir, self.conf['name'], grid_conf.tile_grid().name)
1092
+ else:
1093
+ suffix = grid_conf.conf['srs'].replace(':', '')
1094
+ cache_dir = os.path.join(cache_dir, self.conf['name'] + '_' + suffix)
1095
+ link_single_color_images = self.context.globals.get_value('link_single_color_images', self.conf,
1096
+ global_key='cache.link_single_color_images')
1097
+
1098
+ if link_single_color_images and sys.platform == 'win32':
1099
+ log.warning('link_single_color_images not supported on windows')
1100
+ link_single_color_images = False
1101
+
1102
+
1103
+ return FileCache(
1104
+ cache_dir,
1105
+ file_ext=file_ext,
1106
+ directory_layout=directory_layout,
1107
+ link_single_color_images=link_single_color_images,
1108
+ )
1109
+
1110
+ def _mbtiles_cache(self, grid_conf, file_ext):
1111
+ from mapproxy.cache.mbtiles import MBTilesCache
1112
+
1113
+ filename = self.conf['cache'].get('filename')
1114
+ if not filename:
1115
+ filename = self.conf['name'] + '.mbtiles'
1116
+
1117
+ if filename.startswith('.' + os.sep):
1118
+ mbfile_path = self.context.globals.abspath(filename)
1119
+ else:
1120
+ mbfile_path = os.path.join(self.cache_dir(), filename)
1121
+
1122
+ sqlite_timeout = self.context.globals.get_value('cache.sqlite_timeout', self.conf)
1123
+ wal = self.context.globals.get_value('cache.sqlite_wal', self.conf)
1124
+
1125
+ return MBTilesCache(
1126
+ mbfile_path,
1127
+ timeout=sqlite_timeout,
1128
+ wal=wal,
1129
+ )
1130
+
1131
+ def _geopackage_cache(self, grid_conf, file_ext):
1132
+ from mapproxy.cache.geopackage import GeopackageCache, GeopackageLevelCache
1133
+
1134
+ filename = self.conf['cache'].get('filename')
1135
+ table_name = self.conf['cache'].get('table_name') or \
1136
+ "{}_{}".format(self.conf['name'], grid_conf.tile_grid().name)
1137
+ levels = self.conf['cache'].get('levels')
1138
+
1139
+ if not filename:
1140
+ filename = self.conf['name'] + '.gpkg'
1141
+ if filename.startswith('.' + os.sep):
1142
+ gpkg_file_path = self.context.globals.abspath(filename)
1143
+ else:
1144
+ gpkg_file_path = os.path.join(self.cache_dir(), filename)
1145
+
1146
+ cache_dir = self.conf['cache'].get('directory')
1147
+ if cache_dir:
1148
+ cache_dir = os.path.join(
1149
+ self.context.globals.abspath(cache_dir),
1150
+ grid_conf.tile_grid().name
1151
+ )
1152
+ else:
1153
+ cache_dir = self.cache_dir()
1154
+ cache_dir = os.path.join(
1155
+ cache_dir,
1156
+ self.conf['name'],
1157
+ grid_conf.tile_grid().name
1158
+ )
1159
+
1160
+ if levels:
1161
+ return GeopackageLevelCache(
1162
+ cache_dir, grid_conf.tile_grid(), table_name
1163
+ )
1164
+ else:
1165
+ return GeopackageCache(
1166
+ gpkg_file_path, grid_conf.tile_grid(), table_name
1167
+ )
1168
+
1169
+ def _azureblob_cache(self, grid_conf, file_ext):
1170
+ from mapproxy.cache.azureblob import AzureBlobCache
1171
+
1172
+ container_name = self.context.globals.get_value('cache.container_name', self.conf,
1173
+ global_key='cache.azureblob.container_name')
1174
+
1175
+ if not container_name:
1176
+ raise ConfigurationError("no container_name configured for Azure Blob cache %s" % self.conf['name'])
1177
+
1178
+ connection_string = os.getenv("AZURE_STORAGE_CONNECTION_STRING", self.context.globals.get_value(
1179
+ 'cache.connection_string', self.conf, global_key='cache.azureblob.connection_string'))
1180
+
1181
+ if not connection_string:
1182
+ raise ConfigurationError("no connection_string configured for Azure Blob cache %s" % self.conf['name'])
1183
+
1184
+ directory_layout = self.conf['cache'].get('directory_layout', 'tms')
1185
+
1186
+ base_path = self.conf['cache'].get('directory', None)
1187
+ if base_path is None:
1188
+ base_path = os.path.join(self.conf['name'], grid_conf.tile_grid().name)
1189
+
1190
+ return AzureBlobCache(
1191
+ base_path=base_path,
1192
+ file_ext=file_ext,
1193
+ directory_layout=directory_layout,
1194
+ container_name=container_name,
1195
+ connection_string=connection_string
1196
+ )
1197
+
1198
+ def _s3_cache(self, grid_conf, file_ext):
1199
+ from mapproxy.cache.s3 import S3Cache
1200
+
1201
+ bucket_name = self.context.globals.get_value('cache.bucket_name', self.conf,
1202
+ global_key='cache.s3.bucket_name')
1203
+
1204
+ if not bucket_name:
1205
+ raise ConfigurationError("no bucket_name configured for s3 cache %s" % self.conf['name'])
1206
+
1207
+ profile_name = self.context.globals.get_value('cache.profile_name', self.conf,
1208
+ global_key='cache.s3.profile_name')
1209
+
1210
+ region_name = self.context.globals.get_value('cache.region_name', self.conf,
1211
+ global_key='cache.s3.region_name')
1212
+
1213
+ endpoint_url = self.context.globals.get_value('cache.endpoint_url', self.conf,
1214
+ global_key='cache.s3.endpoint_url')
1215
+
1216
+ access_control_list = self.context.globals.get_value('cache.access_control_list', self.conf,
1217
+ global_key='cache.s3.access_control_list')
1218
+
1219
+ directory_layout = self.conf['cache'].get('directory_layout', 'tms')
1220
+
1221
+ base_path = self.conf['cache'].get('directory', None)
1222
+ if base_path is None:
1223
+ base_path = os.path.join(self.conf['name'], grid_conf.tile_grid().name)
1224
+
1225
+ return S3Cache(
1226
+ base_path=base_path,
1227
+ file_ext=file_ext,
1228
+ directory_layout=directory_layout,
1229
+ bucket_name=bucket_name,
1230
+ profile_name=profile_name,
1231
+ region_name=region_name,
1232
+ endpoint_url=endpoint_url,
1233
+ access_control_list=access_control_list
1234
+ )
1235
+
1236
+ def _sqlite_cache(self, grid_conf, file_ext):
1237
+ from mapproxy.cache.mbtiles import MBTilesLevelCache
1238
+
1239
+ cache_dir = self.conf.get('cache', {}).get('directory')
1240
+ if cache_dir:
1241
+ cache_dir = os.path.join(
1242
+ self.context.globals.abspath(cache_dir),
1243
+ grid_conf.tile_grid().name
1244
+ )
1245
+ else:
1246
+ cache_dir = self.cache_dir()
1247
+ cache_dir = os.path.join(
1248
+ cache_dir,
1249
+ self.conf['name'],
1250
+ grid_conf.tile_grid().name
1251
+ )
1252
+
1253
+ sqlite_timeout = self.context.globals.get_value('cache.sqlite_timeout', self.conf)
1254
+ wal = self.context.globals.get_value('cache.sqlite_wal', self.conf)
1255
+
1256
+ return MBTilesLevelCache(
1257
+ cache_dir,
1258
+ timeout=sqlite_timeout,
1259
+ wal=wal,
1260
+ )
1261
+
1262
+ def _couchdb_cache(self, grid_conf, file_ext):
1263
+ from mapproxy.cache.couchdb import CouchDBCache, CouchDBMDTemplate
1264
+
1265
+ db_name = self.conf['cache'].get('db_name')
1266
+ if not db_name:
1267
+ suffix = grid_conf.conf['srs'].replace(':', '')
1268
+ db_name = self.conf['name'] + '_' + suffix
1269
+
1270
+ url = self.conf['cache'].get('url')
1271
+ if not url:
1272
+ url = 'http://127.0.0.1:5984'
1273
+
1274
+ md_template = CouchDBMDTemplate(self.conf['cache'].get('tile_metadata', {}))
1275
+ tile_id = self.conf['cache'].get('tile_id')
1276
+
1277
+ return CouchDBCache(url=url, db_name=db_name,
1278
+ file_ext=file_ext, tile_grid=grid_conf.tile_grid(),
1279
+ md_template=md_template, tile_id_template=tile_id)
1280
+
1281
+ def _riak_cache(self, grid_conf, file_ext):
1282
+ from mapproxy.cache.riak import RiakCache
1283
+
1284
+ default_ports = self.conf['cache'].get('default_ports', {})
1285
+ default_pb_port = default_ports.get('pb', 8087)
1286
+ default_http_port = default_ports.get('http', 8098)
1287
+
1288
+ nodes = self.conf['cache'].get('nodes')
1289
+ if not nodes:
1290
+ nodes = [{'host': '127.0.0.1'}]
1291
+
1292
+ for n in nodes:
1293
+ if 'pb_port' not in n:
1294
+ n['pb_port'] = default_pb_port
1295
+ if 'http_port' not in n:
1296
+ n['http_port'] = default_http_port
1297
+
1298
+ protocol = self.conf['cache'].get('protocol', 'pbc')
1299
+ bucket = self.conf['cache'].get('bucket')
1300
+ if not bucket:
1301
+ suffix = grid_conf.tile_grid().name
1302
+ bucket = self.conf['name'] + '_' + suffix
1303
+
1304
+ use_secondary_index = self.conf['cache'].get('secondary_index', False)
1305
+ timeout = self.context.globals.get_value('http.client_timeout', self.conf)
1306
+
1307
+ return RiakCache(nodes=nodes, protocol=protocol, bucket=bucket,
1308
+ tile_grid=grid_conf.tile_grid(),
1309
+ use_secondary_index=use_secondary_index,
1310
+ timeout=timeout
1311
+ )
1312
+
1313
+ def _redis_cache(self, grid_conf, file_ext):
1314
+ from mapproxy.cache.redis import RedisCache
1315
+
1316
+ host = self.conf['cache'].get('host', '127.0.0.1')
1317
+ port = self.conf['cache'].get('port', 6379)
1318
+ db = self.conf['cache'].get('db', 0)
1319
+ ttl = self.conf['cache'].get('default_ttl', 3600)
1320
+
1321
+ prefix = self.conf['cache'].get('prefix')
1322
+ if not prefix:
1323
+ prefix = self.conf['name'] + '_' + grid_conf.tile_grid().name
1324
+
1325
+ return RedisCache(
1326
+ host=host,
1327
+ port=port,
1328
+ db=db,
1329
+ prefix=prefix,
1330
+ ttl=ttl,
1331
+ )
1332
+
1333
+ def _compact_cache(self, grid_conf, file_ext):
1334
+ from mapproxy.cache.compact import CompactCacheV1, CompactCacheV2
1335
+
1336
+ cache_dir = self.cache_dir()
1337
+ if self.conf.get('cache', {}).get('directory'):
1338
+ if self.has_multiple_grids():
1339
+ raise ConfigurationError(
1340
+ "using single directory for cache with multiple grids in %s" %
1341
+ (self.conf['name']),
1342
+ )
1343
+ pass
1344
+ else:
1345
+ cache_dir = os.path.join(cache_dir, self.conf['name'], grid_conf.tile_grid().name)
1346
+
1347
+ version = self.conf['cache']['version']
1348
+ if version == 1:
1349
+ return CompactCacheV1(cache_dir=cache_dir)
1350
+ elif version == 2:
1351
+ return CompactCacheV2(cache_dir=cache_dir)
1352
+
1353
+ raise ConfigurationError("compact cache only supports version 1 or 2")
1354
+
1355
+ def _tile_cache(self, grid_conf, file_ext):
1356
+ if self.conf.get('disable_storage', False):
1357
+ from mapproxy.cache.dummy import DummyCache
1358
+ return DummyCache()
1359
+
1360
+ grid_conf.tile_grid() #create to resolve `base` in grid_conf.conf
1361
+ cache_type = self.conf.get('cache', {}).get('type', 'file')
1362
+ return getattr(self, '_%s_cache' % cache_type)(grid_conf, file_ext)
1363
+
1364
+ def _tile_filter(self):
1365
+ filters = []
1366
+ if 'watermark' in self.conf:
1367
+ from mapproxy.tilefilter import create_watermark_filter
1368
+ if self.conf['watermark'].get('color'):
1369
+ self.conf['watermark']['color'] = parse_color(self.conf['watermark']['color'])
1370
+ f = create_watermark_filter(self.conf, self.context)
1371
+ if f:
1372
+ filters.append(f)
1373
+ return filters
1374
+
1375
+ @memoize
1376
+ def image_opts(self):
1377
+ from mapproxy.image.opts import ImageFormat
1378
+
1379
+ format = None
1380
+ if 'format' not in self.conf.get('image', {}):
1381
+ format = self.conf.get('format') or self.conf.get('request_format')
1382
+ image_opts = self.context.globals.image_options.image_opts(self.conf.get('image', {}), format)
1383
+ if image_opts.format is None:
1384
+ if format is not None and format.startswith('image/'):
1385
+ image_opts.format = ImageFormat(format)
1386
+ else:
1387
+ image_opts.format = ImageFormat('image/png')
1388
+ return image_opts
1389
+
1390
+ def supports_tiled_only_access(self, params=None, tile_grid=None):
1391
+ caches = self.caches()
1392
+ if len(caches) > 1:
1393
+ return False
1394
+
1395
+ cache_grid, extent, tile_manager = caches[0]
1396
+ image_opts = self.image_opts()
1397
+
1398
+ if (tile_grid.is_subset_of(cache_grid)
1399
+ and params.get('format') == image_opts.format):
1400
+ return True
1401
+
1402
+ return False
1403
+
1404
+ def source(self, params=None, tile_grid=None, tiled_only=False):
1405
+ from mapproxy.source.tile import CacheSource
1406
+ from mapproxy.layer import map_extent_from_grid
1407
+
1408
+ caches = self.caches()
1409
+ if len(caches) > 1:
1410
+ # cache with multiple grids/sources
1411
+ source = self.map_layer()
1412
+ source.supports_meta_tiles = True
1413
+ return source
1414
+
1415
+ cache_grid, extent, tile_manager = caches[0]
1416
+ image_opts = self.image_opts()
1417
+
1418
+ cache_extent = map_extent_from_grid(tile_grid)
1419
+ cache_extent = extent.intersection(cache_extent)
1420
+
1421
+ source = CacheSource(tile_manager, extent=cache_extent,
1422
+ image_opts=image_opts, tiled_only=tiled_only)
1423
+ return source
1424
+
1425
+ def _sources_for_grid(self, source_names, grid_conf, request_format):
1426
+ sources = []
1427
+ source_image_opts = []
1428
+
1429
+ # a cache can directly access source tiles when _all_ sources are caches too
1430
+ # and when they have compatible grids by using tiled_only on the CacheSource
1431
+ # check if all sources support tiled_only
1432
+ tiled_only = True
1433
+ for source_name in source_names:
1434
+ if source_name in self.context.sources:
1435
+ tiled_only = False
1436
+ break
1437
+ elif source_name in self.context.caches:
1438
+ cache_conf = self.context.caches[source_name]
1439
+ tiled_only = cache_conf.supports_tiled_only_access(
1440
+ params={'format': request_format},
1441
+ tile_grid=grid_conf.tile_grid(),
1442
+ )
1443
+ if not tiled_only:
1444
+ break
1445
+
1446
+ for source_name in source_names:
1447
+ if source_name in self.context.sources:
1448
+ source_conf = self.context.sources[source_name]
1449
+ source = source_conf.source({'format': request_format})
1450
+ elif source_name in self.context.caches:
1451
+ cache_conf = self.context.caches[source_name]
1452
+ source = cache_conf.source(
1453
+ params={'format': request_format},
1454
+ tile_grid=grid_conf.tile_grid(),
1455
+ tiled_only=tiled_only,
1456
+ )
1457
+ else:
1458
+ raise ConfigurationError('unknown source %s' % source_name)
1459
+ if source:
1460
+ sources.append(source)
1461
+ source_image_opts.append(source.image_opts)
1462
+
1463
+ return sources, source_image_opts
1464
+
1465
+ def _sources_for_band_merge(self, sources_conf, grid_conf, request_format):
1466
+ from mapproxy.image.merge import BandMerger
1467
+
1468
+ source_names = []
1469
+
1470
+ for band, band_sources in iteritems(sources_conf):
1471
+ for source in band_sources:
1472
+ name = source['source']
1473
+ if name in source_names:
1474
+ idx = source_names.index(name)
1475
+ else:
1476
+ source_names.append(name)
1477
+ idx = len(source_names) - 1
1478
+
1479
+ source["src_idx"] = idx
1480
+
1481
+ sources, source_image_opts = self._sources_for_grid(
1482
+ source_names=source_names,
1483
+ grid_conf=grid_conf,
1484
+ request_format=request_format,
1485
+ )
1486
+
1487
+ if 'l' in sources_conf:
1488
+ mode = 'L'
1489
+ elif 'a' in sources_conf:
1490
+ mode = 'RGBA'
1491
+ else:
1492
+ mode = 'RGB'
1493
+
1494
+ band_merger = BandMerger(mode=mode)
1495
+ available_bands = {'r': 0, 'g': 1, 'b': 2, 'a': 3, 'l': 0}
1496
+ for band, band_sources in iteritems(sources_conf):
1497
+ band_idx = available_bands.get(band)
1498
+ if band_idx is None:
1499
+ raise ConfigurationError("unsupported band '%s' for cache %s"
1500
+ % (band, self.conf['name']))
1501
+ for source in band_sources:
1502
+ band_merger.add_ops(
1503
+ dst_band=band_idx,
1504
+ src_img=source['src_idx'],
1505
+ src_band=source['band'],
1506
+ factor=source.get('factor', 1.0),
1507
+ )
1508
+
1509
+ return band_merger, sources, source_image_opts
1510
+
1511
+ @memoize
1512
+ def caches(self):
1513
+ from mapproxy.cache.dummy import DummyCache, DummyLocker
1514
+ from mapproxy.cache.tile import TileManager
1515
+ from mapproxy.cache.base import TileLocker
1516
+ from mapproxy.image.opts import compatible_image_options
1517
+ from mapproxy.layer import map_extent_from_grid, merge_layer_extents
1518
+
1519
+ base_image_opts = self.image_opts()
1520
+ if self.conf.get('format') == 'mixed' and not self.conf.get('request_format') in [ 'image/png', 'image/vnd.jpeg-png' ]:
1521
+ raise ConfigurationError('request_format must be set to image/png or image/vnd.jpeg-png if mixed mode is enabled')
1522
+ request_format = self.conf.get('request_format') or self.conf.get('format')
1523
+ if '/' in request_format:
1524
+ request_format_ext = request_format.split('/', 1)[1]
1525
+ else:
1526
+ request_format_ext = request_format
1527
+
1528
+ caches = []
1529
+
1530
+ meta_buffer = self.context.globals.get_value('meta_buffer', self.conf,
1531
+ global_key='cache.meta_buffer')
1532
+ meta_size = self.context.globals.get_value('meta_size', self.conf,
1533
+ global_key='cache.meta_size')
1534
+ bulk_meta_tiles = self.context.globals.get_value('bulk_meta_tiles', self.conf,
1535
+ global_key='cache.bulk_meta_tiles')
1536
+ minimize_meta_requests = self.context.globals.get_value('minimize_meta_requests', self.conf,
1537
+ global_key='cache.minimize_meta_requests')
1538
+ concurrent_tile_creators = self.context.globals.get_value('concurrent_tile_creators', self.conf,
1539
+ global_key='cache.concurrent_tile_creators')
1540
+
1541
+ cache_rescaled_tiles = self.conf.get('cache_rescaled_tiles')
1542
+ upscale_tiles = self.conf.get('upscale_tiles', 0)
1543
+ if upscale_tiles < 0:
1544
+ raise ConfigurationError("upscale_tiles must be positive")
1545
+ downscale_tiles = self.conf.get('downscale_tiles', 0)
1546
+ if downscale_tiles < 0:
1547
+ raise ConfigurationError("downscale_tiles must be positive")
1548
+ if upscale_tiles and downscale_tiles:
1549
+ raise ConfigurationError("cannot use both upscale_tiles and downscale_tiles")
1550
+
1551
+ rescale_tiles = 0
1552
+ if upscale_tiles:
1553
+ rescale_tiles = -upscale_tiles
1554
+ if downscale_tiles:
1555
+ rescale_tiles = downscale_tiles
1556
+
1557
+ renderd_address = self.context.globals.get_value('renderd.address', self.conf)
1558
+
1559
+ band_merger = None
1560
+ for grid_name, grid_conf in self.grid_confs():
1561
+ if isinstance(self.conf['sources'], dict):
1562
+ band_merger, sources, source_image_opts = self._sources_for_band_merge(
1563
+ self.conf['sources'],
1564
+ grid_conf=grid_conf,
1565
+ request_format=request_format,
1566
+ )
1567
+ else:
1568
+ sources, source_image_opts = self._sources_for_grid(
1569
+ self.conf['sources'],
1570
+ grid_conf=grid_conf,
1571
+ request_format=request_format,
1572
+ )
1573
+
1574
+ if not sources:
1575
+ from mapproxy.source import DummySource
1576
+ sources = [DummySource()]
1577
+ source_image_opts.append(sources[0].image_opts)
1578
+ tile_grid = grid_conf.tile_grid()
1579
+ tile_filter = self._tile_filter()
1580
+ image_opts = compatible_image_options(source_image_opts, base_opts=base_image_opts)
1581
+ cache = self._tile_cache(grid_conf, image_opts.format.ext)
1582
+ identifier = self.conf['name'] + '_' + tile_grid.name
1583
+
1584
+ tile_creator_class = None
1585
+
1586
+ use_renderd = bool(renderd_address)
1587
+ if self.context.renderd:
1588
+ # we _are_ renderd
1589
+ use_renderd = False
1590
+ if self.conf.get('disable_storage', False):
1591
+ # can't ask renderd to create tiles that shouldn't be cached
1592
+ use_renderd = False
1593
+
1594
+ if use_renderd:
1595
+ from mapproxy.cache.renderd import RenderdTileCreator, has_renderd_support
1596
+ if not has_renderd_support():
1597
+ raise ConfigurationError("renderd requires requests library")
1598
+ if self.context.seed:
1599
+ priority = 10
1600
+ else:
1601
+ priority = 100
1602
+
1603
+ cache_dir = self.cache_dir()
1604
+
1605
+ lock_dir = self.context.globals.get_value('cache.tile_lock_dir')
1606
+ if not lock_dir:
1607
+ lock_dir = os.path.join(cache_dir, 'tile_locks')
1608
+
1609
+ lock_timeout = self.context.globals.get_value('http.client_timeout', {})
1610
+ locker = TileLocker(lock_dir, lock_timeout, identifier + '_renderd')
1611
+ # TODO band_merger
1612
+ tile_creator_class = partial(RenderdTileCreator, renderd_address,
1613
+ priority=priority, tile_locker=locker)
1614
+
1615
+ else:
1616
+ from mapproxy.cache.tile import TileCreator
1617
+ tile_creator_class = partial(TileCreator, image_merger=band_merger)
1618
+
1619
+ if isinstance(cache, DummyCache):
1620
+ locker = DummyLocker()
1621
+ else:
1622
+ locker = TileLocker(
1623
+ lock_dir=self.lock_dir(),
1624
+ lock_timeout=self.context.globals.get_value('http.client_timeout', {}),
1625
+ lock_cache_id=cache.lock_cache_id,
1626
+ )
1627
+
1628
+ mgr = TileManager(tile_grid, cache, sources, image_opts.format.ext,
1629
+ locker=locker,
1630
+ image_opts=image_opts, identifier=identifier,
1631
+ request_format=request_format_ext,
1632
+ meta_size=meta_size, meta_buffer=meta_buffer,
1633
+ minimize_meta_requests=minimize_meta_requests,
1634
+ concurrent_tile_creators=concurrent_tile_creators,
1635
+ pre_store_filter=tile_filter,
1636
+ tile_creator_class=tile_creator_class,
1637
+ bulk_meta_tiles=bulk_meta_tiles,
1638
+ cache_rescaled_tiles=cache_rescaled_tiles,
1639
+ rescale_tiles=rescale_tiles,
1640
+ )
1641
+ if self.conf['name'] in self.context.caches:
1642
+ mgr._refresh_before = self.context.caches[self.conf['name']].conf.get('refresh_before', {})
1643
+ extent = merge_layer_extents(sources)
1644
+ if extent.is_default:
1645
+ extent = map_extent_from_grid(tile_grid)
1646
+ caches.append((tile_grid, extent, mgr))
1647
+ return caches
1648
+
1649
+ @memoize
1650
+ def grid_confs(self):
1651
+ grid_names = self.conf.get('grids')
1652
+ if grid_names is None:
1653
+ log.warning('cache %s does not have any grids. default will change from [GLOBAL_MERCATOR] to [GLOBAL_WEBMERCATOR] with MapProxy 2.0',
1654
+ self.conf['name'])
1655
+ grid_names = ['GLOBAL_MERCATOR']
1656
+ return [(g, self.context.grids[g]) for g in grid_names]
1657
+
1658
+ @memoize
1659
+ def map_layer(self):
1660
+ from mapproxy.layer import CacheMapLayer, SRSConditional, ResolutionConditional
1661
+
1662
+ image_opts = self.image_opts()
1663
+ max_tile_limit = self.context.globals.get_value('max_tile_limit', self.conf,
1664
+ global_key='cache.max_tile_limit')
1665
+ caches = []
1666
+ main_grid = None
1667
+ for grid, extent, tile_manager in self.caches():
1668
+ if main_grid is None:
1669
+ main_grid = grid
1670
+ caches.append((CacheMapLayer(tile_manager, extent=extent, image_opts=image_opts,
1671
+ max_tile_limit=max_tile_limit),
1672
+ grid.srs))
1673
+
1674
+ if len(caches) == 1:
1675
+ layer = caches[0][0]
1676
+ else:
1677
+ layer = SRSConditional(caches, caches[0][0].extent, opacity=image_opts.opacity,
1678
+ preferred_srs=self.context.globals.preferred_srs)
1679
+
1680
+ if 'use_direct_from_level' in self.conf:
1681
+ self.conf['use_direct_from_res'] = main_grid.resolution(self.conf['use_direct_from_level'])
1682
+ if 'use_direct_from_res' in self.conf:
1683
+ if len(self.conf['sources']) != 1:
1684
+ raise ValueError('use_direct_from_level/res only supports single sources')
1685
+ source_conf = self.context.sources[self.conf['sources'][0]]
1686
+ layer = ResolutionConditional(layer, source_conf.source(), self.conf['use_direct_from_res'],
1687
+ main_grid.srs, layer.extent, opacity=image_opts.opacity)
1688
+ return layer
1689
+
1690
+
1691
+ class WMSLayerConfiguration(ConfigurationBase):
1692
+ @memoize
1693
+ def wms_layer(self):
1694
+ from mapproxy.service.wms import WMSGroupLayer
1695
+
1696
+ layers = []
1697
+ this_layer = None
1698
+
1699
+ if 'layers' in self.conf:
1700
+ layers_conf = self.conf['layers']
1701
+ for layer_conf in layers_conf:
1702
+ lyr = WMSLayerConfiguration(layer_conf, self.context).wms_layer()
1703
+ if lyr:
1704
+ layers.append(lyr)
1705
+
1706
+ if 'sources' in self.conf or 'legendurl' in self.conf:
1707
+ this_layer = LayerConfiguration(self.conf, self.context).wms_layer()
1708
+
1709
+ if not layers and not this_layer:
1710
+ return None
1711
+
1712
+ if not layers:
1713
+ layer = this_layer
1714
+ else:
1715
+ layer = WMSGroupLayer(name=self.conf.get('name'), title=self.conf.get('title'),
1716
+ this=this_layer, layers=layers, md=self.conf.get('md'))
1717
+ return layer
1718
+
1719
+ def cache_source_names(context, cache):
1720
+ """
1721
+ Return all sources for a cache, even if a caches uses another cache.
1722
+ """
1723
+ source_names = []
1724
+ for src in context.caches[cache].conf['sources']:
1725
+ if src in context.caches and src not in context.sources:
1726
+ source_names.extend(cache_source_names(context, src))
1727
+ else:
1728
+ source_names.append(src)
1729
+
1730
+ return source_names
1731
+
1732
+ class LayerConfiguration(ConfigurationBase):
1733
+ @memoize
1734
+ def wms_layer(self):
1735
+ from mapproxy.service.wms import WMSLayer
1736
+
1737
+ sources = []
1738
+ fi_sources = []
1739
+ lg_sources = []
1740
+
1741
+ lg_sources_configured = False
1742
+ if self.conf.get('legendurl'):
1743
+ legend_url = self.conf['legendurl']
1744
+ lg_sources.append(WMSSourceConfiguration.static_legend_source(legend_url, self.context))
1745
+ lg_sources_configured = True
1746
+
1747
+ for source_name in self.conf.get('sources', []):
1748
+ fi_source_names = []
1749
+ lg_source_names = []
1750
+ if source_name in self.context.caches:
1751
+ map_layer = self.context.caches[source_name].map_layer()
1752
+ fi_source_names = cache_source_names(self.context, source_name)
1753
+ lg_source_names = cache_source_names(self.context, source_name)
1754
+ elif source_name in self.context.sources:
1755
+ source_conf = self.context.sources[source_name]
1756
+ if not source_conf.supports_meta_tiles:
1757
+ raise ConfigurationError('source "%s" of layer "%s" does not support un-tiled access'
1758
+ % (source_name, self.conf.get('name')))
1759
+ map_layer = source_conf.source()
1760
+ fi_source_names = [source_name]
1761
+ lg_source_names = [source_name]
1762
+ else:
1763
+ raise ConfigurationError('source/cache "%s" not found' % source_name)
1764
+
1765
+ if map_layer:
1766
+ sources.append(map_layer)
1767
+
1768
+ for fi_source_name in fi_source_names:
1769
+ if fi_source_name not in self.context.sources: continue
1770
+ if not hasattr(self.context.sources[fi_source_name], 'fi_source'): continue
1771
+ fi_source = self.context.sources[fi_source_name].fi_source()
1772
+ if fi_source:
1773
+ fi_sources.append(fi_source)
1774
+ if not lg_sources_configured:
1775
+ for lg_source_name in lg_source_names:
1776
+ if lg_source_name not in self.context.sources: continue
1777
+ if not hasattr(self.context.sources[lg_source_name], 'lg_source'): continue
1778
+ lg_source = self.context.sources[lg_source_name].lg_source()
1779
+ if lg_source:
1780
+ lg_sources.append(lg_source)
1781
+
1782
+ res_range = resolution_range(self.conf)
1783
+ dimensions = None
1784
+ if 'dimensions' in self.conf.keys():
1785
+ dimensions = self.dimensions()
1786
+
1787
+ layer = WMSLayer(self.conf.get('name'), self.conf.get('title'),
1788
+ sources, fi_sources, lg_sources, res_range=res_range, md=self.conf.get('md'),dimensions=dimensions)
1789
+ return layer
1790
+
1791
+ @memoize
1792
+ def dimensions(self):
1793
+ from mapproxy.layer import Dimension
1794
+ from mapproxy.util.ext.wmsparse.util import parse_datetime_range
1795
+ dimensions = {}
1796
+ for dimension, conf in iteritems(self.conf.get('dimensions', {})):
1797
+ raw_values = conf.get('values')
1798
+ if len(raw_values) == 1:
1799
+ # look for time or dim_reference_time
1800
+ if 'time' in dimension.lower():
1801
+ log.debug('Determining values as datetime strings')
1802
+ values = parse_datetime_range(raw_values[0])
1803
+ else:
1804
+ log.debug('Determining values as plain strings')
1805
+ values = raw_values[0].strip().split('/')
1806
+ else:
1807
+ values = [str(val) for val in conf.get('values', ['default'])]
1808
+
1809
+ default = conf.get('default', values[-1])
1810
+ dimensions[dimension.lower()] = Dimension(dimension, values, default=default)
1811
+ return dimensions
1812
+
1813
+ @memoize
1814
+ def tile_layers(self, grid_name_as_path=False):
1815
+ from mapproxy.service.tile import TileLayer
1816
+ from mapproxy.cache.dummy import DummyCache
1817
+ from mapproxy.cache.file import FileCache
1818
+ sources = []
1819
+ fi_only_sources = []
1820
+ if 'tile_sources' in self.conf:
1821
+ sources = self.conf['tile_sources']
1822
+ else:
1823
+ for source_name in self.conf.get('sources', []):
1824
+ # we only support caches for tiled access...
1825
+ if not source_name in self.context.caches:
1826
+ if source_name in self.context.sources:
1827
+ src_conf = self.context.sources[source_name].conf
1828
+ # but we ignore debug layers for convenience
1829
+ if src_conf['type'] == 'debug':
1830
+ continue
1831
+ # and WMS layers with map: False (i.e. FeatureInfo only sources)
1832
+ if src_conf['type'] == 'wms' and src_conf.get('wms_opts', {}).get('map', True) == False:
1833
+ fi_only_sources.append(source_name)
1834
+ continue
1835
+
1836
+ return []
1837
+ sources.append(source_name)
1838
+
1839
+ if len(sources) > 1:
1840
+ # skip layers with more then one source
1841
+ return []
1842
+
1843
+
1844
+ dimensions = self.dimensions()
1845
+
1846
+ tile_layers = []
1847
+ for cache_name in sources:
1848
+ fi_sources = []
1849
+ fi_source_names = cache_source_names(self.context, cache_name)
1850
+
1851
+ for fi_source_name in fi_source_names + fi_only_sources:
1852
+ if fi_source_name not in self.context.sources: continue
1853
+ if not hasattr(self.context.sources[fi_source_name], 'fi_source'): continue
1854
+ fi_source = self.context.sources[fi_source_name].fi_source()
1855
+ if fi_source:
1856
+ fi_sources.append(fi_source)
1857
+
1858
+ for grid, extent, cache_source in self.context.caches[cache_name].caches():
1859
+ disable_storage = self.context.configuration['caches'][cache_name].get('disable_storage', False)
1860
+ if disable_storage:
1861
+ supported_cache_class = DummyCache
1862
+ else:
1863
+ supported_cache_class = FileCache
1864
+ if dimensions and not isinstance(cache_source.cache, supported_cache_class):
1865
+ # caching of dimension layers is not supported yet
1866
+ raise ConfigurationError(
1867
+ "caching of dimension layer (%s) is not supported yet."
1868
+ " need to `disable_storage: true` on %s cache" % (self.conf['name'], cache_name)
1869
+ )
1870
+
1871
+ md = {}
1872
+ md['title'] = self.conf['title']
1873
+ md['name'] = self.conf['name']
1874
+ md['grid_name'] = grid.name
1875
+ if grid_name_as_path:
1876
+ md['name_path'] = (md['name'], md['grid_name'])
1877
+ else:
1878
+ md['name_path'] = (md['name'], grid.srs.srs_code.replace(':', '').upper())
1879
+ md['name_internal'] = md['name_path'][0] + '_' + md['name_path'][1]
1880
+ md['format'] = self.context.caches[cache_name].image_opts().format
1881
+ md['cache_name'] = cache_name
1882
+ md['extent'] = extent
1883
+ tile_layers.append(
1884
+ TileLayer(
1885
+ self.conf['name'], self.conf['title'],
1886
+ info_sources=fi_sources,
1887
+ md=md,
1888
+ tile_manager=cache_source,
1889
+ dimensions=dimensions,
1890
+ )
1891
+ )
1892
+
1893
+ return tile_layers
1894
+
1895
+
1896
+ def fi_xslt_transformers(conf, context):
1897
+ from mapproxy.featureinfo import XSLTransformer, has_xslt_support
1898
+ fi_transformers = {}
1899
+ fi_xslt = conf.get('featureinfo_xslt')
1900
+ if fi_xslt:
1901
+ if not has_xslt_support:
1902
+ raise ValueError('featureinfo_xslt requires lxml. Please install.')
1903
+ for info_type, fi_xslt in fi_xslt.items():
1904
+ fi_xslt = context.globals.abspath(fi_xslt)
1905
+ fi_transformers[info_type] = XSLTransformer(fi_xslt)
1906
+ return fi_transformers
1907
+
1908
+ def extents_for_srs(bbox_srs):
1909
+ from mapproxy.layer import DefaultMapExtent, MapExtent
1910
+ from mapproxy.srs import SRS
1911
+ extents = {}
1912
+ for srs in bbox_srs:
1913
+ if isinstance(srs, str):
1914
+ bbox = DefaultMapExtent()
1915
+ else:
1916
+ srs, bbox = srs['srs'], srs['bbox']
1917
+ bbox = MapExtent(bbox, SRS(srs))
1918
+
1919
+ extents[srs] = bbox
1920
+
1921
+ return extents
1922
+
1923
+
1924
+ plugin_services = {}
1925
+
1926
+ def register_service_configuration(service_name, service_creator,
1927
+ yaml_spec_service_name = None, yaml_spec_service_def = None):
1928
+ """ Method used by plugins to register a new service.
1929
+
1930
+ :param service_name: Name of the service
1931
+ :type service_name: str
1932
+ :param service_creator: Creator method of the service
1933
+ :type service_creator: method of type (serviceConfiguration: ServiceConfiguration, conf: dict) -> Server
1934
+ :param yaml_spec_service_name: Name of the service in the YAML configuration file
1935
+ :type yaml_spec_service_name: str
1936
+ :param yaml_spec_service_def: Definition of the service in the YAML configuration file
1937
+ :type yaml_spec_service_def: dict
1938
+ """
1939
+
1940
+ log.info('Registering configuration for plugin service %s' % service_name)
1941
+ plugin_services[service_name] = service_creator
1942
+ if yaml_spec_service_name is not None and yaml_spec_service_def is not None:
1943
+ add_service_to_mapproxy_yaml_spec(yaml_spec_service_name, yaml_spec_service_def)
1944
+
1945
+
1946
+ class ServiceConfiguration(ConfigurationBase):
1947
+ def __init__(self, conf, context):
1948
+ if 'wms' in conf:
1949
+ if conf['wms'] is None:
1950
+ conf['wms'] = {}
1951
+ if 'md' not in conf['wms']:
1952
+ conf['wms']['md'] = {'title': 'MapProxy WMS'}
1953
+
1954
+ ConfigurationBase.__init__(self, conf, context)
1955
+
1956
+ def services(self):
1957
+ services = []
1958
+ ows_services = []
1959
+ for service_name, service_conf in iteritems(self.conf):
1960
+ creator = getattr(self, service_name + '_service', None)
1961
+ if not creator:
1962
+ # If not a known service, try to use the plugin mechanism
1963
+ global plugin_services
1964
+ creator = plugin_services.get(service_name, None)
1965
+ if not creator:
1966
+ raise ValueError('unknown service: %s' % service_name)
1967
+ new_services = creator(self, service_conf or {})
1968
+ else:
1969
+ new_services = creator(service_conf or {})
1970
+
1971
+ # a creator can return a list of services...
1972
+ if not isinstance(new_services, (list, tuple)):
1973
+ new_services = [new_services]
1974
+
1975
+ for new_service in new_services:
1976
+ if getattr(new_service, 'service', None):
1977
+ ows_services.append(new_service)
1978
+ else:
1979
+ services.append(new_service)
1980
+
1981
+ if ows_services:
1982
+ from mapproxy.service.ows import OWSServer
1983
+ services.append(OWSServer(ows_services))
1984
+ return services
1985
+
1986
+ def tile_layers(self, conf, use_grid_names=False):
1987
+ layers = odict()
1988
+ for layer_name, layer_conf in iteritems(self.context.layers):
1989
+ for tile_layer in layer_conf.tile_layers(grid_name_as_path=use_grid_names):
1990
+ if not tile_layer: continue
1991
+ if use_grid_names:
1992
+ layers[tile_layer.md['name_path']] = tile_layer
1993
+ else:
1994
+ layers[tile_layer.md['name_internal']] = tile_layer
1995
+ return layers
1996
+
1997
+ def kml_service(self, conf):
1998
+ from mapproxy.service.kml import KMLServer
1999
+
2000
+ md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
2001
+ md.update(conf.get('md', {}))
2002
+ max_tile_age = self.context.globals.get_value('tiles.expires_hours')
2003
+ max_tile_age *= 60 * 60 # seconds
2004
+ use_grid_names = conf.get('use_grid_names', False)
2005
+ layers = self.tile_layers(conf, use_grid_names=use_grid_names)
2006
+ return KMLServer(layers, md, max_tile_age=max_tile_age, use_dimension_layers=use_grid_names)
2007
+
2008
+ def tms_service(self, conf):
2009
+ from mapproxy.service.tile import TileServer
2010
+
2011
+ md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
2012
+ md.update(conf.get('md', {}))
2013
+ max_tile_age = self.context.globals.get_value('tiles.expires_hours')
2014
+ max_tile_age *= 60 * 60 # seconds
2015
+
2016
+ origin = conf.get('origin')
2017
+ use_grid_names = conf.get('use_grid_names', False)
2018
+ layers = self.tile_layers(conf, use_grid_names=use_grid_names)
2019
+ return TileServer(layers, md, max_tile_age=max_tile_age, use_dimension_layers=use_grid_names,
2020
+ origin=origin)
2021
+
2022
+ def wmts_service(self, conf):
2023
+ from mapproxy.service.wmts import WMTSServer, WMTSRestServer
2024
+
2025
+ md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
2026
+ md.update(conf.get('md', {}))
2027
+ layers = self.tile_layers(conf, use_grid_names=True)
2028
+
2029
+ kvp = conf.get('kvp')
2030
+ restful = conf.get('restful')
2031
+
2032
+ max_tile_age = self.context.globals.get_value('tiles.expires_hours')
2033
+ max_tile_age *= 60 * 60 # seconds
2034
+
2035
+ info_formats = conf.get('featureinfo_formats', [])
2036
+ info_formats = odict((f['suffix'], f['mimetype']) for f in info_formats)
2037
+
2038
+ if kvp is None and restful is None:
2039
+ kvp = restful = True
2040
+
2041
+ services = []
2042
+ if kvp:
2043
+ services.append(
2044
+ WMTSServer(
2045
+ layers, md, max_tile_age=max_tile_age,
2046
+ info_formats=info_formats,
2047
+ )
2048
+ )
2049
+
2050
+ if restful:
2051
+ template = conf.get('restful_template')
2052
+ fi_template = conf.get('restful_featureinfo_template')
2053
+ if template and '{{' in template:
2054
+ # TODO remove warning in 1.6
2055
+ log.warning("double braces in WMTS restful_template are deprecated {{x}} -> {x}")
2056
+ services.append(
2057
+ WMTSRestServer(
2058
+ layers, md, template=template,
2059
+ fi_template=fi_template,
2060
+ max_tile_age=max_tile_age,
2061
+ info_formats=info_formats,
2062
+ )
2063
+ )
2064
+
2065
+ return services
2066
+
2067
+ def wms_service(self, conf):
2068
+ from mapproxy.service.wms import WMSServer
2069
+ from mapproxy.request.wms import Version
2070
+
2071
+ md = conf.get('md', {})
2072
+ inspire_md = conf.get('inspire_md', {})
2073
+ tile_layers = self.tile_layers(conf)
2074
+ attribution = conf.get('attribution')
2075
+ strict = self.context.globals.get_value('strict', conf, global_key='wms.strict')
2076
+ on_source_errors = self.context.globals.get_value('on_source_errors',
2077
+ conf, global_key='wms.on_source_errors')
2078
+ root_layer = self.context.wms_root_layer.wms_layer()
2079
+ if not root_layer:
2080
+ raise ConfigurationError("found no WMS layer")
2081
+ if not root_layer.title:
2082
+ # set title of root layer to WMS title
2083
+ root_layer.title = md.get('title')
2084
+ concurrent_layer_renderer = self.context.globals.get_value(
2085
+ 'concurrent_layer_renderer', conf,
2086
+ global_key='wms.concurrent_layer_renderer')
2087
+ image_formats_names = self.context.globals.get_value('image_formats', conf,
2088
+ global_key='wms.image_formats')
2089
+ image_formats = odict()
2090
+ for format in image_formats_names:
2091
+ opts = self.context.globals.image_options.image_opts({}, format)
2092
+ if opts.format in image_formats:
2093
+ log.warning('duplicate mime-type for WMS image_formats: "%s" already configured, will use last format',
2094
+ opts.format)
2095
+ image_formats[opts.format] = opts
2096
+ info_types = conf.get('featureinfo_types')
2097
+ srs = self.context.globals.get_value('srs', conf, global_key='wms.srs')
2098
+ self.context.globals.base_config.wms.srs = srs
2099
+ srs_extents = extents_for_srs(conf.get('bbox_srs', []))
2100
+
2101
+ versions = conf.get('versions')
2102
+ if versions:
2103
+ versions = sorted([Version(v) for v in versions])
2104
+
2105
+ max_output_pixels = self.context.globals.get_value('max_output_pixels', conf,
2106
+ global_key='wms.max_output_pixels')
2107
+ if isinstance(max_output_pixels, list):
2108
+ max_output_pixels = max_output_pixels[0] * max_output_pixels[1]
2109
+
2110
+ max_tile_age = self.context.globals.get_value('tiles.expires_hours')
2111
+ max_tile_age *= 60 * 60 # seconds
2112
+
2113
+ server = WMSServer(root_layer, md, attribution=attribution,
2114
+ image_formats=image_formats, info_types=info_types,
2115
+ srs=srs, tile_layers=tile_layers, strict=strict, on_error=on_source_errors,
2116
+ concurrent_layer_renderer=concurrent_layer_renderer,
2117
+ max_output_pixels=max_output_pixels, srs_extents=srs_extents,
2118
+ max_tile_age=max_tile_age, versions=versions,
2119
+ inspire_md=inspire_md,
2120
+ )
2121
+
2122
+ server.fi_transformers = fi_xslt_transformers(conf, self.context)
2123
+
2124
+ return server
2125
+
2126
+ def demo_service(self, conf):
2127
+ from mapproxy.service.demo import DemoServer
2128
+ services = list(self.context.services.conf.keys())
2129
+ md = self.context.services.conf.get('wms', {}).get('md', {}).copy()
2130
+ md.update(conf.get('md', {}))
2131
+ layers = odict()
2132
+ for layer_name, layer_conf in iteritems(self.context.layers):
2133
+ lyr = layer_conf.wms_layer()
2134
+ if lyr:
2135
+ layers[layer_name] = lyr
2136
+ image_formats = self.context.globals.get_value('image_formats', conf, global_key='wms.image_formats')
2137
+ srs = self.context.globals.get_value('srs', conf, global_key='wms.srs')
2138
+ tms_conf = self.context.services.conf.get('tms', {}) or {}
2139
+ use_grid_names = tms_conf.get('use_grid_names', False)
2140
+ tile_layers = self.tile_layers(tms_conf, use_grid_names=use_grid_names)
2141
+
2142
+ # WMTS restful template
2143
+ wmts_conf = self.context.services.conf.get('wmts', {}) or {}
2144
+ from mapproxy.service.wmts import WMTSRestServer
2145
+ if wmts_conf:
2146
+ restful_template = wmts_conf.get('restful_template', WMTSRestServer.default_template)
2147
+ else:
2148
+ restful_template = WMTSRestServer.default_template
2149
+
2150
+ if 'wmts' in self.context.services.conf:
2151
+ kvp = wmts_conf.get('kvp')
2152
+ restful = wmts_conf.get('restful')
2153
+
2154
+ if kvp is None and restful is None:
2155
+ kvp = restful = True
2156
+
2157
+ if kvp:
2158
+ services.append('wmts_kvp')
2159
+ if restful:
2160
+ services.append('wmts_restful')
2161
+
2162
+ if 'wms' in self.context.services.conf:
2163
+ versions = self.context.services.conf['wms'].get('versions', ['1.1.1'])
2164
+ if '1.1.1' in versions:
2165
+ # demo service only supports 1.1.1, use wms_111 as an indicator
2166
+ services.append('wms_111')
2167
+
2168
+ return DemoServer(layers, md, tile_layers=tile_layers,
2169
+ image_formats=image_formats, srs=srs, services=services, restful_template=restful_template)
2170
+
2171
+
2172
+ def load_plugins():
2173
+ """ Locate plugins that belong to the 'mapproxy' group and load them """
2174
+ if sys.version_info >= (3, 8):
2175
+ from importlib import metadata as importlib_metadata
2176
+ else:
2177
+ try:
2178
+ import importlib_metadata
2179
+ except ImportError:
2180
+ return
2181
+
2182
+ for dist in importlib_metadata.distributions():
2183
+ for ep in dist.entry_points:
2184
+ if ep.group == 'mapproxy':
2185
+ log.info('Loading plugin from package %s' % dist.metadata['name'])
2186
+ ep.load().plugin_entrypoint()
2187
+
2188
+
2189
+ def load_configuration(mapproxy_conf, seed=False, ignore_warnings=True, renderd=False):
2190
+
2191
+ load_plugins()
2192
+
2193
+ conf_base_dir = os.path.abspath(os.path.dirname(mapproxy_conf))
2194
+
2195
+ # A configuration is checked/validated four times, each step has a different
2196
+ # focus and returns different errors. The steps are:
2197
+ # 1. YAML loading: checks YAML syntax like tabs vs. space, indention errors, etc.
2198
+ # 2. Options: checks all options agains the spec and validates their types,
2199
+ # e.g is disable_storage a bool, is layers a list, etc.
2200
+ # 3. References: checks if all referenced caches, sources and grids exist
2201
+ # 4. Initialization: creates all MapProxy objects, returns on first error
2202
+
2203
+ try:
2204
+ conf_dict = load_configuration_file([os.path.basename(mapproxy_conf)], conf_base_dir)
2205
+ except YAMLError as ex:
2206
+ raise ConfigurationError(ex)
2207
+ errors, informal_only = validate_options(conf_dict)
2208
+ for error in errors:
2209
+ log.warning(error)
2210
+ if not informal_only or (errors and not ignore_warnings):
2211
+ raise ConfigurationError('invalid configuration')
2212
+ errors = validate_references(conf_dict)
2213
+ for error in errors:
2214
+ log.warning(error)
2215
+
2216
+ services = conf_dict.get('services')
2217
+ if services is not None and 'demo' in services:
2218
+ log.warning('Application has demo page enabled. It is recommended to disable this in production.')
2219
+
2220
+ return ProxyConfiguration(conf_dict, conf_base_dir=conf_base_dir, seed=seed,
2221
+ renderd=renderd)
2222
+
2223
+ def load_configuration_file(files, working_dir):
2224
+ """
2225
+ Return configuration dict from imported files
2226
+ """
2227
+ # record all config files with timestamp for reloading
2228
+ conf_dict = {'__config_files__': {}}
2229
+ for conf_file in files:
2230
+ conf_file = os.path.normpath(os.path.join(working_dir, conf_file))
2231
+ log.info('reading: %s' % conf_file)
2232
+ current_dict = load_yaml_file(conf_file)
2233
+ conf_dict['__config_files__'][os.path.abspath(conf_file)] = os.path.getmtime(conf_file)
2234
+ if 'base' in current_dict:
2235
+ current_working_dir = os.path.dirname(conf_file)
2236
+ base_files = current_dict.pop('base')
2237
+ if isinstance(base_files, string_type):
2238
+ base_files = [base_files]
2239
+ imported_dict = load_configuration_file(base_files, current_working_dir)
2240
+ current_dict = merge_dict(current_dict, imported_dict)
2241
+ conf_dict = merge_dict(conf_dict, current_dict)
2242
+
2243
+ return conf_dict
2244
+
2245
+ def merge_dict(conf, base):
2246
+ """
2247
+ Return `base` dict with values from `conf` merged in.
2248
+ """
2249
+ for k, v in iteritems(conf):
2250
+ if k not in base:
2251
+ base[k] = v
2252
+ else:
2253
+ if isinstance(base[k], dict):
2254
+ merge_dict(v, base[k])
2255
+ else:
2256
+ base[k] = v
2257
+ return base
2258
+
2259
+ def parse_color(color):
2260
+ """
2261
+ >>> parse_color((100, 12, 55))
2262
+ (100, 12, 55)
2263
+ >>> parse_color('0xff0530')
2264
+ (255, 5, 48)
2265
+ >>> parse_color('#FF0530')
2266
+ (255, 5, 48)
2267
+ >>> parse_color('#FF053080')
2268
+ (255, 5, 48, 128)
2269
+ """
2270
+ if isinstance(color, (list, tuple)) and 3 <= len(color) <= 4:
2271
+ return tuple(color)
2272
+ if not isinstance(color, string_type):
2273
+ raise ValueError('color needs to be a tuple/list or 0xrrggbb/#rrggbb(aa) string, got %r' % color)
2274
+
2275
+ if color.startswith('0x'):
2276
+ color = color[2:]
2277
+ if color.startswith('#'):
2278
+ color = color[1:]
2279
+
2280
+ r, g, b = map(lambda x: int(x, 16), [color[:2], color[2:4], color[4:6]])
2281
+
2282
+ if len(color) == 8:
2283
+ a = int(color[6:8], 16)
2284
+ return r, g, b, a
2285
+
2286
+ return r, g, b