• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geopython / OWSLib / 11628884181

01 Nov 2024 11:49AM UTC coverage: 58.814% (-1.3%) from 60.156%
11628884181

Pull #548

github

web-flow
Merge 4b9b7cf1f into ae98c2039
Pull Request #548: Strip out redundant __new__ to enable object pickling

8364 of 14221 relevant lines covered (58.81%)

1.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.22
/owslib/fes2.py
1
# -*- coding: ISO-8859-15 -*-
2
# =============================================================================
3
# Copyright (c) 2021 Tom Kralidis
4
#
5
# Authors : Tom Kralidis <tomkralidis@gmail.com>
6
#
7
# Contact email: tomkralidis@gmail.com
8
# =============================================================================
9

10
"""
11
API for OGC Filter Encoding (FE) constructs and metadata.
12

13
Filter Encoding: http://www.opengeospatial.org/standards/filter
14

15
Supports version 2.0.2 (09-026r2).
16
"""
17

18
from owslib.etree import etree
2✔
19
from owslib import util
2✔
20
from owslib.namespaces import Namespaces
2✔
21
from abc import ABCMeta, abstractmethod
2✔
22

23

24
# default variables
25
def get_namespaces():
2✔
26
    n = Namespaces()
2✔
27
    ns = n.get_namespaces(["dif", "fes", "gml", "ogc", "ows110", "xs", "xsi"])
2✔
28
    ns[None] = n.get_namespace("fes")
2✔
29
    return ns
2✔
30

31

32
namespaces = get_namespaces()
2✔
33
schema = 'http://schemas.opengis.net/filter/2.0/filterAll.xsd'
2✔
34
schema_location = '%s %s' % (namespaces['fes'], schema)
2✔
35

36

37
class FilterRequest(object):
2✔
38
    """ filter class """
39
    def __init__(self, parent=None, version='2.0.0'):
2✔
40
        """
41

42
        filter Constructor
43

44
        Parameters
45
        ----------
46

47
        - parent: parent etree.Element object (default is None)
48
        - version: version (default is '2.0.0')
49

50
        """
51

52
        self.version = version
×
53
        self._root = etree.Element(util.nspath_eval('fes:Filter', namespaces))
×
54
        if parent is not None:
×
55
            self._root.set(util.nspath_eval('xsi:schemaLocation', namespaces), schema_location)
×
56

57
    def set(self, parent=False, qtype=None, keywords=[], typenames='csw:Record', propertyname='csw:AnyText', bbox=None,
2✔
58
            identifier=None):
59
        """
60

61
        Construct and process a  GetRecords request
62

63
        Parameters
64
        ----------
65

66
        - parent: the parent Element object.  If this is not, then generate a standalone request
67
        - qtype: type of resource to query (i.e. service, dataset)
68
        - keywords: list of keywords
69
        - propertyname: the ValueReference to Filter against
70
        - bbox: the bounding box of the spatial query in the form [minx,miny,maxx,maxy]
71
        - identifier: the dc:identifier to query against with a PropertyIsEqualTo.  Ignores all other inputs.
72

73
        """
74

75
        # Set the identifier if passed.  Ignore other parameters
76
        dc_identifier_equals_filter = None
×
77
        if identifier is not None:
×
78
            dc_identifier_equals_filter = PropertyIsEqualTo('dc:identifier', identifier)
×
79
            self._root.append(dc_identifier_equals_filter.toXML())
×
80
            return self._root
×
81

82
        # Set the query type if passed
83
        dc_type_equals_filter = None
×
84
        if qtype is not None:
×
85
            dc_type_equals_filter = PropertyIsEqualTo('dc:type', qtype)
×
86

87
        # Set a bbox query if passed
88
        bbox_filter = None
×
89
        if bbox is not None:
×
90
            bbox_filter = BBox(bbox)
×
91

92
        # Set a keyword query if passed
93
        keyword_filter = None
×
94
        if len(keywords) > 0:
×
95
            if len(keywords) > 1:  # loop multiple keywords into an Or
×
96
                ks = []
×
97
                for i in keywords:
×
98
                    ks.append(PropertyIsLike(propertyname, "*%s*" % i, wildCard="*"))
×
99
                keyword_filter = Or(operations=ks)
×
100
            elif len(keywords) == 1:  # one keyword
