Source code for sos.sos

# -*- 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 SOSOperationMetadata (): """ Operations metadata """ def __init__(self): self.name = "" self.methods = {} self.parameters = {}
[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()