123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817 |
- """!
- @brief Preparation of parameters for drivers, which download it, and managing downloaded data.
- List of classes:
- - wms_base::WMSBase
- - wms_base::GRASSImporter
- - wms_base::WMSDriversInfo
- (C) 2012-2019 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Stepan Turek <stepan.turek seznam.cz> (Mentor: Martin Landa)
- """
- import os
- from math import ceil
- import base64
- try:
- from urllib2 import Request, urlopen, HTTPError
- from httplib import HTTPException
- except ImportError:
- from urllib.request import Request, urlopen
- from urllib.error import HTTPError
- from http.client import HTTPException
- import grass.script as grass
- from grass.exceptions import CalledModuleError
- class WMSBase(object):
- def __init__(self):
- # these variables are information for destructor
- self.temp_files_to_cleanup = []
- self.params = {}
- self.tile_size = {"bbox": None}
- self.temp_map = None
- self.temp_warpmap = None
- def __del__(self):
- # tries to remove temporary files, all files should be
- # removed before, implemented just in case of unexpected
- # stop of module
- for temp_file in self.temp_files_to_cleanup:
- grass.try_remove(temp_file)
- def _debug(self, fn, msg):
- grass.debug("%s.%s: %s" % (self.__class__.__name__, fn, msg))
- def _initializeParameters(self, options, flags):
- self._debug("_initialize_parameters", "started")
- # initialization of module parameters (options, flags)
- self.params["driver"] = options["driver"]
- drv_info = WMSDriversInfo()
- driver_props = drv_info.GetDrvProperties(options["driver"])
- self._checkIgnoeredParams(options, flags, driver_props)
- self.params["capfile"] = options["capfile"].strip()
- for key in ["url", "layers", "styles", "method"]:
- self.params[key] = options[key].strip()
- self.flags = flags
- if self.flags["o"]:
- self.params["transparent"] = "FALSE"
- else:
- self.params["transparent"] = "TRUE"
- for key in ["password", "username", "urlparams"]:
- self.params[key] = options[key]
- if (self.params["password"] and self.params["username"] == "") or (
- self.params["password"] == "" and self.params["username"]
- ):
- grass.fatal(
- _(
- "Please insert both %s and %s parameters or none of them."
- % ("password", "username")
- )
- )
- self.params["bgcolor"] = options["bgcolor"].strip()
- if (
- options["format"] == "jpeg"
- and "format" not in driver_props["ignored_params"]
- ):
- if not flags["o"] and "WMS" in self.params["driver"]:
- grass.warning(_("JPEG format does not support transparency"))
- self.params["format"] = drv_info.GetFormat(options["format"])
- if not self.params["format"]:
- self.params["format"] = self.params["format"]
- # TODO: get srs from Tile Service file in OnEarth_GRASS driver
- self.params["srs"] = int(options["srs"])
- if self.params["srs"] <= 0 and "srs" not in driver_props["ignored_params"]:
- grass.fatal(_("Invalid EPSG code %d") % self.params["srs"])
- self.params["wms_version"] = options["wms_version"]
- if (
- "CRS" in GetSRSParamVal(self.params["srs"])
- and self.params["wms_version"] == "1.1.1"
- ):
- self.params["wms_version"] = "1.3.0"
- grass.warning(
- _(
- "WMS version <1.3.0> will be used, because version <1.1.1> does not support <%s>projection"
- )
- % GetSRSParamVal(self.params["srs"])
- )
- if self.params["wms_version"] == "1.3.0":
- self.params["proj_name"] = "CRS"
- else:
- self.params["proj_name"] = "SRS"
- # read projection info
- self.proj_location = grass.read_command("g.proj", flags="jf").rstrip("\n")
- self.proj_location = self._modifyProj(self.proj_location)
- self.source_epsg = str(GetEpsg(self.params["srs"]))
- self.target_epsg = None
- target_crs = grass.parse_command("g.proj", flags="g", delimiter="=")
- if "epsg" in target_crs.keys():
- self.target_epsg = target_crs["epsg"]
- if self.source_epsg != self.target_epsg:
- grass.warning(
- _(
- "SRS differences: WMS source EPSG %s != location EPSG %s (use srs=%s to adjust)"
- )
- % (self.source_epsg, self.target_epsg, self.target_epsg)
- )
- self.proj_srs = grass.read_command(
- "g.proj", flags="jf", epsg=str(GetEpsg(self.params["srs"]))
- )
- self.proj_srs = self.proj_srs.rstrip("\n")
- self.proj_srs = self._modifyProj(self.proj_srs)
- if not self.proj_srs or not self.proj_location:
- grass.fatal(_("Unable to get projection info"))
- self.region = options["region"]
- min_tile_size = 100
- maxcols = int(options["maxcols"])
- if maxcols <= min_tile_size:
- grass.fatal(_("Maxcols must be greater than 100"))
- maxrows = int(options["maxrows"])
- if maxrows <= min_tile_size:
- grass.fatal(_("Maxrows must be greater than 100"))
- # setting optimal tile size according to maxcols and maxrows constraint
- # and region cols and rows
- self.tile_size["cols"] = int(
- self.region["cols"] / ceil(self.region["cols"] / float(maxcols))
- )
- self.tile_size["rows"] = int(
- self.region["rows"] / ceil(self.region["rows"] / float(maxrows))
- )
- # default format for GDAL library
- self.gdal_drv_format = "GTiff"
- self._debug("_initialize_parameters", "finished")
- def _modifyProj(self, proj):
- """!Modify proj.4 string for usage in this module"""
- # add +wktext parameter to avoid droping of +nadgrids parameter (if presented) in gdalwarp
- if "+nadgrids=" in proj and " +wktext" not in proj:
- proj += " +wktext"
- return proj
- def _checkIgnoeredParams(self, options, flags, driver_props):
- """!Write warnings for set parameters and flags, which chosen driver does not use."""
- not_relevant_params = []
- for i_param in driver_props["ignored_params"]:
- if (
- i_param in options
- and options[i_param]
- and i_param not in ["srs", "wms_version", "format"]
- ): # params with default value
- not_relevant_params.append("<" + i_param + ">")
- if len(not_relevant_params) > 0:
- grass.warning(
- _(
- "These parameter are ignored: %s\n\
- %s driver does not support the parameters."
- % (",".join(not_relevant_params), options["driver"])
- )
- )
- not_relevant_flags = []
- for i_flag in driver_props["ignored_flags"]:
- if flags[i_flag]:
- not_relevant_flags.append("<" + i_flag + ">")
- if len(not_relevant_flags) > 0:
- grass.warning(
- _(
- "These flags are ignored: %s\n\
- %s driver does not support the flags."
- % (",".join(not_relevant_flags), options["driver"])
- )
- )
- def GetMap(self, options, flags):
- """!Download data from WMS server."""
- self._initializeParameters(options, flags)
- self.bbox = self._computeBbox()
- self.temp_map = self._download()
- if not self.temp_map:
- return
- self._reprojectMap()
- return self.temp_warpmap
- def _fetchCapabilities(self, options):
- """!Download capabilities from WMS server"""
- cap_url = options["url"].strip()
- if "?" in cap_url:
- cap_url += "&"
- else:
- cap_url += "?"
- if "WMTS" in options["driver"]:
- cap_url += "SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=1.0.0"
- elif "OnEarth" in options["driver"]:
- cap_url += "REQUEST=GetTileService"
- else:
- cap_url += (
- "SERVICE=WMS&REQUEST=GetCapabilities&VERSION=" + options["wms_version"]
- )
- if options["urlparams"]:
- cap_url += "&" + options["urlparams"]
- grass.debug("Fetching capabilities file.\n%s" % cap_url)
- try:
- cap = self._fetchDataFromServer(
- cap_url, options["username"], options["password"]
- )
- except (IOError, HTTPException) as e:
- if isinstance(e, HTTPError) and e.code == 401:
- grass.fatal(
- _("Authorization failed to <%s> when fetching capabilities")
- % options["url"]
- )
- else:
- msg = _("Unable to fetch capabilities from <{0}>. Reason: ").format(
- options["url"]
- )
- if hasattr(e, "reason"):
- msg += "{0}".format(e.reason)
- else:
- msg += "{0}".format(e)
- grass.fatal(msg)
- grass.debug("Fetching capabilities OK")
- return grass.decode(cap.read())
- def _fetchDataFromServer(self, url, username=None, password=None):
- """!Fetch data from server"""
- request = Request(url)
- if username and password:
- base64string = base64.encodestring("%s:%s" % (username, password)).replace(
- "\n", ""
- )
- request.add_header("Authorization", "Basic %s" % base64string)
- try:
- return urlopen(request)
- except ValueError as error:
- grass.fatal("%s" % error)
- def GetCapabilities(self, options):
- """!Get capabilities from WMS server"""
- cap = self._fetchCapabilities(options)
- capfile_output = options["capfile_output"].strip()
- # save to file
- if capfile_output:
- try:
- with open(capfile_output, "w") as temp:
- temp.write(cap)
- return
- except IOError as error:
- grass.fatal(
- _("Unable to open file '%s'.\n%s\n" % (capfile_output, error))
- )
- # print to output
- print(cap)
- def _computeBbox(self):
- """!Get region extent for WMS query (bbox)"""
- self._debug("_computeBbox", "started")
- bbox_region_items = {"maxy": "n", "miny": "s", "maxx": "e", "minx": "w"}
- bbox = {}
- if self.proj_srs == self.proj_location: # TODO: do it better
- for bbox_item, region_item in bbox_region_items.items():
- bbox[bbox_item] = self.region[region_item]
- # if location projection and wms query projection are
- # different, corner points of region are transformed into wms
- # projection and then bbox is created from extreme coordinates
- # of the transformed points
- else:
- for bbox_item, region_item in bbox_region_items.items():
- bbox[bbox_item] = None
- temp_region = self._tempfile()
- try:
- temp_region_opened = open(temp_region, "w")
- temp_region_opened.write(
- "%f %f\n%f %f\n%f %f\n%f %f\n"
- % (
- self.region["e"],
- self.region["n"],
- self.region["w"],
- self.region["n"],
- self.region["w"],
- self.region["s"],
- self.region["e"],
- self.region["s"],
- )
- )
- except IOError:
- grass.fatal(_("Unable to write data into tempfile"))
- finally:
- temp_region_opened.close()
- points = grass.read_command(
- "m.proj",
- flags="d",
- proj_out=self.proj_srs,
- proj_in=self.proj_location,
- input=temp_region,
- quiet=True,
- ) # TODO: stdin
- grass.try_remove(temp_region)
- if not points:
- grass.fatal(_("Unable to determine region, %s failed") % "m.proj")
- points = points.splitlines()
- if len(points) != 4:
- grass.fatal(_("Region definition: 4 points required"))
- for point in points:
- try:
- point = list(map(float, point.split("|")))
- except ValueError:
- grass.fatal(_("Reprojection of region using m.proj failed."))
- if not bbox["maxy"]:
- bbox["maxy"] = point[1]
- bbox["miny"] = point[1]
- bbox["maxx"] = point[0]
- bbox["minx"] = point[0]
- continue
- if bbox["maxy"] < point[1]:
- bbox["maxy"] = point[1]
- elif bbox["miny"] > point[1]:
- bbox["miny"] = point[1]
- if bbox["maxx"] < point[0]:
- bbox["maxx"] = point[0]
- elif bbox["minx"] > point[0]:
- bbox["minx"] = point[0]
- self._debug("_computeBbox", "finished -> %s" % bbox)
- # Ordering of coordinates axis of geographic coordinate
- # systems in WMS 1.3.0 is flipped. If self.tile_size['flip_coords'] is
- # True, coords in bbox need to be flipped in WMS query.
- return bbox
- def _reprojectMap(self):
- """!Reproject data using gdalwarp if needed"""
- # reprojection of raster
- do_reproject = True
- if (
- self.source_epsg is not None
- and self.target_epsg is not None
- and self.source_epsg == self.target_epsg
- ):
- do_reproject = False
- # TODO: correctly compare source and target crs
- if do_reproject and self.proj_srs == self.proj_location:
- do_reproject = False
- if do_reproject:
- grass.message(_("Reprojecting raster..."))
- self.temp_warpmap = grass.tempfile() + ".tif"
- if int(os.getenv("GRASS_VERBOSE", "2")) <= 2:
- nuldev = open(os.devnull, "w+")
- else:
- nuldev = None
- if self.params["method"] == "nearest":
- gdal_method = "near"
- elif self.params["method"] == "linear":
- gdal_method = "bilinear"
- else:
- gdal_method = self.params["method"]
- # RGB rasters - alpha layer is added for cropping edges of projected raster
- try:
- if self.temp_map_bands_num == 3:
- ps = grass.Popen(
- [
- "gdalwarp",
- "-s_srs",
- "%s" % self.proj_srs,
- "-t_srs",
- "%s" % self.proj_location,
- "-r",
- gdal_method,
- "-dstalpha",
- self.temp_map,
- self.temp_warpmap,
- ],
- stdout=nuldev,
- )
- # RGBA rasters
- else:
- ps = grass.Popen(
- [
- "gdalwarp",
- "-s_srs",
- "%s" % self.proj_srs,
- "-t_srs",
- "%s" % self.proj_location,
- "-r",
- gdal_method,
- self.temp_map,
- self.temp_warpmap,
- ],
- stdout=nuldev,
- )
- ps.wait()
- except OSError as e:
- grass.fatal(
- "%s \nThis can be caused by missing %s utility. " % (e, "gdalwarp")
- )
- if nuldev:
- nuldev.close()
- if ps.returncode != 0:
- grass.fatal(_("%s failed") % "gdalwarp")
- grass.try_remove(self.temp_map)
- # raster projection is same as projection of location
- else:
- self.temp_warpmap = self.temp_map
- self.temp_files_to_cleanup.remove(self.temp_map)
- return self.temp_warpmap
- def _tempfile(self):
- """!Create temp_file and append list self.temp_files_to_cleanup
- with path of file
- @return string path to temp_file
- """
- temp_file = grass.tempfile()
- if temp_file is None:
- grass.fatal(_("Unable to create temporary files"))
- # list of created tempfiles for destructor
- self.temp_files_to_cleanup.append(temp_file)
- return temp_file
- class GRASSImporter:
- def __init__(self, opt_output, cleanup_bands):
- self.cleanup_mask = False
- self.cleanup_bands = cleanup_bands
- # output map name
- self.opt_output = opt_output
- # suffix for existing mask (during overriding will be saved
- # into raster named:self.opt_output + this suffix)
- self.original_mask_suffix = "_temp_MASK"
- # check names of temporary rasters, which module may create
- maps = []
- for suffix in (".red", ".green", ".blue", ".alpha", self.original_mask_suffix):
- rast = self.opt_output + suffix
- if grass.find_file(rast, element="cell", mapset=".")["file"]:
- maps.append(rast)
- if len(maps) != 0:
- grass.fatal(
- _(
- "Please change output name, or change names of these rasters: %s, "
- "module needs to create this temporary maps during execution."
- )
- % ",".join(maps)
- )
- def __del__(self):
- # removes temporary mask, used for import transparent or warped temp_map
- if self.cleanup_mask:
- # clear temporary mask, which was set by module
- try:
- grass.run_command("r.mask", quiet=True, flags="r")
- except CalledModuleError:
- grass.fatal(_("%s failed") % "r.mask")
- # restore original mask, if exists
- if grass.find_file(
- self.opt_output + self.original_mask_suffix, element="cell", mapset="."
- )["name"]:
- try:
- mask_copy = self.opt_output + self.original_mask_suffix
- grass.run_command("g.copy", quiet=True, raster=mask_copy + ",MASK")
- except CalledModuleError:
- grass.fatal(_("%s failed") % "g.copy")
- # remove temporary created rasters
- maps = []
- rast = self.opt_output + ".alpha"
- if grass.find_file(rast, element="cell", mapset=".")["file"]:
- maps.append(rast)
- if self.cleanup_bands:
- for suffix in (".red", ".green", ".blue", self.original_mask_suffix):
- rast = self.opt_output + suffix
- if grass.find_file(rast, element="cell", mapset=".")["file"]:
- maps.append(rast)
- if maps:
- grass.run_command(
- "g.remove", quiet=True, flags="fb", type="raster", name=",".join(maps)
- )
- # delete environmental variable which overrides region
- if "GRASS_REGION" in os.environ.keys():
- os.environ.pop("GRASS_REGION")
- maplist = grass.read_command(
- "g.list",
- type="raster",
- pattern="%s*" % (self.opt_output),
- mapset=".",
- separator=",",
- ).rstrip("\n")
- if len(maplist) == 0:
- grass.fatal(_("WMS import failed, nothing imported"))
- else:
- for raster in maplist.split(","):
- grass.raster_history(raster, overwrite=True)
- grass.run_command(
- "r.support", map=raster, description="generated by r.in.wms"
- )
- grass.message(_("<%s> created.") % raster)
- def ImportMapIntoGRASS(self, raster):
- """!Import raster into GRASS."""
- # importing temp_map into GRASS
- try:
- # do not use -o flag !
- grass.run_command(
- "r.in.gdal",
- flags="o",
- quiet=True,
- overwrite=True,
- input=raster,
- output=self.opt_output,
- )
- except CalledModuleError:
- grass.fatal(_("%s failed") % "r.in.gdal")
- # information for destructor to cleanup temp_layers, created
- # with r.in.gdal
- # setting region for full extend of imported raster
- if grass.find_file(self.opt_output + ".red", element="cell", mapset=".")[
- "file"
- ]:
- region_map = self.opt_output + ".red"
- else:
- region_map = self.opt_output
- os.environ["GRASS_REGION"] = grass.region_env(rast=region_map)
- # mask created from alpha layer, which describes real extend
- # of warped layer (may not be a rectangle), also mask contains
- # transparent parts of raster
- if grass.find_file(self.opt_output + ".alpha", element="cell", mapset=".")[
- "name"
- ]:
- # saving current mask (if exists) into temp raster
- if grass.find_file("MASK", element="cell", mapset=".")["name"]:
- try:
- mask_copy = self.opt_output + self.original_mask_suffix
- grass.run_command("g.copy", quiet=True, raster="MASK," + mask_copy)
- except CalledModuleError:
- grass.fatal(_("%s failed") % "g.copy")
- # info for destructor
- self.cleanup_mask = True
- try:
- grass.run_command(
- "r.mask",
- quiet=True,
- overwrite=True,
- maskcats="0",
- flags="i",
- raster=self.opt_output + ".alpha",
- )
- except CalledModuleError:
- grass.fatal(_("%s failed") % "r.mask")
- if not self.cleanup_bands:
- # use the MASK to set NULL vlues
- for suffix in (".red", ".green", ".blue"):
- rast = self.opt_output + suffix
- if grass.find_file(rast, element="cell", mapset=".")["file"]:
- grass.run_command(
- "g.rename",
- rast="%s,%s" % (rast, rast + "_null"),
- quiet=True,
- )
- grass.run_command(
- "r.mapcalc",
- expression="%s = %s" % (rast, rast + "_null"),
- quiet=True,
- )
- grass.run_command(
- "g.remove",
- type="raster",
- name="%s" % (rast + "_null"),
- flags="f",
- quiet=True,
- )
- # TODO one band + alpha band?
- if (
- grass.find_file(self.opt_output + ".red", element="cell", mapset=".")[
- "file"
- ]
- and self.cleanup_bands
- ):
- try:
- grass.run_command(
- "r.composite",
- quiet=True,
- overwrite=True,
- red=self.opt_output + ".red",
- green=self.opt_output + ".green",
- blue=self.opt_output + ".blue",
- output=self.opt_output,
- )
- except CalledModuleError:
- grass.fatal(_("%s failed") % "r.composite")
- class WMSDriversInfo:
- def __init__(self):
- """!Provides information about driver parameters."""
- # format labels
- self.f_labels = ["geotiff", "tiff", "png", "jpeg", "gif", "png8"]
- # form for request
- self.formats = [
- "image/geotiff",
- "image/tiff",
- "image/png",
- "image/jpeg",
- "image/gif",
- "image/png8",
- ]
- self.srs = ("epsg", "ogc")
- def GetDrvProperties(self, driver):
- """!Get information about driver parameters."""
- if driver == "WMS_GDAL":
- return self._GDALDrvProperties()
- if "WMS" in driver:
- return self._WMSProperties()
- if "WMTS" in driver:
- return self._WMTSProperties()
- if "OnEarth" in driver:
- return self._OnEarthProperties()
- def _OnEarthProperties(self):
- props = {}
- props["ignored_flags"] = ["o"]
- props["ignored_params"] = [
- "bgcolor",
- "styles",
- "capfile_output",
- "format",
- "srs",
- "wms_version",
- ]
- props["req_multiple_layers"] = False
- return props
- def _WMSProperties(self):
- props = {}
- props["ignored_params"] = ["capfile"]
- props["ignored_flags"] = []
- props["req_multiple_layers"] = True
- return props
- def _WMTSProperties(self):
- props = {}
- props["ignored_flags"] = ["o"]
- props["ignored_params"] = ["urlparams", "bgcolor", "wms_version"]
- props["req_multiple_layers"] = False
- return props
- def _GDALDrvProperties(self):
- props = {}
- props["ignored_flags"] = []
- props["ignored_params"] = [
- "urlparams",
- "bgcolor",
- "capfile",
- "capfile_output",
- "username",
- "password",
- ]
- props["req_multiple_layers"] = True
- return props
- def GetFormatLabel(self, format):
- """!Convert format request form to value in parameter 'format'."""
- if format in self.formats:
- return self.f_labels[self.formats.index(format)]
- return None
- def GetFormat(self, label):
- """!Convert value in parameter 'format' to format request form."""
- if label in self.f_labels:
- return self.formats[self.f_labels.index(label)]
- return None
- def GetSrs(self):
- """!Get supported srs prefixes (e.g. epsg/crs)
- @todo filter according to version and driver params
- """
- return self.srs
- # TODO move to utils?
- def GetSRSParamVal(srs):
- """!Decides whether to use CRS or EPSG prefix according to srs number."""
- if srs in [84, 83, 27]:
- return "OGC:CRS{}".format(srs)
- else:
- return "EPSG:{}".format(srs)
- def GetEpsg(srs):
- """
- @return EPSG number
- If srs is CRS number, return EPSG number which corresponds to CRS number.
- """
- if srs == 84:
- return 4326
- if srs == 83:
- return 4269
- if srs == 27:
- return 4267
- return srs
|