×
101
                keyword_filter = PropertyIsLike(propertyname, "*%s*" % keywords[0], wildCard="*")
×
102

103
        # And together filters if more than one exists
104
        filters = [_f for _f in [keyword_filter, bbox_filter, dc_type_equals_filter] if _f]
×
105
        if len(filters) == 1:
×
106
            self._root.append(filters[0].toXML())
×
107
        elif len(filters) > 1:
×
108
            self._root.append(And(operations=filters).toXML())
×
109

110
        return self._root
×
111

112
    def setConstraint(self, constraint, tostring=False):
2✔
113
        """
114
        Construct and process a  GetRecords request
115

116
        Parameters
117
        ----------
118

119
        - constraint: An OgcExpression object
120
        - tostring (optional): return as string
121

122
        """
123
        self._root.append(constraint.toXML())
×
124

125
        if tostring:
×
126
            return util.element_to_string(self._root, xml_declaration=False)
×
127

128
        return self._root
×
129

130
    def setConstraintList(self, constraints, tostring=False):
2✔
131
        """
132
        Construct and process a  GetRecords request
133

134
        Parameters
135
        ----------
136

137
        - constraints: A list of OgcExpression objects
138
                       The list is interpretted like so:
139

140
                       [a,b,c]
141
                       a || b || c
142

143
                       [[a,b,c]]
144
                       a && b && c
145

146
                       [[a,b],[c],[d],[e]] or [[a,b],c,d,e]
147
                       (a && b) || c || d || e
148
        - tostring (optional): return as string
149

150
        """
151
        ors = []
×
152
        if len(constraints) == 1:
×
153
            if isinstance(constraints[0], OgcExpression):
×
154
                flt = self.setConstraint(constraints[0])
×
155
            else:
156
                self._root.append(And(operations=constraints[0]).toXML())
×
157
                flt = self._root
×
158
            if tostring:
×
159
                return util.element_to_string(flt, xml_declaration=False)
×
160
            else:
161
                return flt
×
162

163
        for c in constraints:
×
164
            if isinstance(c, OgcExpression):
×
165
                ors.append(c)
×
166
            elif isinstance(c, list) or isinstance(c, tuple):
×
167
                if len(c) == 1:
×
168
                    ors.append(c[0])
×
169
                elif len(c) >= 2:
×
170
                    ands = []
×
171
                    for sub in c:
×
172
                        if isinstance(sub, OgcExpression):
×
173
                            ands.append(sub)
×
174
                    ors.append(And(operations=ands))
×
175

176
        self._root.append(Or(operations=ors).toXML())
×
177

178
        if tostring:
×
179
            return util.element_to_string(self._root, xml_declaration=False)
×
180

181
        return self._root
×
182

183

184
class FilterCapabilities(object):
2✔
185
    """Abstraction for Filter_Capabilities 2.0"""
186
    def __init__(self, elem):
2✔
187

188
        if elem is None:
×
189
            self.spatial_operands = []
×
190
            self.spatial_operators = []
×
191
            self.temporal_operators = []
×
192
            self.temporal_operands = []
×
193
            self.scalar_comparison_operators = []
×
194
            self.conformance = {}
×
195
            return
×
196

197
        # Spatial_Capabilities
198
        self.spatial_operands = [f.attrib.get('name') for f in elem.findall(util.nspath_eval(
×
199
            'fes:Spatial_Capabilities/fes:GeometryOperands/fes:GeometryOperand', namespaces))]
200
        self.spatial_operators = []
×
201
        for f in elem.findall(util.nspath_eval(
×
202
                'fes:Spatial_Capabilities/fes:SpatialOperators/fes:SpatialOperator', namespaces)):
203
            self.spatial_operators.append(f.attrib['name'])
×
204

205
        # Temporal_Capabilities
206
        self.temporal_operands = [f.attrib.get('name') for f in elem.findall(util.nspath_eval(
×
207
            'fes:Temporal_Capabilities/fes:TemporalOperands/fes:TemporalOperand', namespaces))]
208
        self.temporal_operators = []
×
209
        for f in elem.findall(util.nspath_eval(
×
210
                'fes:Temporal_Capabilities/fes:TemporalOperators/fes:TemporalOperator', namespaces)):
211
            self.temporal_operators.append(f.attrib['name'])
×
212

213
        # Scalar_Capabilities
