1"""!
2@brief GDAL WMS driver.
3
4List of classes:
5 - wms_drv::NullDevice
6 - wms_drv::WMSGdalDrv
7
8(C) 2012-2021 by the GRASS Development Team
9
10This program is free software under the GNU General Public License
11(>=v2). Read the file COPYING that comes with GRASS for details.
12
13@author Stepan Turek <stepan.turek seznam.cz> (Mentor: Martin Landa)
14"""
15
16import os
17import grass.script as grass
18
19try:
20    from osgeo import gdal
21    from osgeo import gdalconst
22except:
23    grass.fatal(_("Unable to load GDAL Python bindings (requires package 'python-gdal' being installed)"))
24
25import xml.etree.ElementTree as etree
26
27from wms_base import WMSBase, GetSRSParamVal
28
29
30class NullDevice():
31
32    def write(self, s):
33        pass
34
35
36class WMSGdalDrv(WMSBase):
37    def __init__(self, createopt):
38        super(WMSGdalDrv, self).__init__()
39        self.proxy = None
40        self.proxy_user_pw = None
41        self.createopt = createopt
42
43    def setProxy(self, proxy, proxy_user_pw=None):
44        """ Set the HTTP proxy and its user and password
45
46        @input proxy HTTP proxy with [IP address]:[port]
47        @input proxy_user_pw with [user name]:[password]
48        """
49        self.proxy = proxy
50        self.proxy_user_pw = proxy_user_pw
51
52    def _createXML(self):
53        """!Create XML for GDAL WMS driver
54
55        @return path to XML file
56        """
57        self._debug("_createXML", "started")
58
59        gdal_wms = etree.Element("GDAL_WMS")
60        service = etree.SubElement(gdal_wms, "Service")
61        name = etree.Element("name")
62        service.set("name", "WMS")
63
64        version = etree.SubElement(service, "Version")
65        version.text = self.params['wms_version']
66
67        server_url = etree.SubElement(service, "ServerUrl")
68        server_url.text = self.params['url']
69
70        srs = etree.SubElement(service, self.params['proj_name'])
71        srs.text = GetSRSParamVal(self.params['srs'])
72
73        image_format = etree.SubElement(service, "ImageFormat")
74        image_format.text = self.params['format']
75
76        image_format = etree.SubElement(service, "Transparent")
77        image_format.text = self.params['transparent']
78
79        layers = etree.SubElement(service, "Layers")
80        layers.text = self.params['layers']
81
82        styles = etree.SubElement(service, "Styles")
83        styles.text = self.params['styles']
84
85        data_window = etree.SubElement(gdal_wms, "DataWindow")
86
87        upper_left_x = etree.SubElement(data_window, "UpperLeftX")
88        upper_left_x.text = str(self.bbox['minx'])
89
90        upper_left_y = etree.SubElement(data_window, "UpperLeftY")
91        upper_left_y.text = str(self.bbox['maxy'])
92
93        lower_right_x = etree.SubElement(data_window, "LowerRightX")
94        lower_right_x.text = str(self.bbox['maxx'])
95
96        lower_right_y = etree.SubElement(data_window, "LowerRightY")
97        lower_right_y.text = str(self.bbox['miny'])
98
99        size_x = etree.SubElement(data_window, "SizeX")
100        size_x.text = str(self.region['cols'])
101
102        size_y = etree.SubElement(data_window, "SizeY")
103        size_y.text = str(self.region['rows'])
104
105        # RGB + alpha
106        self.temp_map_bands_num = 4
107        block_size_x = etree.SubElement(gdal_wms, "BandsCount")
108        block_size_x.text = str(self.temp_map_bands_num)
109
110        block_size_x = etree.SubElement(gdal_wms, "BlockSizeX")
111        block_size_x.text = str(self.tile_size['cols'])
112
113        block_size_y = etree.SubElement(gdal_wms, "BlockSizeY")
114        block_size_y.text = str(self.tile_size['rows'])
115
116        if self.params['username'] and self.params['password']:
117            user_password = etree.SubElement(gdal_wms, "UserPwd")
118            user_password.text = "%s:%s" % (self.params['username'], self.params['password'])
119
120        xml_file = self._tempfile()
121
122        etree.ElementTree(gdal_wms).write(xml_file)
123
124        self._debug("_createXML", "finished -> %s" % xml_file)
125
126        return xml_file
127
128    def _download(self):
129        """!Downloads data from WMS server using GDAL WMS driver
130
131        @return temp_map with stored downloaded data
132        """
133        grass.message("Downloading data from WMS server...")
134
135        # GDAL WMS driver does not flip geographic coordinates
136        # according to WMS standard 1.3.0.
137        if ("+proj=latlong" in self.proj_srs or
138                "+proj=longlat" in self.proj_srs) and \
139                self.params['wms_version'] == "1.3.0":
140            grass.warning(_("If module will not be able to fetch the data in this " +
141                            "geographic projection, \n try 'WMS_GRASS' driver or use WMS version 1.1.1."))
142
143        self._debug("_download", "started")
144        temp_map = self._tempfile()
145
146        xml_file = self._createXML()
147
148        # print xml file content for debug level 1
149        file = open(xml_file, "r")
150        grass.debug("WMS request XML:\n%s" % file.read(), 1)
151        file.close()
152
153        if self.proxy:
154            gdal.SetConfigOption('GDAL_HTTP_PROXY', str(self.proxy))
155        if self.proxy_user_pw:
156            gdal.SetConfigOption('GDAL_HTTP_PROXYUSERPWD', str(self.proxy_user_pw))
157        wms_dataset = gdal.Open(xml_file, gdal.GA_ReadOnly)
158        grass.try_remove(xml_file)
159        if wms_dataset is None:
160            grass.fatal(_("Unable to open GDAL WMS driver"))
161
162        self._debug("_download", "GDAL dataset created")
163
164        driver = gdal.GetDriverByName(self.gdal_drv_format)
165        if driver is None:
166            grass.fatal(_("Unable to find %s driver" % format))
167
168        metadata = driver.GetMetadata()
169        if gdal.DCAP_CREATECOPY not in metadata or \
170           metadata[gdal.DCAP_CREATECOPY] == 'NO':
171            grass.fatal(_('Driver %s supports CreateCopy() method.') % self.gdal_drv_name)
172
173        self._debug("_download", "calling GDAL CreateCopy...")
174
175        if self.createopt is None:
176            temp_map_dataset = driver.CreateCopy(temp_map, wms_dataset, 0)
177        else:
178            self._debug("_download", "Using GDAL createopt <%s>" % str(self.createopt))
179            temp_map_dataset = driver.CreateCopy(
180                temp_map, wms_dataset, 0, self.createopt
181            )
182
183        if temp_map_dataset is None:
184            grass.fatal(_("Incorrect WMS query"))
185
186        temp_map_dataset = None
187        wms_dataset = None
188
189        self._debug("_download", "finished")
190
191        return temp_map
192