# -*- coding: utf-8 -*-
"""
sos module
"""
from sosparser import * #@UnusedWildImport
from qgstime import QgsTimeInstant, QgsTimePeriod
from PyQt4.QtCore import QUrl, QObject, QFile, pyqtSlot, pyqtSignal, Qt, QVariant
from PyQt4.QtXml import QDomDocument
from qgis.core import QgsGeometry, QgsRectangle, QgsOgcUtils, QgsFields, QgsField, QgsFeature, QgsCoordinateReferenceSystem, QGis, QgsVectorFileWriter, QgsVectorLayer
[docs]class SensorObservationService (QObject):
"""
Represent a Sensor Observation Service
"""
def __init__(self, url, xmlFile=None):
"""
:param url: Sensor Observation Service URL
:type str
:param xmlFile: XML capabilities filename
:type str
"""
super (SensorObservationService, self).__init__()
self._url = url
self._capabilities = SOSCapabilities ()
if xmlFile:
xml = QFile (xmlFile)
self._capabilities = XMLParserFactory.getInstance("SOSCapabilities")().parse(xml)
@staticmethod
[docs] def capabilitiesUrl(url):
_url = QUrl (url)
_url.addQueryItem("Service", "SOS")
_url.addQueryItem("AcceptVersions", "1.0.0")
_url.addQueryItem("Request","GetCapabilities")
return _url
@property
def version(self):
return '1.0.0'
@property
def url (self):
return self._url
@property
def capabilitiesVersion (self):
try:
return self._capabilities.version
except AttributeError:
return ""
@property
def capabilitiesXml (self):
try:
return self._capabilities.xml
except AttributeError:
return ""
@property
def identification (self):
try:
return self._capabilities.serviceIdentification
except AttributeError:
return SOSServiceIdentification()
@property
def provider (self):
try:
return self._capabilities.serviceProvider
except AttributeError:
return SOSServiceProvider()
@property
def observationOfferingList (self):
try:
return self._capabilities.observationOfferingList.keys()
except AttributeError:
return []
@property
def operationsMetadata (self):
try:
return self._capabilities.operationsMetadata
except AttributeError:
return {}
@property
def getObservationsUrl (self):
try:
url = QUrl(self.operationsMetadata['GetObservation'].methods['Post'])
#Para servidores mal configurados
if url.host() == 'localhost':
url.setHost (self.url.host())
url.setPort (self.url.port())
return url
return url
except:
return QUrl()
@property
def spatialOperands (self):
try:
return self._capabilities.filterCapabilities.spatial_operands
except AttributeError:
return []
@property
def spatialOperators (self):
try:
return self._capabilities.filterCapabilities.spatial_operators
except AttributeError:
return []
@property
def temporalOperands (self):
try:
return self._capabilities.filterCapabilities.temporal_operands
except AttributeError:
return []
@property
def temporalOperators (self):
try:
return self._capabilities.filterCapabilities.temporal_operators
except AttributeError:
return []
@property
def scalarOperators (self):
try:
return self._capabilities.filterCapabilities.scalar_operators
except AttributeError:
return []
def __getitem__(self, offer):
if offer in self._capabilities.observationOfferingList.keys():
return self._capabilities.observationOfferingList[offer]
else:
raise KeyError ("No Observational Offering with id: %s" % offer)
def __unicode__ (self):
return "<html><body>"+"".join(
map (lambda title, text:
'<p class="subheaderglossy">' + title + '</p><p>' + text + '</p>', [
self.tr ('Identification'),
self.tr ('Provider')
],[
unicode (self.identification),
unicode (self.provider)])) + "</body></html>"
[docs] def getObservations (self, offering="", properties=[], features=[], procedures=[], filters=None, resultModel = ""):
"""
:param offering: Offering name
:type offering: str
:param properties: Selected properties names
:type properties: str list
:param features: Selected features of interest names
:type features: str list
:param procedures: Selected procedures names
:type procedures: str list
:param filters: Configured filters
:type filters: FilterRequest
:param resultModel: Selected result model
:type resultModel: str
:return: xml data
"""
doc = QDomDocument()
doc.appendChild(doc.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"'))
root = doc.createElement('GetObservation')
root.setAttribute('xmlns',"http://www.opengis.net/sos/1.0")
root.setAttribute('xmlns:ows',"http://www.opengis.net/ows/1.1")
root.setAttribute('xmlns:gml',"http://www.opengis.net/gml")
root.setAttribute('xmlns:ogc',"http://www.opengis.net/ogc")
root.setAttribute('xmlns:om',"http://www.opengis.net/om/1.0")
root.setAttribute('xmlns:xsi',"http://www.w3.org/2001/XMLSchema-instance")
root.setAttribute('xsi:schemaLocation',"http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd")
root.setAttribute('service',"SOS")
root.setAttribute('version',"1.0.0")
root.setAttribute('srsName',self[offering].srsName)
doc.appendChild(root)
offer = doc.createElement("offering")
offer.appendChild(doc.createTextNode(offering))
root.appendChild (offer)
if filters.temporalFilter:
timeEvent = doc.createElement("eventTime")
operator = doc.createElement("ogc:%s" % filters.temporalOperator)
prop = doc.createElement("ogc:PropertyName")
prop.appendChild (doc.createTextNode ("om:samplingTime"))
operand = doc.createElement(filters.temporalOperand)
if filters.temporalOperand == "gml:TimeInstant":
timePos = doc.createElement("gml:timePosition")
timePos.appendChild(doc.createTextNode(str(filters.temporalValue)))
operand.appendChild (timePos)
elif filters.temporalOperand == "gml:TimePeriod":
begin = doc.createElement("gml:beginPosition")
begin.appendChild(doc.createTextNode(str(filters.temporalValue).split()[0]))
end = doc.createElement("gml:endPosition")
end.appendChild(doc.createTextNode(str(filters.temporalValue).split()[-1]))
operand.appendChild(begin)
operand.appendChild(end)
root.appendChild(timeEvent)
timeEvent.appendChild(operator)
operator.appendChild (prop)
operator.appendChild (operand)
for proc in procedures:
procElement = doc.createElement("procedure")
procElement.appendChild (doc.createTextNode(proc))
root.appendChild (procElement)
for prop in properties:
propElement = doc.createElement("observedProperty")
propElement.appendChild(doc.createTextNode(prop))
root.appendChild (propElement)
foi = doc.createElement("featureOfInterest") if len (features) else None
for foiName in features:
foiElement = doc.createElement("ObjectID")
foiElement.appendChild(doc.createTextNode(foiName))
foi.appendChild (foiElement)
if filters.spatialFilter and not foi:
foi = doc.createElement("featureOfInterest")
operator = doc.createElement("ogc:%s" % filters.spatialOperator)
prop = doc.createElement("ogc:PropertyName")
prop.appendChild (doc.createTextNode ("urn:ogc:data:location"))
operator.appendChild (prop)
try:
if filters.spatialOperand == "gml:Point":
gmlNode = QgsOgcUtils.geometryToGML(QgsGeometry.fromPoint(filters._spatialValue), doc)
elif filters.spatialOperand == "gml:Envelope":
gmlNode = QgsOgcUtils.rectangleToGMLEnvelope(QgsGeometry.fromRect(filters._spatialValue).boundingBox(), doc)
elif filters.spatialOperand == "gml:Polygon":
gmlNode = QgsOgcUtils.geometryToGML(QgsGeometry.fromPolygon(filters._spatialValue), doc)
elif filters.spatialOperand == "gml:LineString":
gmlNode = QgsOgcUtils.geometryToGML(QgsGeometry.fromPolyline(filters._spatialValue), doc)
gmlNode.setAttribute('srsName',self[offering].srsName)
operator.appendChild (gmlNode)
except:
pass
foi.appendChild(operator)
#Lista de features o filtro espacial
if foi:
root.appendChild(foi)
if filters.scalarFilter:
result = doc.createElement("result")
operator = doc.createElement("ogc:PropertyIs%s" % filters.scalarOperator)
prop = doc.createElement("ogc:PropertyName")
prop.appendChild (doc.createTextNode (filters.scalarOperand))
operator.appendChild (prop)
if filters.scalarOperator in ["Between"]:
try:
lower = doc.createElement ("ogc:LowerBoundary")
lowValue = doc.createElement ("ogc:Literal")
lowValue.appendChild(doc.createTextNode(str(filters.scalarValue).split()[0]))
lower.appendChild (lowValue)
upper = doc.createElement ("ogc:UpperBoundary")
upValue = doc.createElement ("ogc:Literal")
upValue.appendChild(doc.createTextNode(str(filters.scalarValue).split()[-1]))
upper.appendChild (upValue)
operator.appendChild (lower)
operator.appendChild (upper)
except:
pass
else:
value = doc.createElement ("ogc:Literal")
value.appendChild(doc.createTextNode(str(filters.scalarValue)))
operator.appendChild (value)
root.appendChild(result)
result.appendChild(operator)
responseFormat = doc.createElement ("responseFormat")
responseFormat.appendChild (doc.createTextNode('text/xml;subtype="om/1.0.0"'))
root.appendChild (responseFormat)
resultModelElement = doc.createElement ("resultModel")
resultModelElement.appendChild (doc.createTextNode(resultModel))
root.appendChild (resultModelElement)
responseMode = doc.createElement ("responseMode")
responseMode.appendChild (doc.createTextNode("inline"))
root.appendChild (responseMode)
return doc.toString(4)
[docs]class SOSCapabilities ():
"""
Capabilities data
"""
def __init__(self):
self.xml = ""
self.version = ""
self.serviceIdentification = None
self.serviceProvider = None
self.operationsMetadata = {}
self.filterCapabilities = None
self.observationOfferingList = {}
[docs]class SOSServiceIdentification (QObject):
"""
Service Identification data
"""
def __init__(self) :
super (SOSServiceIdentification, self).__init__()
self.title = ""
self.abstract = ""
self.keywords = []
self.serviceType = ""
self.serviceTypeVersion = ""
def __unicode__ (self):
return "".join(
map (lambda title, text:
'<p class="glossy">' + title + '</p><p>' + text + '</p>', [
self.tr ('Title'),
self.tr ('Abstract'),
self.tr ('Keywords'),
self.tr ('Service')
],[
self.title,
self.abstract,
", ".join(self.keywords),
self.serviceType +' (' + self.serviceTypeVersion +')']))
[docs]class SOSServiceProvider (QObject):
"""
Service Provider data
"""
def __init__(self) :
super (SOSServiceProvider, self).__init__()
self.providerName = ""
self.providerSite = ""
self.phones = {}
self.address = {}
def __unicode__ (self):
return "".join(
map (lambda title, text:
'<p class="glossy">' + title + '</p><p>' + text + '</p>', [
self.tr ('Name'),
self.tr ('Site'),
self.tr ('Phones'),
self.tr ('Address')
],[
self.providerName,
self.providerSite,
" ".join(self.phones.values()),
"<br>".join(self.address.values())]))
[docs]class SOSFilterCapabilities ():
"""
Filter capabilities data
"""
def __init__(self):
self.spatial_operands = []
self.spatial_operators = []
self.temporal_operands = []
self.temporal_operators = []
self.scalar_operators = []
[docs]class SOSObservationOffering ():
"""
Observation offering data
"""
def __init__(self) :
self.id = ""
self.name = ""
self.description = ""
self.srsName = ""
self.boundedBy = None
self.time = None
self.proceduresList = []
self.observedPropertiesList = []
self.featureOfInterestList = []
self.responseFormat = ""
self.resultModel = []
self.responseMode = ""
[docs]class FilterRequest (object):
"""
Filter request: Spatial, Temporal and Scalar with Operator and Operands
"""
def __init__(self, service):
assert isinstance(service, SensorObservationService)
self._service = service
self.spatialFilter = None
if len(self._service.spatialOperators) > 0:
self.spatialFilter = False
self.spatialOperator = self._service.spatialOperators[0]
self.spatialOperand = self._service.spatialOperands [0]
self._spatialValue = None
self.temporalFilter = None
if len(self._service.temporalOperators) > 0:
self.temporalFilter = False
self.temporalOperator = self._service.temporalOperators[0]
self.temporalOperand = self._service.temporalOperands [0]
self._temporalValue = None
self.scalarFilter = None
if len(self._service.scalarOperators):
self.scalarFilter = False
self.scalarOperator = self._service.scalarOperators[0]
self.scalarOperand = ""
self._scalarValue = None
@property
def spatialValue (self):
try:
if self._spatialValue == None:
raise ValueError
if self.spatialOperand == "gml:Point":
return QgsGeometry.fromPoint(self._spatialValue).exportToWkt()
elif self.spatialOperand == "gml:Envelope":
return QgsGeometry.fromRect(self._spatialValue).exportToWkt()
elif self.spatialOperand == "gml:Polygon":
return QgsGeometry.fromPolygon(self._spatialValue).exportToWkt()
elif self.spatialOperand == "gml:LineString":
return QgsGeometry.fromPolyline(self._spatialValue).exportToWkt()
else:
raise ValueError
except:
return ""
@spatialValue.setter
def spatialValue (self, value):
try:
if value.__len__:
geom = QgsGeometry.fromWkt(value)
else:
raise ValueError
if self.spatialOperand == "gml:Point":
self._spatialValue = geom.asPoint()
elif self.spatialOperand == "gml:Envelope":
self._spatialValue = geom.boundingBox()
elif self.spatialOperand == "gml:Polygon":
self._spatialValue = geom.asPolygon()
elif self.spatialOperand == "gml:LineString":
self._spatialValue = geom.asPolyline()
else:
raise ValueError
except:
self._spatialValue = None
@property
def temporalValue (self):
try:
return self._temporalValue if self._temporalValue else ""
except:
return ""
@temporalValue.setter
def temporalValue (self, value):
if self.temporalOperand == "gml:TimeInstant":
self._temporalValue = QgsTimeInstant (value)
elif self.temporalOperand == "gml:TimePeriod":
self._temporalValue = QgsTimePeriod (value.split()[0], value.split()[-1])
else:
self._temporalValue = value
@property
def scalarValue (self):
try:
return self._scalarValue if self._scalarValue else ""
except:
return ""
@scalarValue.setter
def scalarValue (self, value):
self._scalarValue = value
def __str__(self):
return "Spatial: {} {} {} {}; Temporal: {} {} {} {}; Scalar: {} {} {} {}".format(self.spatialFilter,self.spatialOperator, self.spatialOperand, self.spatialValue, self.temporalFilter, self.temporalOperator, self.temporalOperand, self.temporalValue, self.scalarFilter, self.scalarOperator, self.scalarOperand, self.scalarValue)
[docs]class ExceptionReport(Exception):
"""
SOS Exception
"""
def __init__(self, exceptionCode, exceptionText):
self.__exceptionCode = exceptionCode
self.__exceptionText = exceptionText
@property
def exceptionCode(self):
return self.__exceptionCode if self.__exceptionCode else ""
@property
def exceptionText(self):
return self.__exceptionText if self.__exceptionText else ""
def __str__(self):
return self.exceptionCode + ": " + self.exceptionText
[docs]class SOSProvider (object):
"""
Fake QgsVectorDataProvider
"""
def __init__(self):
self.srsName = ""
self.extent = QgsRectangle ()
self.features = {}
self.fields = [QgsField("foi", QVariant.String), QgsField("name", QVariant.String)]
self._observations = {}
self.only1stGeo = False
[docs] def setObservation (self, foi, time, observedProperty, value):
"""
:param foi: Feature Of Interest
:type foi: str
:param time: Phenomenom Time
:type time: str
:param observedProperty: Property
:type observedProperty: str
:param value: observed value
:type value: float
"""
try:
timeList, propertiesList = self._observations[foi]
propertiesList[timeList.index(time)][observedProperty] = value
except ValueError:
#Ya existe registro para la foi, pero no para time
timeList.append(time)
propertiesList.append({observedProperty: value})
except KeyError:
#No existe registro para la foi
timeList = []
propertiesList = []
self._observations[foi] = (timeList,propertiesList)
timeList.append(time)
propertiesList.append({observedProperty: value})
[docs] def getObservation (self, foi="", time = None):
"""
Only for testing purposes!
"""
text = ""
for foi in self._observations.keys():
text += ";" + foi + ": "
timeList, propertiesList = self._observations[foi]
for i in range(len(timeList)):
text += str(timeList[i].toString(Qt.ISODate)) + "-" + str(propertiesList[i]) + ","
return text
[docs] def getFeatures (self):
"""
:return QgsFeaures generator
"""
fields = QgsFields()
map (fields.append, self.fields)
foiIds = {foiId : [] for foiId in self.features}
try:
idIsTuple = isinstance (eval(self._observations.keys()[0]),tuple)
except:
idIsTuple = False
if idIsTuple:
map (lambda x: foiIds[eval(x)[0]].append(x), self._observations.keys())
else:
map (lambda x: foiIds[x].append(x), self.features)
for foi in self.features:
name, geo = self.features[foi]
for foi_id in foiIds[foi]:
timeList, propertiesList = self._observations[foi_id]
#for i,t in enumerate(timeList):
for i, t in sorted({i:t for i,t in enumerate(timeList)}.items(), key=lambda x:x[1]):
values = []
for f in self.fields[3:]: #TODO si no tiene time esto no va a funcionar...
try:
values.append(propertiesList[i][str(f.name())])
except KeyError:
values.append (f.name())
feature = QgsFeature (fields)
if geo:
feature.setGeometry (geo)
if self.only1stGeo: geo = None
feature.setAttributes([foi, name, t.toString(Qt.ISODate)] + values)
yield feature
[docs]class ObservationsLayer (QObject):
"""
Encapsulate QgsVectorLayer generation
"""
finished = pyqtSignal ()
failed = pyqtSignal (unicode)
def __init__(self, name = "Observations", xmlFile=None, only1stGeo = False):
"""
:param name Layer name
:type str
:param xmlFile XML Observations filename
:type str
:param only1stGeo If True only first occurrence by foi will include geometric data
:type bool
"""
super (ObservationsLayer, self).__init__()
self._name = name
self.features = []
self.provider = None
self.xmlFile = xmlFile
self._layer = None
self._error = None
self._only1stGeo = only1stGeo
@property
def name (self):
return self._name
@pyqtSlot ()
[docs] def toVectorLayer (self):
"""
Generate QgsVectorLayer
"""
try:
if self.xmlFile:
xml = QFile (self.xmlFile)
self.provider = XMLParserFactory.getInstance("SOSObservations")().parse(xml)
self.provider.only1stGeo = self._only1stGeo
layer = self._toVectorLayer_geojson()
layer.setCustomProperty("xml", self.xmlFile)
self._layer = layer
self._error = None
except Exception as error:
self._layer = None
self._error = unicode(error)
finally:
self.finished.emit()
@property
def vectorLayer (self):
if self._layer:
return self._layer
else:
return QgsVectorLayer()
@property
def error (self):
if not self._error and not self.vectorLayer.isValid():
self._error = self.tr("Invalid layer")
return self._error if self._error else ""
# def toVectorLayer_sqlite (self):
# crs = QgsCoordinateReferenceSystem()
# crs.createFromUserInput(self.provider.srsName)
#
# fileName = self.xmlFile.replace(".xml", ".sqlite")
# fields = QgsFields ()
# map (fields.append, self.provider.fields)
# writer = QgsVectorFileWriter (fileName, "utf-8", fields, QGis.WKBPoint, crs, "SQLite")
#
# if writer.hasError() != QgsVectorFileWriter.NoError:
# raise Exception (writer.errorMessage())
#
# for feature in self.provider.getFeatures():
#
# self.features.append(feature)
# writer.addFeature(feature)
#
# del writer #Forzar escritura a disco
#
# return QgsVectorLayer( fileName, self.name, "ogr")
def _toVectorLayer_geojson (self):
crs = QgsCoordinateReferenceSystem()
crs.createFromUserInput(self.provider.srsName)
fileName = self.xmlFile.replace(".xml", ".geojson")
fields = QgsFields ()
map (fields.append, self.provider.fields)
writer = QgsVectorFileWriter (fileName, "utf-8", fields, QGis.WKBPoint, crs, "GeoJSON")
if writer.hasError() != QgsVectorFileWriter.NoError:
raise Exception (writer.errorMessage())
for feature in self.provider.getFeatures():
self.features.append(feature)
writer.addFeature(feature)
del writer #Forzar escritura a disco
return QgsVectorLayer( fileName, self.name, "ogr")
# def toVectorLayer_memory (self):
# def qgsSrsName (srsName = ""):
# import re
# m = re.match (".*(EPSG:[0-9]+)$",srsName)
# return m.groups()[-1] if m else ""
#
# path = ("Point?type=SOS&crs={srs!s}&field=").format(srs=qgsSrsName(self.provider.srsName))
# path += "&field=".join (map (lambda f: (str(f.name()) + ":") + ("double" if f.type() == QVariant.Double else "string"), self.provider.fields))
# path += "&index=yes"
#
# for feature in self.provider.getFeatures():
# self.features.append(feature)
#
# layer = QgsVectorLayer (path, self._name, "memory")
# layerProvider = layer.dataProvider()
# layer.startEditing()
# layerProvider.addFeatures (self.features)
# layer.commitChanges()
#
# return layer
def __str__(self):
return self.provider.getObservation()