1 | # Copyright (C) 2008 STFC & NERC (Science and Technology Facilities Council). |
---|
2 | # This software may be distributed under the terms of the |
---|
3 | # Q Public License, version 1.0 or later. |
---|
4 | # http://ndg.nerc.ac.uk/public_docs/QPublic_license.txt |
---|
5 | |
---|
6 | """ Classes to handle filter encoding queries as used in WFS """ |
---|
7 | |
---|
8 | |
---|
9 | from xml.etree import ElementTree as etree |
---|
10 | import logging |
---|
11 | log = logging.getLogger(__name__) |
---|
12 | |
---|
13 | """ utility functions to save writing out fully qualified names """ |
---|
14 | nsOGC = 'http://www.opengis.net/ogc' |
---|
15 | def OGC(tag): |
---|
16 | return "{"+nsOGC+"}"+tag |
---|
17 | |
---|
18 | nsGML = 'http://www.opengis.net/gml' |
---|
19 | def GML(tag): |
---|
20 | return "{"+nsGML+"}"+tag |
---|
21 | |
---|
22 | class FEQuery(object): |
---|
23 | """ Represents a WFS Query string which can contain feature type names and a variety of filters """ |
---|
24 | |
---|
25 | def __init__(self, querystring): |
---|
26 | self.querystring=str(querystring) #may be unicode, so convert to string |
---|
27 | self.elem=etree.fromstring(self.querystring) |
---|
28 | log.debug("Query String: %s"%self.querystring) |
---|
29 | log.debug("Query Element: %s"%self.elem) |
---|
30 | |
---|
31 | def getTypeName(self): |
---|
32 | typename=self.elem.get('typeName') |
---|
33 | log.debug('TypeName = %s'%typename) |
---|
34 | return typename |
---|
35 | |
---|
36 | def getFilters(self): |
---|
37 | """returns parts of query as individual filters which can then be translated into |
---|
38 | method calls on the wfs_iface. Examples of filters include, getByGMLid, getByBBox, getByTimeRange""" |
---|
39 | queryfilters=[] |
---|
40 | for child in self.elem.getchildren(): |
---|
41 | if child.tag==OGC('Filter'): |
---|
42 | #break <ogc:Filter> down into single filters. |
---|
43 | fp=FilterParser(child) |
---|
44 | for filter in fp.filters: |
---|
45 | queryfilters.append(filter) |
---|
46 | return queryfilters |
---|
47 | |
---|
48 | class FilterParser(object): |
---|
49 | """ Handles an OGC Filter object which in reality can contain several 'sub-filters'. Breaks this down into individual 'Filters' """ |
---|
50 | def __init__(self, filterelem): |
---|
51 | self.filters=[] |
---|
52 | for elem in filterelem.getchildren(): |
---|
53 | if elem.tag == OGC('GmlObjectId'): |
---|
54 | value=elem.get(GML('id')) |
---|
55 | f=Filter('GmlObjectId', value) |
---|
56 | self.filters.append(f) |
---|
57 | elif elem.tag==OGC('BBOX'): |
---|
58 | value=(1,2,3,4) |
---|
59 | srsname='WGS84' |
---|
60 | f=BBoxFilter(srsname, 'BBOX', value) |
---|
61 | self.filters.append(f) |
---|
62 | |
---|
63 | |
---|
64 | class Filter(object): |
---|
65 | """represents an individual WFS filter (i.e. a filter containing 'And' will be modelled as 2 filters). |
---|
66 | @ivar filtertype: Type of filter (e.g. 'getByBBox') |
---|
67 | @ivar fitlervalue: Appropriate value on which to filter (e.g. Bounding box tuple) |
---|
68 | """ |
---|
69 | def __init__(self, filtertype, filtervalue): |
---|
70 | self.filtertype = filtertype |
---|
71 | self.filtervalue = filtervalue |
---|
72 | |
---|
73 | class BBoxFilter(Filter): |
---|
74 | """ As Filter, but with additional srsname attribute """ |
---|
75 | def __init__(self, srsname, *args): |
---|
76 | super(BBoxFilter, self).__init__(*args) |
---|
77 | self.srsname=srsname |
---|
78 | |
---|
79 | class FEQueryProcessor(object): |
---|
80 | """ Given a Featureset and a list of of Filters, applies the filters |
---|
81 | to the Featureset and creates a resultset |
---|
82 | Assumptions: |
---|
83 | 1) GmlObjectId is only used as a filter alone or with other GmlObjectId filters, not with other filter types. |
---|
84 | 2) Other filter types, such as bounding box or temporal range filters only appear once. |
---|
85 | |
---|
86 | """ |
---|
87 | |
---|
88 | def __init__(self, featureset, filters): |
---|
89 | self.featureset=featureset |
---|
90 | self.resultset=[] |
---|
91 | temporalrangeresults=[] |
---|
92 | bboxresults=[] |
---|
93 | temporalflag=False |
---|
94 | bboxflag = False |
---|
95 | for filter in filters: |
---|
96 | log.debug('Filtertype %s'%filter.filtertype) |
---|
97 | if filter.filtertype=='GmlObjectId': |
---|
98 | feature=self.featureset.getFeatureByGMLid(filter.filtervalue) |
---|
99 | if feature: |
---|
100 | self.resultset.append(feature) |
---|
101 | else: |
---|
102 | #handle other filtertypes, bbox & temporal for now: |
---|
103 | if filter.filtertype=='BBOX': |
---|
104 | bboxflag=True |
---|
105 | bboxresults=self.featureset.getFeaturesByBBox(filter.filtervalue, filter.srsname) |
---|
106 | elif filter.filtertype=='TemporalRange': |
---|
107 | temporalflag=True |
---|
108 | temporalrangeresults=self.featureset.getFeaturesByTemporalRange(filter.filtervalue) |
---|
109 | |
---|
110 | #Now work out what the overlapping (if appropriate) resultset is. |
---|
111 | log.debug('bboxflag %s'%bboxflag) |
---|
112 | if temporalflag and bboxflag: #both filters applied |
---|
113 | log.debug('results from temporal & bbox filter %s'%self.resultset) |
---|
114 | self.resultset=self._combineresults([set(temporalrangeresults), set(bboxresults)]) |
---|
115 | elif temporalflag: #only temporal filter |
---|
116 | self.resultset=temporalrangeresults |
---|
117 | log.debug('results from temporal only filter %s'%self.resultset) |
---|
118 | elif bboxflag: #only bbox filter |
---|
119 | self.resultset=bboxresults |
---|
120 | log.debug('results from bbox only filter %s'%self.resultset) |
---|
121 | |
---|
122 | log.debug('results from gml filter %s'%self.resultset) |
---|
123 | #TODO, handle null filter |
---|
124 | |
---|
125 | |
---|
126 | def _combineresults(self, results): |
---|
127 | """ combine (AND operation) results from two or more filters """ |
---|
128 | baseset=[] |
---|
129 | for set in results: |
---|
130 | if baseset == []: |
---|
131 | baseset = set |
---|
132 | else: |
---|
133 | baseset=baseset.intersection(set) |
---|
134 | return baseset |
---|
135 | |
---|
136 | |
---|