wms_base.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. import os
  2. from math import ceil
  3. import xml.etree.ElementTree as etree
  4. from urllib2 import urlopen, HTTPError, URLError
  5. import grass.script as grass
  6. class WMSBase:
  7. def __init__(self):
  8. # these variables are information for destructor
  9. self.temp_files_to_cleanup = []
  10. self.cleanup_mask = False
  11. self.cleanup_layers = False
  12. self.bbox = None
  13. self.temp_map = None
  14. def __del__(self):
  15. # removes temporary mask, used for import transparent or warped temp_map
  16. if self.cleanup_mask:
  17. # clear temporary mask, which was set by module
  18. if grass.run_command('r.mask',
  19. quiet = True,
  20. flags = 'r') != 0:
  21. grass.fatal(_('%s failed') % 'r.mask')
  22. # restore original mask, if exists
  23. if grass.find_file(self.o_output + self.original_mask_suffix, element = 'cell', mapset = '.' )['name']:
  24. if grass.run_command('g.copy',
  25. quiet = True,
  26. rast = self.o_output + self.original_mask_suffix + ',MASK') != 0:
  27. grass.fatal(_('%s failed') % 'g.copy')
  28. # tries to remove temporary files, all files should be
  29. # removoved before, implemented just in case of unexpected
  30. # stop of module
  31. for temp_file in self.temp_files_to_cleanup:
  32. grass.try_remove(temp_file)
  33. # remove temporary created rasters
  34. if self.cleanup_layers:
  35. maps = []
  36. for suffix in ('.red', '.green', '.blue', '.alpha', self.original_mask_suffix):
  37. rast = self.o_output + suffix
  38. if grass.find_file(rast, element = 'cell', mapset = '.')['file']:
  39. maps.append(rast)
  40. if maps:
  41. grass.run_command('g.remove',
  42. quiet = True,
  43. flags = 'f',
  44. rast = ','.join(maps))
  45. # deletes enviromental variable which overrides region
  46. if 'GRASS_REGION' in os.environ.keys():
  47. os.environ.pop('GRASS_REGION')
  48. def _debug(self, fn, msg):
  49. grass.debug("%s.%s: %s" %
  50. (self.__class__.__name__, fn, msg))
  51. def _initializeParameters(self, options, flags):
  52. self._debug("_initialize_parameters", "started")
  53. # inicialization of module parameters (options, flags)
  54. self.flags = flags
  55. if self.flags['o']:
  56. self.transparent = 'FALSE'
  57. else:
  58. self.transparent = 'TRUE'
  59. self.o_mapserver_url = options['mapserver'].strip() + "?"
  60. self.o_layers = options['layers'].strip()
  61. self.o_styles = options['styles'].strip()
  62. self.o_output = options['output']
  63. self.o_method = options['method']
  64. self.o_bgcolor = options['bgcolor'].strip()
  65. if self.o_bgcolor != "" and not flags["d"]:
  66. grass.warning(_("Parameter bgcolor ignored, use -d flag"))
  67. self.o_urlparams = options['urlparams'].strip()
  68. if self.o_urlparams != "" and not flags["d"]:
  69. grass.warning(_("Parameter urlparams ignored, use -d flag"))
  70. self.o_wms_version = options['wms_version']
  71. if self.o_wms_version == "1.3.0":
  72. self.projection_name = "CRS"
  73. else:
  74. self.projection_name = "SRS"
  75. self.o_format = options['format']
  76. if self.o_format == "geotiff":
  77. self.mime_format = "image/geotiff"
  78. elif self.o_format == "tiff":
  79. self.mime_format = "image/tiff"
  80. elif self.o_format == "png":
  81. self.mime_format = "image/png"
  82. elif self.o_format == "jpeg":
  83. self.mime_format = "image/jpeg"
  84. if flags['o']:
  85. grass.warning(_("JPEG format does not support transparency"))
  86. elif self.o_format == "gif":
  87. self.mime_format = "image/gif"
  88. else:
  89. self.mime_format = self.o_format
  90. self.o_srs = int(options['srs'])
  91. if self.o_srs <= 0:
  92. grass.fatal(_("Invalid EPSG code %d") % self.o_srs)
  93. # read projection info
  94. self.proj_location = grass.read_command('g.proj',
  95. flags ='jf').rstrip('\n')
  96. self.proj_srs = grass.read_command('g.proj',
  97. flags = 'jf',
  98. epsg = str(self.o_srs) ).rstrip('\n')
  99. if not self.proj_srs or not self.proj_location:
  100. grass.fatal(_("Unable to get projection info"))
  101. # set region
  102. self.o_region = options['region']
  103. if self.o_region:
  104. if not grass.find_file(name = self.o_region, element = 'windows', mapset = '.' )['name']:
  105. grass.fatal(_("Region <%s> not found") % self.o_region)
  106. if self.o_region:
  107. s = grass.read_command('g.region',
  108. quiet = True,
  109. flags = 'ug',
  110. region = self.o_region)
  111. self.region = grass.parse_key_val(s, val_type = float)
  112. else:
  113. self.region = grass.region()
  114. min_tile_size = 100
  115. self.o_maxcols = int(options['maxcols'])
  116. if self.o_maxcols <= min_tile_size:
  117. grass.fatal(_("Maxcols must be greater than 100"))
  118. self.o_maxrows = int(options['maxrows'])
  119. if self.o_maxrows <= min_tile_size:
  120. grass.fatal(_("Maxrows must be greater than 100"))
  121. # setting optimal tile size according to maxcols and maxrows constraint and region cols and rows
  122. self.tile_cols = int(self.region['cols'] / ceil(self.region['cols'] / float(self.o_maxcols)))
  123. self.tile_rows = int(self.region['rows'] / ceil(self.region['rows'] / float(self.o_maxrows)))
  124. # suffix for existing mask (during overriding will be saved
  125. # into raster named:self.o_output + this suffix)
  126. self.original_mask_suffix = "_temp_MASK"
  127. # check names of temporary rasters, which module may create
  128. maps = []
  129. for suffix in ('.red', '.green', '.blue', '.alpha', self.original_mask_suffix ):
  130. rast = self.o_output + suffix
  131. if grass.find_file(rast, element = 'cell', mapset = '.')['file']:
  132. maps.append(rast)
  133. if len(maps) != 0:
  134. grass.fatal(_("Please change output name, or change names of these rasters: %s, "
  135. "module needs to create this temporary maps during runing") % ",".join(maps))
  136. # default format for GDAL library
  137. self.gdal_drv_format = "GTiff"
  138. self._debug("_initialize_parameters", "finished")
  139. def GetMap(self, options, flags):
  140. """!Download data from WMS server and import data
  141. (using GDAL library) into GRASS as a raster map."""
  142. self._initializeParameters(options, flags)
  143. self.bbox = self._computeBbox()
  144. self.temp_map = self._download()
  145. self._createOutputMap()
  146. def GetCapabilities(self, options):
  147. """!Get capabilities from WMS server
  148. """
  149. # download capabilities file
  150. cap_url = options['mapserver'] + "?service=WMS&request=GetCapabilities&version=" + options['wms_version']
  151. try:
  152. cap = urlopen(cap_url)
  153. except IOError:
  154. grass.fatal(_("Unable to get capabilities from '%s'") % options['mapserver'])
  155. cap_lines = cap.readlines()
  156. for line in cap_lines:
  157. print line
  158. def _computeBbox(self):
  159. """!Get region extent for WMS query (bbox)
  160. """
  161. self._debug("_computeBbox", "started")
  162. bbox_region_items = {'maxy' : 'n', 'miny' : 's', 'maxx' : 'e', 'minx' : 'w'}
  163. bbox = {}
  164. if self.proj_srs == self.proj_location: # TODO: do it better
  165. for bbox_item, region_item in bbox_region_items.iteritems():
  166. bbox[bbox_item] = self.region[region_item]
  167. # if location projection and wms query projection are
  168. # different, corner points of region are transformed into wms
  169. # projection and then bbox is created from extreme coordinates
  170. # of the transformed points
  171. else:
  172. for bbox_item, region_item in bbox_region_items.iteritems():
  173. bbox[bbox_item] = None
  174. temp_region = self._tempfile()
  175. try:
  176. temp_region_opened = open(temp_region, 'w')
  177. temp_region_opened.write("%f %f\n%f %f\n%f %f\n%f %f\n" %\
  178. (self.region['e'], self.region['n'],\
  179. self.region['w'], self.region['n'],\
  180. self.region['w'], self.region['s'],\
  181. self.region['e'], self.region['s'] ))
  182. except IOError:
  183. grass.fatal(_("Unable to write data into tempfile"))
  184. finally:
  185. temp_region_opened.close()
  186. points = grass.read_command('m.proj', flags = 'd',
  187. proj_output = self.proj_srs,
  188. proj_input = self.proj_location,
  189. input = temp_region) # TODO: stdin
  190. grass.try_remove(temp_region)
  191. if not points:
  192. grass.fatal(_("Unable to determine region, %s failed") % 'm.proj')
  193. points = points.splitlines()
  194. if len(points) != 4:
  195. grass.fatal(_("Region defintion: 4 points required"))
  196. for point in points:
  197. point = map(float, point.split("|"))
  198. if not bbox['maxy']:
  199. bbox['maxy'] = point[1]
  200. bbox['miny'] = point[1]
  201. bbox['maxx'] = point[0]
  202. bbox['minx'] = point[0]
  203. continue
  204. if bbox['maxy'] < point[1]:
  205. bbox['maxy'] = point[1]
  206. elif bbox['miny'] > point[1]:
  207. bbox['miny'] = point[1]
  208. if bbox['maxx'] < point[0]:
  209. bbox['maxx'] = point[0]
  210. elif bbox['minx'] > point[0]:
  211. bbox['minx'] = point[0]
  212. self._debug("_computeBbox", "finished -> %s" % bbox)
  213. # Ordering of coordinates axis of geographic coordinate
  214. # systems in WMS 1.3.0 is fliped. If self.flip_coords is
  215. # True, coords in bbox need to be flipped in WMS query.
  216. self.flip_coords = False
  217. hasLongLat = self.proj_srs.find("+proj=longlat")
  218. hasLatLong = self.proj_srs.find("+proj=latlong")
  219. if (hasLongLat != -1 or hasLatLong != -1) and self.o_wms_version == "1.3.0":
  220. self.flip_coords = True
  221. return bbox
  222. def _createOutputMap(self):
  223. """!Import downloaded data into GRASS, reproject data if needed
  224. using gdalwarp
  225. """
  226. # reprojection of raster
  227. if self.proj_srs != self.proj_location: # TODO: do it better
  228. grass.message(_("Reprojecting raster..."))
  229. temp_warpmap = self._tempfile()
  230. if int(os.getenv('GRASS_VERBOSE', '2')) <= 2:
  231. nuldev = file(os.devnull, 'w+')
  232. else:
  233. nuldev = None
  234. # RGB rasters - alpha layer is added for cropping edges of projected raster
  235. if self.temp_map_bands_num == 3:
  236. ps = grass.Popen(['gdalwarp',
  237. '-s_srs', '%s' % self.proj_srs,
  238. '-t_srs', '%s' % self.proj_location,
  239. '-r', self.o_method, '-dstalpha',
  240. self.temp_map, temp_warpmap], stdout = nuldev)
  241. # RGBA rasters
  242. else:
  243. ps = grass.Popen(['gdalwarp',
  244. '-s_srs', '%s' % self.proj_srs,
  245. '-t_srs', '%s' % self.proj_location,
  246. '-r', self.o_method,
  247. self.temp_map, temp_warpmap], stdout = nuldev)
  248. ps.wait()
  249. if nuldev:
  250. nuldev.close()
  251. if ps.returncode != 0:
  252. grass.fatal(_('%s failed') % 'gdalwarp')
  253. # raster projection is same as projection of location
  254. else:
  255. temp_warpmap = self.temp_map
  256. grass.message(_("Importing raster map into GRASS..."))
  257. # importing temp_map into GRASS
  258. if grass.run_command('r.in.gdal',
  259. quiet = True,
  260. input = temp_warpmap,
  261. output = self.o_output) != 0:
  262. grass.fatal(_('%s failed') % 'r.in.gdal')
  263. # information for destructor to cleanup temp_layers, created
  264. # with r.in.gdal
  265. self.cleanup_layers = True
  266. # setting region for full extend of imported raster
  267. os.environ['GRASS_REGION'] = grass.region_env(rast = self.o_output + '.red')
  268. # mask created from alpha layer, which describes real extend
  269. # of warped layer (may not be a rectangle), also mask contains
  270. # transparent parts of raster
  271. if grass.find_file( self.o_output + '.alpha', element = 'cell', mapset = '.' )['name']:
  272. # saving current mask (if exists) into temp raster
  273. if grass.find_file('MASK', element = 'cell', mapset = '.' )['name']:
  274. if grass.run_command('g.copy',
  275. quiet = True,
  276. rast = 'MASK,' + self.o_output + self.original_mask_suffix) != 0:
  277. grass.fatal(_('%s failed') % 'g.copy')
  278. # info for destructor
  279. self.cleanup_mask = True
  280. if grass.run_command('r.mask',
  281. quiet = True,
  282. overwrite = True,
  283. maskcats = "0",
  284. flags = 'i',
  285. input = self.o_output + '.alpha') != 0:
  286. grass.fatal(_('%s failed') % 'r.mask')
  287. if grass.run_command('r.composite',
  288. quiet = True,
  289. red = self.o_output + '.red',
  290. green = self.o_output + '.green',
  291. blue = self.o_output + '.blue',
  292. output = self.o_output ) != 0:
  293. grass.fatal(_('%s failed') % 'r.composite')
  294. grass.try_remove(temp_warpmap)
  295. grass.try_remove(self.temp_map)
  296. def _flipBbox(self, bbox):
  297. """
  298. flips items in dictionary
  299. value flips between this keys:
  300. maxy -> maxx
  301. maxx -> maxy
  302. miny -> minx
  303. minx -> miny
  304. @return copy of bbox with fliped cordinates
  305. """
  306. temp_bbox = dict(bbox)
  307. new_bbox = {}
  308. new_bbox['maxy'] = temp_bbox['maxx']
  309. new_bbox['miny'] = temp_bbox['minx']
  310. new_bbox['maxx'] = temp_bbox['maxy']
  311. new_bbox['minx'] = temp_bbox['miny']
  312. return new_bbox
  313. def _tempfile(self):
  314. """!Create temp_file and append list self.temp_files_to_cleanup
  315. with path of file
  316. @return string path to temp_file
  317. """
  318. temp_file = grass.tempfile()
  319. if temp_file is None:
  320. grass.fatal(_("Unable to create temporary files"))
  321. # list of created tempfiles for destructor
  322. self.temp_files_to_cleanup.append(temp_file)
  323. return temp_file