"""
Module to implement TMS services as a protocol.
"""
import os
import re
import tempfile
import shutil
# Fix gdal 2.40 and 3.3 integration problems
try:
import ogr
import osr
except ModuleNotFoundError:
from osgeo import ogr, osr
from tools import const
from tools.protocols.masterProtocol import MasterProtocol
M2RAD = 180.0 / (3.1416 * 6378388)
MAX_DIST = 1000
class TMS(MasterProtocol):
"""
Class to control de TMS 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',
'source': 'source',
'port': 'port', 'domain': 'domain',
'user': 'user', 'password': 'password',
'subtype': 'type', 'layerName': 'layer_name'}
NAME = const.TMS_KEY
def __init__(self, ip, port, user, password, *_, **__):
super().__init__()
ip = re.sub(r'(?:(?}', ip)
self.ip = ip.replace("$", r"\$")
self.port = port
self.user = user
self.password = password
self.cred_string = "{user}:{password}"\
.format(user=user, password=password)*bool(user)
self.gdal_wms_conf = "" \
"" \
" {url}" \
"" \
"" \
" -20037508.34" \
" 20037508.34" \
" 20037508.34" \
" -20037508.34" \
" 21" \
" 1" \
" 1" \
" top" \
"" \
"EPSG:3857" \
"256" \
"256" \
"3" + \
self.cred_string + \
"204,404,500" \
"true" \
""
def exists(self, _):
"""
Check if the layer exists. Always return True.
Returns
-------
"""
return True
def upload(self, *_, **__):
"""
Upload data to a TMS service is not allowed
"""
return None
def gdal_layer(self, *_):
"""
Get gdal connection string to open the service.
"""
return self.gdal_wms_conf.format(url=self.ip,
credentials=self.cred_string)
def gdal_url(self, *_, **__):
"""
Get gdal connection string to open the service.
In that case is exactly the same as
gdal_layer
"""
return self.gdal_wms_conf.format(url=self.ip,
credentials=self.cred_string)
def is_file(self, path):
"""
Here is not used
"""
return True
def download(self, src_path, dst, *_, **kwargs):
"""
Download method. Due to a TMS 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 raster.
"""
tmp_dir = tempfile.mkdtemp(dir=os.path.dirname(dst))
ds_vector = ogr.Open(kwargs['vector'].gdal_layer())
ds_layer = list(ds_vector)[0]
osr_3857 = osr.SpatialReference()
osr_3857.ImportFromEPSG(3857)
is_3857 = ds_layer.GetSpatialRef().ExportToWkt() == osr_3857.ExportToWkt()
extent = ds_layer.GetExtent()
del ds_vector
if not is_3857:
# 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_3857.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
# # First creating a vrt with the bbox of the crop
os.system("gdal_translate \"{raster_layer}\" {tmp_dir}/cropped.vrt "
"-projwin {ulx} {uly} {lrx} {lry} -tr {resolution} {resolution}"
.format(raster_layer=self.gdal_url(src_path), ulx=extent[0], uly=extent[3],
lrx=extent[1],
lry=extent[2],
resolution=kwargs['res'],
tmp_dir=tmp_dir))
# # Second: calculate size of tiles to be created. The reason to do that is
# request small tiles to the WMS service, avoiding some limitation.
# The requested tiles will not be bigger than MAX_DIST
ruled_by = min(extent[1] - extent[0], extent[3] - extent[2])
os.mkdir('{tmp_dir}/tiles'.format(tmp_dir=tmp_dir))
os.system("gdal_retile.py -s_srs \"{s_srs}\" -ps {pixel_size} {pixel_size} "
"-targetDir {tmp_dir}/tiles {tmp_dir}/cropped.vrt"
.format(s_srs=osr_3857.ExportToWkt().replace('"', '\\"'),
pixel_size=int((ruled_by / (ruled_by//MAX_DIST + 1)) / kwargs['res']),
tmp_dir=tmp_dir))
# Make a vrt of tiles to crop all with the desired vector layer
os.system("gdalbuildvrt -tr {resolution} {resolution} "
"{tmp_dir}/merge.vrt {tmp_dir}/tiles/*".format(resolution=kwargs['res'],
tmp_dir=tmp_dir))
# Apply crop
os.system("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=os.path.join(tmp_dir, 'before_reproject.vrt'),
resolution=kwargs['res']))
# Change to EPS:4326
os.system("gdalwarp -q -overwrite -t_srs 'EPSG:4326' "
"-dstalpha {tmp_dir}/before_reproject.vrt {dst} "
"-co BIGTIFF=YES -co TILED=YES "
"--config CHECK_DISK_FREE_SPACE FALSE".format(tmp_dir=tmp_dir, dst=dst))
shutil.rmtree("{}".format(tmp_dir))
return dst
def format_parameters(self, _, parameters):
"""
Method to format the parameters to work with TMS protocol.
Nothing is required,
Parameters
----------
_
parameters: dict
Parameters of the protocol
Returns
-------
dict:
Parameters
"""
return parameters