Skip to content

Commit

Permalink
release version 0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
eox-cs1 committed May 10, 2017
1 parent 5a517b7 commit 7378165
Show file tree
Hide file tree
Showing 19 changed files with 4,375 additions and 3,634 deletions.
397 changes: 0 additions & 397 deletions EOxWCSClient/cmdline_wcs_client.py

This file was deleted.

114 changes: 61 additions & 53 deletions EOxWCSClient/wcs_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@
global crs_url
crs_url = 'http://www.opengis.net/def/crs/EPSG/0/'

global namespacemap
namespacemap = {"wcs": "http://www.opengis.net/wcs/2.0", "wcseo": "http://www.opengis.net/wcseo/1.0", "crs": "http://www.opengis.net/wcs/service-extension/crs/1.0", "gml" : "http://www.opengis.net/gml/3.2", "gmlcov" : "http://www.opengis.net/gmlcov/1.0", "ogc" : "http://www.opengis.net/ogc", "ows" : "http://www.opengis.net/ows/2.0", "swe" : "http://www.opengis.net/swe/2.0", "int" : "http://www.opengis.net/WCS_service-extension_interpolation/1.0", "eop" : "http://www.opengis.net/eop/2.0", "om" : "http://www.opengis.net/om/2.0"}


# sets a storage location in case the user doesn't provide one (to be on the save side) - eg. for error msgs.
global temp_storage
Expand Down Expand Up @@ -294,14 +291,17 @@ def _set_base_desceocoverageset(self):
'request': '&request=',
'server_url': '',
'eoID': '&eoID=',
'subset_lon': '&subset=Long,'+crs_url+'4326(',
'subset_lat': '&subset=Lat,'+crs_url+'4326(',
# 'subset_lon': '&subset=Long,'+crs_url+'4326(', #@@
# 'subset_lat': '&subset=Lat,'+crs_url+'4326(', #@@
'subset_lon': '&subset=Long(',
'subset_lat': '&subset=Lat(',
'subset_time': '&subset=phenomenonTime(%22',
'containment': '&containment=',
'section': '&section=',
'count': '&count=',
'IDs_only': False}

#print "base_desceocoverageset", base_desceocoverageset
return base_desceocoverageset


Expand All @@ -323,7 +323,8 @@ def _set_base_getcov(self):
'subset_y': '&subset=',
'rangesubset': '&rangesubset=',
'outputcrs': '&outputcrs='+crs_url,
'interpolation': '&interpolation=',
'interpolation': '&interpolation=', #@@
# 'interpolation': '&interpolation=nearest-neighbour', #@@
'mediatype': '&mediatype=',
'mask': '&mask=polygon,'+crs_url,
'size_x': '&',
Expand Down Expand Up @@ -463,7 +464,7 @@ def DescribeEOCoverageSet(self, input_params):
#/* GetCoverage() */
#/************************************************************************/

