""" Module to implement WMS services as a protocol. """ import os import re from xml.etree import ElementTree import tempfile import shutil import base64 import requests # Fix gdal 2.40 and 3.3 integration problems try: import ogr import osr except ModuleNotFoundError: from osgeo import ogr, osr from tools.protocols.masterProtocol import MasterProtocol from tools import const M2RAD = 180.0 / (3.1416 * 6378388) MAX_DIST = 1000*M2RAD class LOCAL(MasterProtocol): """ Class to control de WMS protocol Attributes ---------- CONNECTION_REQUIRED: dict Mapping of required connection parameters. Only is important when GMS is used with Itasker. NAME: str Protocol identifier string. Parameters ---------- ip: str Url of service """ CONNECTION_REQUIRED = {'fileType': 'driver_type', 'ip': 'ip', 'user': 'user', 'password': 'password', 'port': 'port', 'source': 'source', 'domain': 'domain', 'subtype': 'type', 'layerName': 'layer_name'} NAME = const.WMS_KEY def __init__(self, ip, port, user, password, *_, **__): super().__init__() self.ip = ip self.port = port self.user = user self.password = password self.gdal_wms_conf = "" \ "" \ " {url}" \ " EPSG:4326" \ " {{layer}}" \ "" \ "" \ " 2666666" \ " 1333333" \ "" \ "204,404.500" \ "true" \ "".format(url=self.ip) self._available_layers = [] self._parse() def _parse(self): # """ # Method to parse the GetCapabilities document of WMS service # """ # if self.user: # encoded_auth_token = base64.b64encode("{}:{}".format(self.user, # self.password).encode('utf8') # ).decode('utf8') # headers = {'Authorization': "Basic {}".format(encoded_auth_token)} # resp = requests.get("{url}?&SERVICE=WMS&REQUEST=GetCapabilities".format(url=self.ip), # headers=headers) # else: # resp = requests.get("{url}?&SERVICE=WMS&REQUEST=GetCapabilities".format(url=self.ip)) # # Searching xml namespaces # xml_string = resp.content.decode() # results = re.findall("xmlns:?(.*?)=\"(.*?)\"", xml_string) # ns = dict(results) # root = ElementTree.fromstring(xml_string) # for layer_str in root.find('{{{}}}Capability'.format(ns[''])).find( # '{{{}}}Layer'.format(ns[''])).findall('{{{}}}Layer'.format(ns[''])): # name = layer_str.find('{{{}}}Name'.format(ns[''])) # self._available_layers.append(name.text) return 0 def exists(self, layer): """ Method to check if the layer exist inside the WMS service Parameters ---------- layer: Layer Layer containing the service. Returns ------- bool: True if exists, False otherwise """ return layer['source'] in self._available_layers def gdal_layer(self, layer, with_vsi): """ Method to get connection string in gdal format. Parameters ---------- layer: Layer Layer to get the connection string from. with_vsi: bool True if want the string with the vsi (virtual drivers) prefix. Returns ------- str: Connection string """ return self.gdal_url(layer['source'], with_vsi=with_vsi, driver_prefix=layer.driver.gdal_prefix) def gdal_url(self, source, **__): """ Parameters ---------- source: str Path to source Returns ------- str: Gdal connection string """ return self._mount_connection(source) def _mount_connection(self, source): """ Method to mount the service configuration in XML format. For more information go to https://gdal.org/drivers/raster/wms.html. Parameters ---------- source: str Source to mount, corresponds with the layer of the WMS Returns ------- str: Connection string in xml format. """ tmp_dir = tempfile.mkdtemp(dir='tmp') xml_tmp = tempfile.NamedTemporaryFile(dir=tmp_dir, suffix='.xml').name connection_string = "WMS:{url}?" \ "SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&" \ "LAYERS={source}".format(url=self.ip, source=source) # Aqui, la source es "OI.OrthoimageCoverage", no "Ortoimagen" # Automatic generation of main xml. command = "gdal_translate \"{wms_connection}\" {xml} -of WMS"\ .format(wms_connection=connection_string, xml=xml_tmp) os.system(command) root = ElementTree.parse(xml_tmp).getroot() shutil.rmtree(tmp_dir) # Added http codes that raise a ZeroBlock event. zeroblocks = ElementTree.Element('ZeroBlockHttpCodes') zeroblocks.text = '204,400,500' root.append(zeroblocks) # True to avoid the service to fail when a ZeroBlock event arise. zeroblocks = ElementTree.Element('ZeroBlockOnServerException') zeroblocks.text = 'true' root.append(zeroblocks) # Added user and password for HTTP authentication if self.user: userpwd = ElementTree.Element('UserPwd') userpwd.text = "{user}:{password}".format(user=self.user, password=self.password) root.append(userpwd) return ElementTree.tostring(root).decode().replace('"', '\\"') def is_file(self, path): return True def upload(self, *_, **__): """ With a WMS protocol the upload operation is not permitted """ return None def download(self, src_path, dst, *_, **kwargs): """ Download method. Due to a WMS service could be very big, a crop by a vector layer must be done and download the resulting crop. Parameters ---------- src_path: str Source pointing to the layer, in this method it is ignored. dst: str Local path to download the file. kwargs: dict Dictionary containing: - vector: Layer object containing a vector layer to crop the service - res: float number pointing the pixel per meter of the resulting crop. Returns ------- str: Path to downloaded file. """ tmp_dir = tempfile.mkdtemp(dir=os.path.dirname(dst)) res = kwargs['res']*M2RAD # Check srs of vector layer, must be in 4326 to crop the WMS service. ds = ogr.Open(kwargs['vector'].gdal_layer()) ds_layer = list(ds)[0] osr_4326 = osr.SpatialReference() osr_4326.ImportFromEPSG(4326) is_4326 = ds_layer.GetSpatialRef().ExportToWkt() == osr_4326.ExportToWkt() extent = ds_layer.GetExtent() del ds if not is_4326: # Change srs of vector layer from original to 4326, working with WKT srs reprojected_source = "{}/reprojected.geojson".format(tmp_dir) command = "ogr2ogr -overwrite -t_srs \"{t_srs}\" \"{dst}\" \"{src}\"" os.system(command.format(dst=reprojected_source, src=kwargs['vector'].gdal_layer(), t_srs=osr_4326.ExportToWkt().replace('"', '\\"')),) kwargs['vector'].update_driver_and_protocol({ 'protocol': 'LOCAL', 'type': const.VECTOR_KEY, 'ip': '0.0.0.0', 'user': '', 'password': '', 'port': 99999, 'source': reprojected_source, 'driver_type': const.GJSON_KEY, 'layer_name': reprojected_source.split(os.sep)[-1].split('.')[0] }) ds = ogr.Open(kwargs['vector'].gdal_layer()) extent = list(ds)[0].GetExtent() del ds all_formats = [] if src_path.split(',') != -1: for word in src_path.split(','): all_formats.append(word.upper()) all_formats.append(word.lower()) else: all_formats.append(src_path.upper()) all_formats.append(src_path.lower()) # Cortamos el ecw y lo convertimos en tiff for index,formato in enumerate(all_formats): command = """gdal_translate -of GTiff -projwin {ulx} {uly} {lrx} {lry} -projwin_srs EPSG:4326 {tmp_dir}/cropped_tif.tif /home/master_folder/{image}/*.{format_files}""".format(ulx=extent[0], uly=extent[3], lrx=extent[1], lry=extent[2], tmp_dir=tmp_dir, image=self.ip, format_files=formato) try: os.system(command) print(command) except: pass try: os.mkdir('{tmp_dir}/tiles'.format(tmp_dir=tmp_dir)) shutil.move('{tmp_dir}/cropped_tif.tif'.format(tmp_dir=tmp_dir), '{tmp_dir}/tiles'.format(tmp_dir=tmp_dir)) except: pass # Make a vrt of tiles to crop all with the desired vector layer command = "gdalbuildvrt -tr {resolution} {resolution} " \ "{tmp_dir}/merge.vrt {tmp_dir}/tiles/*".format(resolution=res, tmp_dir=tmp_dir) os.system(command) print(command) # Apply crop command = "gdalwarp -q -overwrite -dstalpha -cutline \"{layer_vector}\" " \ "-crop_to_cutline -tr {resolution} {resolution} {tmp_dir}/merge.vrt {dst} " \ "-co BIGTIFF=YES -co TILED=YES " \ "--config CHECK_DISK_FREE_SPACE FALSE" \ .format(layer_vector=kwargs['vector'].gdal_layer(), tmp_dir=tmp_dir, dst=dst, resolution=res) os.system(command) print(command) shutil.rmtree("{}".format(tmp_dir)) return dst def format_parameters(self, _, parameters): """ Method to format the parameters to work with WMS protocol. Nothing is required. Parameters ---------- _ parameters: dict Parameters of the protocol Returns ------- dict: Parameters """ return parameters