214
        self.scalar_comparison_operators = [f.text for f in elem.findall(util.nspath_eval(
×
215
            'fes:Scalar_Capabilities/fes:ComparisonOperators/fes:ComparisonOperator', namespaces))]
216

217
        # Conformance
218
        self.conformance = {}
×
219
        for f in elem.findall(util.nspath_eval('fes:Conformance/fes:Constraint', namespaces)):
×
220
            self.conformance[f.attrib.get('name')] = f.find(util.nspath_eval('ows110:DefaultValue', namespaces)).text
×
221

222

223
def setsortby(parent, propertyname, order='ASC'):
2✔
224
    """
225

226
    constructs a SortBy element
227

228
    Parameters
229
    ----------
230

231
    - parent: parent etree.Element object
232
    - propertyname: the ValueReference
233
    - order: the SortOrder (default is 'ASC')
234

235
    """
236

237
    tmp = etree.SubElement(parent, util.nspath_eval('fes:SortBy', namespaces))
×
238
    tmp2 = etree.SubElement(tmp, util.nspath_eval('fes:SortProperty', namespaces))
×
239
    etree.SubElement(tmp2, util.nspath_eval('fes:ValueReference', namespaces)).text = propertyname
×
240
    etree.SubElement(tmp2, util.nspath_eval('fes:SortOrder', namespaces)).text = order
×
241

242

243
class SortProperty(object):
2✔
244
    def __init__(self, propertyname, order='ASC'):
2✔
245
        self.propertyname = propertyname
×
246
        self.order = order.upper()
×
247
        if self.order not in ['DESC', 'ASC']:
×
248
            raise ValueError("SortOrder can only be 'ASC' or 'DESC'")
×
249

250
    def toXML(self):
2✔
251
        node0 = etree.Element(util.nspath_eval("fes:SortProperty", namespaces))
×
252
        etree.SubElement(node0, util.nspath_eval('fes:ValueReference', namespaces)).text = self.propertyname
×
253
        etree.SubElement(node0, util.nspath_eval('fes:SortOrder', namespaces)).text = self.order
×
254
        return node0
×
255

256

257
class SortBy(object):
2✔
258
    def __init__(self, properties):
2✔
259
        self.properties = properties
×
260

261
    def toXML(self):
2✔
262
        node0 = etree.Element(util.nspath_eval("fes:SortBy", namespaces))
×
263
        for prop in self.properties:
×
264
            node0.append(prop.toXML())
×
265
        return node0
×
266

267

268
class OgcExpression(object):
2✔
269
    def __init__(self):
2✔
270
        pass
×
271

272

273
class BinaryComparisonOpType(OgcExpression):
2✔
274
    """ Super class of all the property operation classes"""
275
    def __init__(self, propertyoperator, propertyname, literal, matchcase=True):
2✔
276
        self.propertyoperator = propertyoperator
×
277
        self.propertyname = propertyname
×
278
        self.literal = literal
×
279
        self.matchcase = matchcase
×
280

281
    def toXML(self):
2✔
282
        node0 = etree.Element(util.nspath_eval(self.propertyoperator, namespaces))
×
283
        if not self.matchcase:
×
284
            node0.set('matchCase', 'false')
×
285
        etree.SubElement(node0, util.nspath_eval('fes:ValueReference', namespaces)).text = self.propertyname
×
286
        etree.SubElement(node0, util.nspath_eval('fes:Literal', namespaces)).text = self.literal
×
287
        return node0
×
288

289

290
class PropertyIsEqualTo(BinaryComparisonOpType):
2✔
291
    """ PropertyIsEqualTo class"""
292
    def __init__(self, propertyname, literal, matchcase=True):
2✔
293
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsEqualTo', propertyname, literal, matchcase)
×
294

295

296
class PropertyIsNotEqualTo(BinaryComparisonOpType):
2✔
297
    """ PropertyIsNotEqualTo class """
298
    def __init__(self, propertyname, literal, matchcase=True):
2✔
299
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsNotEqualTo', propertyname, literal, matchcase)
×
300

301

302
class PropertyIsLessThan(BinaryComparisonOpType):
2✔
303
    """PropertyIsLessThan class"""
304
    def __init__(self, propertyname, literal, matchcase=True):
2✔
305
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsLessThan', propertyname, literal, matchcase)
×
306

307