def GetCoverage(self, input_params):
def GetCoverage(self, input_params, use_wcs_GCo_call):
"""
Creates a GetCoverage request url based on the input_parameters
and executes the request.
Expand Down Expand Up @@ -513,27 +514,29 @@ def GetCoverage(self, input_params):

# provide the same functionality for input as for the cmd-line
# (to get around the url-notation for input)
if input_params.has_key('subset_x') and input_params['subset_x'].startswith('epsg'):
if input_params.has_key('subset_x') and input_params['subset_x'] is not None and input_params['subset_x'].startswith('epsg') :
crs = input_params['subset_x'].split(':')[1].split(' ')[0]
label = input_params['subset_x'].split(':')[1].split(' ')[1]
coord = input_params['subset_x'].split(':')[1].split(' ')[2]
out = label+','+crs_url+crs+'('+coord
# out = label+','+crs_url+crs+'('+coord #@@
out = label+'('+coord
input_params['subset_x'] = out
elif input_params.has_key('subset_x') and (input_params['subset_x'].startswith('pix') or input_params['subset_x'].startswith('ori')):
elif input_params.has_key('subset_x') and input_params['subset_x'] is not None and (input_params['subset_x'].startswith('pix') or input_params['subset_x'].startswith('ori')):
label = input_params['subset_x'].split(' ')[1]
coord = input_params['subset_x'].split(' ')[2]
out = label+'('+coord
input_params['subset_x'] = out
else:
pass

if input_params.has_key('subset_y') and input_params['subset_y'].startswith('epsg'):
if input_params.has_key('subset_y') and input_params['subset_y'] is not None and input_params['subset_y'].startswith('epsg'):
crs = input_params['subset_y'].split(':')[1].split(' ')[0]
label = input_params['subset_y'].split(':')[1].split(' ')[1]
coord = input_params['subset_y'].split(':')[1].split(' ')[2]
out = label+','+crs_url+crs+'('+coord
# out = label+','+crs_url+crs+'('+coord #@@
out = label+'('+coord
input_params['subset_y'] = out
elif input_params.has_key('subset_y') and (input_params['subset_y'].startswith('pix') or input_params['subset_y'].startswith('ori')):
elif input_params.has_key('subset_y') and input_params['subset_y'] is not None and (input_params['subset_y'].startswith('pix') or input_params['subset_y'].startswith('ori')):
label = input_params['subset_y'].split(' ')[1]
coord = input_params['subset_y'].split(' ')[2]
out = label+'('+coord
Expand All @@ -542,15 +545,15 @@ def GetCoverage(self, input_params):
pass


if input_params.has_key('size_x'):
if input_params.has_key('size_x') and input_params['size_x'] is not None:
if input_params['size_x'].startswith('siz'):
out = "size="+input_params['size_x'].split(" ")[1]+"("+input_params['size_x'].split(" ")[2]
input_params['size_x'] = out
elif input_params['size_x'].startswith('res'):
out = "resolution="+input_params['size_x'].split(" ")[1]+"("+input_params['size_x'].split(" ")[2]
input_params['size_x'] = out

if input_params.has_key('size_y'):
if input_params.has_key('size_y') and input_params['size_y'] is not None:
if input_params['size_y'].startswith('siz'):
out = "size="+input_params['size_y'].split(" ")[1]+"("+input_params['size_y'].split(" ")[2]
input_params['size_y'] = out
Expand Down Expand Up @@ -579,17 +582,17 @@ def _parse_xml(self, in_xml):
"""
join_xml = ''.join(in_xml)
tree = etree.fromstring(join_xml)
tag_ids = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/wcs:CoverageId/text()", namespaces=namespacemap)
#print tag_ids

## TODO - map each axis and crs to the respective coverage, abd show them accordingly to the selection

# also read out the gml:Envelope axisLabels and srsName - use only first returned entry
axis_labels = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:Envelope/@axisLabels|wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:EnvelopeWithTimePeriod/@axisLabels", namespaces=namespacemap)
try:
tag_ids = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/wcs:CoverageId/text()", namespaces=tree.nsmap)
#print tag_ids
except etree.XPathEvalError:
raise IndexError