308
class PropertyIsGreaterThan(BinaryComparisonOpType):
2✔
309
    """PropertyIsGreaterThan class"""
310
    def __init__(self, propertyname, literal, matchcase=True):
2✔
311
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsGreaterThan', propertyname, literal, matchcase)
×
312

313

314
class PropertyIsLessThanOrEqualTo(BinaryComparisonOpType):
2✔
315
    """PropertyIsLessThanOrEqualTo class"""
316
    def __init__(self, propertyname, literal, matchcase=True):
2✔
317
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsLessThanOrEqualTo', propertyname, literal, matchcase)
×
318

319

320
class PropertyIsGreaterThanOrEqualTo(BinaryComparisonOpType):
2✔
321
    """PropertyIsGreaterThanOrEqualTo class"""
322
    def __init__(self, propertyname, literal, matchcase=True):
2✔
323
        BinaryComparisonOpType.__init__(self, 'fes:PropertyIsGreaterThanOrEqualTo', propertyname, literal, matchcase)
×
324

325

326
class PropertyIsLike(OgcExpression):
2✔
327
    """PropertyIsLike class"""
328
    def __init__(self, propertyname, literal, escapeChar='\\', singleChar='_', wildCard='%', matchCase=True):
2✔
329
        self.propertyname = propertyname
2✔
330
        self.literal = literal
2✔
331
        self.escapeChar = escapeChar
2✔
332
        self.singleChar = singleChar
2✔
333
        self.wildCard = wildCard
2✔
334
        self.matchCase = matchCase
2✔
335

336
    def toXML(self):
2✔
337
        node0 = etree.Element(util.nspath_eval('fes:PropertyIsLike', namespaces))
2✔
338
        node0.set('wildCard', self.wildCard)
2✔
339
        node0.set('singleChar', self.singleChar)
2✔
340
        node0.set('escapeChar', self.escapeChar)
2✔
341
        if not self.matchCase:
2✔
342
            node0.set('matchCase', 'false')
×
343
        etree.SubElement(node0, util.nspath_eval('fes:ValueReference', namespaces)).text = self.propertyname
2✔
344
        etree.SubElement(node0, util.nspath_eval('fes:Literal', namespaces)).text = self.literal
2✔
345
        return node0
2✔
346

347

348
class PropertyIsNull(OgcExpression):
2✔
349
    """PropertyIsNull class"""
350
    def __init__(self, propertyname):
2✔
351
        self.propertyname = propertyname
×
352

353
    def toXML(self):
2✔
354
        node0 = etree.Element(util.nspath_eval('fes:PropertyIsNull', namespaces))
×
355
        etree.SubElement(node0, util.nspath_eval('fes:ValueReference', namespaces)).text = self.propertyname
×
356
        return node0
×
357

358

359
class PropertyIsBetween(OgcExpression):
2✔
360
    """PropertyIsBetween class"""
361
    def __init__(self, propertyname, lower, upper):
2✔
362
        self.propertyname = propertyname
×
363
        self.lower = lower
×
364
        self.upper = upper
×
365

366
    def toXML(self):
2✔
367
        node0 = etree.Element(util.nspath_eval('fes:PropertyIsBetween', namespaces))
×
368
        etree.SubElement(node0, util.nspath_eval('fes:ValueReference', namespaces)).text = self.propertyname
×
369
        node1 = etree.SubElement(node0, util.nspath_eval('fes:LowerBoundary', namespaces))
×
370
        etree.SubElement(node1, util.nspath_eval('fes:Literal', namespaces)).text = '%s' % self.lower
×
371
        node2 = etree.SubElement(node0, util.nspath_eval('fes:UpperBoundary', namespaces))
×
372
        etree.SubElement(node2, util.nspath_eval('fes:Literal', namespaces)).text = '%s' % self.upper
×
373
        return node0
×
374

375

376
class BBox(OgcExpression):
2✔
377
    """Construct a BBox, two pairs of coordinates (west-south and east-north)"""
378
    def __init__(self, bbox, crs=None):
2✔
379
        self.bbox = bbox
×
380
        self.crs = crs
×
381

382
    def toXML(self):
2✔
383
        tmp = etree.Element(util.nspath_eval('fes:BBOX', namespaces))
×
384
        etree.SubElement(tmp, util.nspath_eval('fes:ValueReference', namespaces)).text = 'ows:BoundingBox'