axis_labels = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:Envelope/@axisLabels|wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:EnvelopeWithTimePeriod/@axisLabels", namespaces=tree.nsmap)
#print 'AxisLabels: ', type(axis_labels),len(axis_labels), axis_labels
axis_labels = axis_labels[0].encode().split(" ")
#print axis_labels
offered_crs = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:Envelope/@srsName|wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:EnvelopeWithTimePeriod/@srsName", namespaces=namespacemap)
offered_crs = tree.xpath("wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:Envelope/@srsName|wcs:CoverageDescriptions/wcs:CoverageDescription/gml:boundedBy/gml:EnvelopeWithTimePeriod/@srsName", namespaces=tree.nsmap)
offered_crs = os.path.basename(offered_crs[0])
#print offered_crs
if len(axis_labels) == 0:
Expand All @@ -611,34 +614,40 @@ def _execute_xml_request(self, http_request, IDs_only=False):
Output: prints out the submitted http_request or Error_XML in case of failure
"""
# print "I'm in "+sys._getframe().f_code.co_name
print http_request
print 'REQUEST: ',http_request #@@

try:
# access the url
request_handle = urllib2.urlopen(http_request)
# read the content of the url
result_xml = request_handle.read()
# create a request object,
request_handle = urllib2.Request(http_request, headers={'User-Agent': 'Python-urllib/2.7,QgsWcsClient-plugin'})
response = urllib2.urlopen(request_handle)
xml_result = response.read()
status = response.code
#headers = response.headers.dict
#print 'HEADERS ', headers
#print 'XML-ResponseStatus: ', status


## TODO ## to change the User-agent header --> change the above to the following
# create a request object, this construct doesn't need the close-statements (-> line 633 / 633) anymore
#request_handle = urllib2.Request(http_request, headers={'User-Agent': 'Python-urllib/2.6,QgsWcsClient-plugin'})
#result_xml = urllib2.urlopen(request_handle).read()

# extract only the CoverageIDs and provide them as a list for further usage
if IDs_only == True:
cids, axis_labels, offered_crs = self._parse_xml(result_xml)
request_handle.close()
return cids, axis_labels, offered_crs
try:
cids, axis_labels, offered_crs = self._parse_xml(xml_result)
# request_handle.close()
return cids, axis_labels, offered_crs
except IndexError:
raise IndexError
else:
request_handle.close()
return result_xml
return xml_result

except urllib2.URLError, url_ERROR:
if hasattr(url_ERROR, 'reason'):
print '\n', time.strftime("%Y-%m-%dT%H:%M:%S%Z"), "- ERROR: Server not accessible -", url_ERROR.reason
print '\n', time.strftime("%Y-%m-%dT%H:%M:%S%Z"), "- ERROR: Server not accessible -" , url_ERROR.reason
err_msg=['ERROR', url_ERROR.read()]
return err_msg

try:
print url_ERROR.read(), '\n'

except:
pass

Expand Down Expand Up @@ -669,7 +678,7 @@ def _execute_getcov_request(self, http_request, input_params):
Returns: HttpCode (if success)
"""
# print "I'm in "+sys._getframe().f_code.co_name
print http_request
print 'REQUEST:', http_request

now = time.strftime('_%Y%m%dT%H%M%S')

Expand All @@ -689,35 +698,34 @@ def _execute_getcov_request(self, http_request, input_params):
else:
out_format_ext = input_params['format']

#if not (input_params['coverageID'].endswith('tif') or input_params['coverageID'].endswith('tiff') or \
#input_params['coverageID'].endswith('jpeg') or input_params['coverageID'].endswith('jpg') or \
#input_params['coverageID'].endswith('gif')):
#out_coverageID = input_params['coverageID']+now+'.'+out_format_ext # input_params['format']

#else:
#out_coverageID = input_params['coverageID']
#@@
out_coverageID = input_params['coverageID']+now+'.'+out_format_ext # input_params['format']

if input_params.has_key('output') and input_params['output'] is not None:
outfile = input_params['output']+dsep+out_coverageID
else:
outfile = temp_storage+dsep+out_coverageID

print 'REQUEST-GetCov: ',http_request #@@

try:
request_handle = urllib2.urlopen(http_request)
status = request_handle.code
request_handle = urllib2.Request(http_request, headers={'User-Agent': 'Python-urllib/2.7,QgsWcsClient-plugin'})
response = urllib2.urlopen(request_handle)
result = response.read()
status = response.code
#headers = response.headers.dict
#print 'HEADERS ', headers
#print 'GCov-Status: ', status

try:
file_getcov = open(outfile, 'w+b')
file_getcov.write(request_handle.read())
file_getcov.write(result)
file_getcov.flush()
os.fsync(file_getcov.fileno())
file_getcov.close()
request_handle.close()
return status


except IOError as (errno, strerror):
print "I/O error({0}): {1}".format(errno, strerror)
except:
Expand Down Expand Up @@ -756,7 +764,7 @@ def _merge_dicts(self, input_params, procedure_dict):

request_dict = {}
for k, v in input_params.iteritems():
#print k,' -- ',v
#print 'TTTT: ', k,' -- ',v
# make sure there is always a version set
if k == 'version' and (v == '' or v == None):
v = '2.0.0'
Expand All @@ -768,11 +776,10 @@ def _merge_dicts(self, input_params, procedure_dict):
# (which got inserted for argparse() to handle negativ input values correctly)
request_dict[k] = str(procedure_dict[k])+str(v).strip()


# get the basic request settings
base_request = self._set_base_request()
request_dict.update(base_request)
#print 'request_dict ',request_dict

return request_dict


Expand All @@ -787,6 +794,7 @@ def _create_request(self, input_params, procedure_dict):
# print "I'm in "+sys._getframe().f_code.co_name

request_dict = self._merge_dicts(input_params, procedure_dict)
#print 'Request_Dict: ', request_dict #@@

# this doesn't look so nice, but this way I can control the order within the generated request
http_request = ''
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# A OGC WCS 2.0/EO-WCS Client
# -------------------
# begin : 2014-06-26
# begin : 2014-06-26; 2017-04-10
# copyright : (C) 2014 by Christian Schiller / EOX IT Services GmbH, Vienna, Austria
# email : [email protected]
# ***************************************************************************/
Expand Down
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,45 @@ QgsWcsClient2

A QGis WCS2.0/EO-WCS Plugin

A tool to download (and subset in space and time) a series of raster data
A tool to download (and subset in space and time) a time-series of raster data and get the data in your desired file-format and projection.

The WCS 2.0/EO-WCS Client allows to specify an Area-Of-Interest and a Time-Of-Interest and then access/download the raster data (coverages) from OGC WCS-2.0 compliant servers.
The WCS 2.0/EO-WCS Client allows to specify an Area-Of-Interest and a Time-Of-Interest and then access/download the raster data (coverages) from OGC WCS-2.0 and WCS-2.0/EO-WCS compliant servers.
Unlike WMS, WCS enables the access to the original data (and not just to portrayals).
The tool also handles the EO-WCS Application profile which allows to access/download a full time-series of coverages with just a few clicks. For multi-band EO-imagery the bands of interest can also be selected/sub-setted and their order in the output can be chosen.
The downloaded coverages are directly loaded as layers into QGIS
The downloaded coverages are directly loaded as layers into QGIS.

Requirements: This tool requires the python lxml-module to be pre-installed.
[ Help for installation in Windows (thankfully provided by hkristen) can by found at: https://github.com/EOX-A/QgsWcsClient2/issues/8 ]

It would be more than welcome to receive accessible EO-WCS server-urls for additional testing.


Added Features & Fixed Bugs:
2017-05-03 - verison 0.3:
- added Button to import WCS-Urls already stored in native QGis settings
- added Button to sort the Server_List
- enabled resizing of QgsWcsClient2-Client Window
- added "WGS84 UpperCorner & WGS84LowerCorner" (BoundingBox) fields to GetCapabilities and DescribeEOCoverageSet Results-View
- added a uniq "Browser tag" to be submitted with the requests, to verify in the access log-files that the Qgis-plugin was used
(Identifies now with User-Agent => 'Python-urllib/2.7,QgsWcsClient-plugin')
- enabled support to access https:// sites
- config_server.pkl file (containing the server entries) will not get overwritten during update/re-installatinon anymore
(a default one will only be installed if it is not available at plugin startup)
- added possibility to view full GetCapabilities XML response-document (also made more clear to view DescribeEOCoverageSet XML)
(now all are accessible => GetCap, DescCov, DescEOCov: just use copy/paste to save them)
- better error checking e.g. for http errors -> warning messages (e.g. for redirects, automatic redirection is not supported to
minimize security issues)

- fixed xml_parsing error
- fixed issue with "offered CRS"
- fixed issue "offered interpolation"
- fixed issue if no coverage was found in selected time-period
- removed the "striping/alternatingRowColors" from the Coverage-listings
- fixed issue with "no data selected" fixed for "DescribeEOCoverage" Request
- fixed various the xml-namespace handling issues
- fixed issue with the "clock" icon shown permanently
- fixed Time selection (BeginTime/EndTime) for DescribeEOCoverageSet (2.5 D coverages i.e 2D plus Time), added plausability check
- fixed issue with associateing the corresponding axisLabel / CRS etc. with each coverage (once DescribeCoverage is executed for a specific
Coverage)


2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
A QGIS plugin
A OGC WCS 2.0/EO-WCS Client
-------------------
begin : 2014-06-26
begin : 2014-06-26; 2017-04-10
copyright : (C) 2014 by Christian Schiller / EOX IT Services GmbH, Vienna, Austria
email : christian dot schiller at eox dot at
***************************************************************************/
Expand Down
Loading

0 comments on commit 7378165

Please sign in to comment.