×
385
        tmp2 = etree.SubElement(tmp, util.nspath_eval('gml:Envelope', namespaces))
×
386
        if self.crs is not None:
×
387
            tmp2.set('srsName', self.crs)
×
388
        etree.SubElement(tmp2, util.nspath_eval('gml:lowerCorner', namespaces)).text = '{} {}'.format(
×
389
            self.bbox[0], self.bbox[1])
390
        etree.SubElement(tmp2, util.nspath_eval('gml:upperCorner', namespaces)).text = '{} {}'.format(
×
391
            self.bbox[2], self.bbox[3])
392
        return tmp
×
393

394

395
class Filter(OgcExpression):
2✔
396
    def __init__(self, filter):
2✔
397
        self.filter = filter
2✔
398

399
    def toXML(self):
2✔
400
        node = etree.Element(util.nspath_eval("fes:Filter", namespaces))
2✔
401
        node.append(self.filter.toXML())
2✔
402
        return node
2✔
403

404

405
class TopologicalOpType(OgcExpression, metaclass=ABCMeta):
2✔
406
    """Abstract base class for topological operators."""
407
    @property
2✔
408
    @abstractmethod
2✔
409
    def operation(self):
2✔
410
        """This is a mechanism to ensure this class is subclassed by an actual operation."""
411
        pass
×
412

413
    def __init__(self, propertyname, geometry):
2✔
414
        self.propertyname = propertyname
2✔
415
        self.geometry = geometry
2✔
416

417
    def toXML(self):
2✔
418
        node = etree.Element(util.nspath_eval(f"fes:{self.operation}", namespaces))
2✔
419
        etree.SubElement(node, util.nspath_eval("fes:ValueReference", namespaces)).text = self.propertyname
2✔
420
        node.append(self.geometry.toXML())
2✔
421

422
        return node
2✔
423

424

425
class Intersects(TopologicalOpType):
2✔
426
    operation = "Intersects"
2✔
427

428

429
class Contains(TopologicalOpType):
2✔
430
    operation = "Contains"
2✔
431

432

433
class Disjoint(TopologicalOpType):
2✔
434
    operation = "Disjoint"
2✔
435

436

437
class Within(TopologicalOpType):
2✔
438
    operation = "Within"
2✔
439

440

441
class Touches(TopologicalOpType):
2✔
442
    operation = "Touches"
2✔
443

444

445
class Overlaps(TopologicalOpType):
2✔
446
    operation = "Overlaps"
2✔
447

448

449
class Equals(TopologicalOpType):
2✔
450
    operation = "Equals"
2✔
451

452

453
# BINARY
454
class BinaryLogicOpType(OgcExpression):
2✔
455
    """ Binary Operators: And / Or """
456
    def __init__(self, binary_operator, operations):
2✔
457
        self.binary_operator = binary_operator
2✔
458
        try:
2✔
459
            assert len(operations) >= 2
2✔
460
            self.operations = operations
2✔
461
        except Exception:
×
462
            raise ValueError("Binary operations (And / Or) require a minimum of two operations to operate against")
×
463

464
    def toXML(self):
2✔
465
        node0 = etree.Element(util.nspath_eval(self.binary_operator, namespaces))
2✔
466
        for op in self.operations:
2✔
467
            node0.append(op.toXML())
2✔
468
        return node0
2✔
469

470

471
class And(BinaryLogicOpType):
2✔
472
    def __init__(self, operations):
2✔
473
        super(And, self).__init__('fes:And', operations)
2✔
474

475

476
class Or(BinaryLogicOpType):
2✔
477
    def __init__(self, operations):
2✔
478
        super(Or, self).__init__('fes:Or', operations)
×
479

480

481
# UNARY
482
class UnaryLogicOpType(OgcExpression):
2✔
483
    """ Unary Operator: Not """
484
    def __init__(self, unary_operator, operations):
2✔
485
        self.unary_operator = unary_operator
×
486
        self.operations = operations
×
487

488
    def toXML(self):
2✔
489
        node0 = etree.Element(util.nspath_eval(self.unary_operator, namespaces))
×
490
        for op in self.operations:
×
491
            node0.append(op.toXML())
×
492
        return node0
×
493

494

495
class Not(UnaryLogicOpType):
2✔
496
    def __init__(self, operations):
2✔
497
        super(Not, self).__init__('fes:Not', operations)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc