| 55 | | reader = WFSCapabilitiesReader(self.version) |
|---|
| 56 | | self._capabilities = ServiceMetadata(reader.readString(xml)) |
|---|
| 57 | | |
|---|
| 58 | | def _getcapproperty(self): |
|---|
| 59 | | if not self._capabilities: |
|---|
| 60 | | reader = WFSCapabilitiesReader(self.version) |
|---|
| 61 | | self._capabilities = ServiceMetadata(reader.read(self.url)) |
|---|
| 62 | | return self._capabilities |
|---|
| 63 | | capabilities = property(_getcapproperty, None) |
|---|
| 64 | | |
|---|
| | 64 | self._capabilities = reader.readString(xml) |
|---|
| | 65 | else: |
|---|
| | 66 | self._capabilities = reader.read(self.url) |
|---|
| | 67 | self._buildMetadata() |
|---|
| | 68 | |
|---|
| | 69 | def _buildMetadata(self): |
|---|
| | 70 | '''set up capabilities metadata objects: ''' |
|---|
| | 71 | |
|---|
| | 72 | #serviceIdentification metadata |
|---|
| | 73 | serviceelem=self._capabilities.find(nspath('Service')) |
|---|
| | 74 | self.identification=ServiceIdentification(serviceelem, self.version) |
|---|
| | 75 | |
|---|
| | 76 | #serviceProvider metadata |
|---|
| | 77 | self.provider=ServiceProvider(serviceelem) |
|---|
| | 78 | |
|---|
| | 79 | #serviceOperations metadata |
|---|
| | 80 | self.operations=[] |
|---|
| | 81 | for elem in self._capabilities.find(nspath('Capability/Request')).getchildren(): |
|---|
| | 82 | self.operations.append(OperationMetadata(elem)) |
|---|
| | 83 | |
|---|
| | 84 | #serviceContents metadata: our assumption is that services use a top-level |
|---|
| | 85 | #layer as a metadata organizer, nothing more. |
|---|
| | 86 | |
|---|
| | 87 | self.contents={} |
|---|
| | 88 | featuretypelist=self._capabilities.find(nspath('FeatureTypeList')) |
|---|
| | 89 | features = self._capabilities.findall(nspath('FeatureTypeList/FeatureType')) |
|---|
| | 90 | for feature in features: |
|---|
| | 91 | cm=ContentMetadata(feature, featuretypelist) |
|---|
| | 92 | self.contents[cm.id]=cm |
|---|
| | 93 | |
|---|
| | 94 | #exceptions |
|---|
| | 95 | self.exceptions = [f.text for f \ |
|---|
| | 96 | in self._capabilities.findall('Capability/Exception/Format')] |
|---|
| | 97 | |
|---|
| 151 | | |
|---|
| 152 | | class ServiceMetadata(object): |
|---|
| 153 | | """Abstraction for WFS metadata. |
|---|
| 154 | | |
|---|
| 155 | | Implements IServiceMetadata. |
|---|
| 156 | | """ |
|---|
| 157 | | |
|---|
| 158 | | def __init__(self, infoset): |
|---|
| 159 | | """Initialize from an element tree.""" |
|---|
| 160 | | self._root = infoset.getRoot() |
|---|
| 161 | | # properties |
|---|
| 162 | | self.service = self._root.find(nspath('Service/Name')).text |
|---|
| 163 | | self.title = self._root.find(nspath('Service/Title')).text |
|---|
| 164 | | self.abstract = self._root.find(nspath('Service/Abstract')).text |
|---|
| 165 | | self.link = self._root.find(nspath('Service/OnlineResource')).text |
|---|
| 166 | | |
|---|
| 167 | | # operations [] |
|---|
| 168 | | self.operations = [] |
|---|
| 169 | | for elem in self._root.findall(nspath('Capability/Request/*')): |
|---|
| 170 | | self.operations.append(OperationMetadata(elem)) |
|---|
| 171 | | |
|---|
| 172 | | # contents: our assumption is that services use a top-level layer |
|---|
| 173 | | # as a metadata organizer, nothing more. |
|---|
| 174 | | self.contents = [] |
|---|
| 175 | | top = self._root.find(nspath('FeatureTypeList')) |
|---|
| 176 | | for elem in self._root.findall(nspath('FeatureTypeList/FeatureType')): |
|---|
| 177 | | self.contents.append(ContentMetadata(elem, top)) |
|---|
| 178 | | |
|---|
| 179 | | # keywords |
|---|
| 180 | | self.keywords = [] |
|---|
| 181 | | |
|---|
| 182 | | self.provider = ContactMetadata(self._root.find('Service/ContactInformation')) |
|---|
| 183 | | |
|---|
| 184 | | def getContentByName(self, name): |
|---|
| 185 | | """Return a named content item.""" |
|---|
| 186 | | for item in self.contents: |
|---|
| 187 | | if item.name == name: |
|---|
| 188 | | return item |
|---|
| 189 | | raise KeyError, "No content named %s" % name |
|---|
| 190 | | |
|---|
| | 198 | class ServiceIdentification(object): |
|---|
| | 199 | ''' Implements IServiceIdentificationMetadata ''' |
|---|
| | 200 | |
|---|
| | 201 | def __init__(self, infoset, version): |
|---|
| | 202 | self._root=infoset |
|---|
| | 203 | self.type = self._root.find(nspath('Name')).text |
|---|
| | 204 | self.version = version |
|---|
| | 205 | self.title = self._root.find(nspath('Title')).text |
|---|
| | 206 | abstract = self._root.find(nspath('Abstract')) |
|---|
| | 207 | if abstract is not None: |
|---|
| | 208 | self.abstract = abstract.text |
|---|
| | 209 | else: |
|---|
| | 210 | self.abstract = None |
|---|
| | 211 | self.keywords = [f.text for f in self._root.findall(nspath('Keywords'))] |
|---|
| | 212 | accessconstraints=self._root.find(nspath('AccessConstraints')) |
|---|
| | 213 | if accessconstraints is not None: |
|---|
| | 214 | self.accessconstraints = accessconstraints.text |
|---|
| | 215 | else: |
|---|
| | 216 | accessconstraints=None |
|---|
| | 217 | fees = self._root.find(nspath('Fees')) |
|---|
| | 218 | if fees is not None: |
|---|
| | 219 | self.fees = fees.text |
|---|
| | 220 | |
|---|
| | 221 | class ServiceProvider(object): |
|---|
| | 222 | ''' Implements IServiceProviderMetatdata ''' |
|---|
| | 223 | def __init__(self, infoset): |
|---|
| | 224 | self._root=infoset |
|---|
| | 225 | name=self._root.find(nspath('ContactInformation/ContactPersonPrimary/ContactOrganization')) |
|---|
| | 226 | if name is not None: |
|---|
| | 227 | self.name=name.text |
|---|
| | 228 | else: |
|---|
| | 229 | self.name=None |
|---|
| | 230 | self.url=self._root.find(nspath('OnlineResource')).attrib.get('{http://www.w3.org/1999/xlink}href', '') |
|---|
| | 231 | if self.url == '': |
|---|
| | 232 | self.url=self._root.find(nspath('OnlineResource')).text |
|---|
| | 233 | #contact metadata |
|---|
| | 234 | contact = self._root.find('ContactInformation') |
|---|
| | 235 | ## sometimes there is a contact block that is empty, so make |
|---|
| | 236 | ## sure there are children to parse |
|---|
| | 237 | if contact is not None and contact.getchildren(): |
|---|
| | 238 | self.contact = ContactMetadata(contact) |
|---|
| | 239 | else: |
|---|
| | 240 | self.contact = None |
|---|
| | 241 | |
|---|
| | 242 | class ContactMetadata: |
|---|
| | 243 | """Abstraction for contact details advertised in GetCapabilities. |
|---|
| | 244 | Not fully tested due to lack of Contact info in test capabilities doc. |
|---|
| | 245 | """ |
|---|
| | 246 | def __init__(self, elem): |
|---|
| | 247 | self.name = elem.find(nspath('ContactPersonPrimary/ContactPerson')).text |
|---|
| | 248 | self.email = elem.find(nspath('ContactElectronicMailAddress')).text |
|---|
| | 249 | |
|---|
| | 250 | self.address = self.city = self.region = None |
|---|
| | 251 | self.postcode = self.country = None |
|---|
| | 252 | |
|---|
| | 253 | address = elem.find(nspath('ContactAddress')) |
|---|
| | 254 | if address is not None: |
|---|
| | 255 | street = address.find(nspath('Address')) |
|---|
| | 256 | if street is not None: self.address = street.text |
|---|
| | 257 | |
|---|
| | 258 | city = address.find(nspath('City')) |
|---|
| | 259 | if city is not None: self.city = city.text |
|---|
| | 260 | |
|---|
| | 261 | region = address.find(nspath('StateOrProvince')) |
|---|
| | 262 | if region is not None: self.region = region.text |
|---|
| | 263 | |
|---|
| | 264 | postcode = address.find(nspath('PostCode')) |
|---|
| | 265 | if postcode is not None: self.postcode = postcode.text |
|---|
| | 266 | |
|---|
| | 267 | country = address.find(nspath('Country')) |
|---|
| | 268 | if country is not None: self.country = country.text |
|---|
| | 269 | |
|---|
| | 270 | organization = elem.find(nspath('ContactPersonPrimary/ContactOrganization')) |
|---|
| | 271 | if organization is not None: self.organization = organization.text |
|---|
| | 272 | else:self.organization = None |
|---|
| | 273 | |
|---|
| | 274 | position = elem.find(nspath('ContactPosition')) |
|---|
| | 275 | if position is not None: self.position = position.text |
|---|
| | 276 | else: self.position = None |
|---|
| | 277 | |
|---|
| 251 | | class ContactMetadata: |
|---|
| 252 | | """Abstraction for contact details advertised in GetCapabilities. |
|---|
| 253 | | """ |
|---|
| 254 | | # TODO: refactor with class from wfs |
|---|
| 255 | | |
|---|
| 256 | | def __init__(self, elem): |
|---|
| 257 | | self.name = None |
|---|
| 258 | | self.email = None |
|---|
| 259 | | |
|---|
| 260 | | if elem: |
|---|
| 261 | | self.name = elem.find('ContactPersonPrimary/ContactPerson').text |
|---|
| 262 | | self.organization = elem.find('ContactPersonPrimary/ContactOrganization').text |
|---|
| 263 | | address = elem.find('ContactAddress') |
|---|
| 264 | | if address is not None: |
|---|
| 265 | | try: |
|---|
| 266 | | self.address = address.find('Address').text |
|---|
| 267 | | self.city = address.find('City').text |
|---|
| 268 | | self.region = address.find('StateOrProvince').text |
|---|
| 269 | | self.postcode = address.find('Postcode').text |
|---|
| 270 | | self.country = address.find('Country').text |
|---|
| 271 | | except: pass |
|---|
| 272 | | self.email = elem.find('ContactElectronicMailAddress').text |
|---|
| 273 | | |
|---|
| 274 | | |
|---|
| 275 | | class WFSCapabilitiesInfoset(object): |
|---|
| 276 | | """High-level container for WFS Capabilities based on lxml.etree |
|---|
| 277 | | """ |
|---|
| 278 | | |
|---|
| 279 | | def __init__(self, infoset): |
|---|
| 280 | | """Initialize""" |
|---|
| 281 | | self._infoset = infoset |
|---|
| 282 | | |
|---|
| 283 | | # |
|---|
| 284 | | # XML Node accessors |
|---|
| 285 | | # |
|---|
| 286 | | |
|---|
| 287 | | def getRoot(self): |
|---|
| 288 | | """ |
|---|
| 289 | | Returns the root node of the capabilities document. |
|---|
| 290 | | """ |
|---|
| 291 | | return self._infoset |
|---|
| 292 | | |
|---|
| 293 | | def getServiceNode(self): |
|---|
| 294 | | """ |
|---|
| 295 | | Returns the <Service> node of the capabilities document. |
|---|
| 296 | | """ |
|---|
| 297 | | return self.getRoot().find(nspath('Service')) |
|---|
| 298 | | |
|---|
| 299 | | def getCapabilitiesNode(self): |
|---|
| 300 | | """ |
|---|
| 301 | | Returns the <Capability> node of the capabilities document. |
|---|
| 302 | | """ |
|---|
| 303 | | return self.getRoot().find(nspath('Capability')) |
|---|
| 304 | | |
|---|
| 305 | | def getFeatureTypeNode(self): |
|---|
| 306 | | """ |
|---|
| 307 | | Returns the <FeatureTypeList> node of the capabilities document. |
|---|
| 308 | | """ |
|---|
| 309 | | return self.getRoot().find(nspath('FeatureTypeList')) |
|---|
| 310 | | |
|---|
| 311 | | def getFilterNode(self): |
|---|
| 312 | | """ |
|---|
| 313 | | Returns the <Filter_Capabilities> node of the capabilities document. |
|---|
| 314 | | """ |
|---|
| 315 | | return self.getRoot().find(nspath('Filter_Capabilities')) |
|---|
| 316 | | |
|---|
| 317 | | # |
|---|
| 318 | | # Info accessors |
|---|
| 319 | | # |
|---|
| 320 | | |
|---|
| 321 | | def getServiceInfo(self): |
|---|
| 322 | | """ |
|---|
| 323 | | Returns the WFS Service information packed in a dictionary. |
|---|
| 324 | | """ |
|---|
| 325 | | service = self.getServiceNode() |
|---|
| 326 | | info = {} |
|---|
| 327 | | for tag in ('Name', 'Title', 'Abstract', 'Keyword', 'OnlineResource', |
|---|
| 328 | | 'Fees', 'AccessConstraints'): |
|---|
| 329 | | info[tag.lower()] = service.findtext(nspath(tag)) |
|---|
| 330 | | return info |
|---|
| 331 | | |
|---|
| 332 | | def getCapabilityInfo(self): |
|---|
| 333 | | """ |
|---|
| 334 | | Returns the WFS Capability information packed in a dictionary. |
|---|
| 335 | | """ |
|---|
| 336 | | # Simplify the resource URLs, favoring GET over POST. |
|---|
| 337 | | # Assume GML2. |
|---|
| 338 | | capabilities = self.getCapabilitiesNode() |
|---|
| 339 | | info = {} |
|---|
| 340 | | |
|---|
| 341 | | for key, path_id in [('capabilities', 'GetCapabilities'), |
|---|
| 342 | | ('description', 'DescribeFeatureType'), |
|---|
| 343 | | ('features', 'GetFeature')]: |
|---|
| 344 | | get_path = nspath('Request/%s/DCPType/HTTP/Get' % path_id) |
|---|
| 345 | | post_path = nspath('Request/%s/DCPType/HTTP/Post' % path_id) |
|---|
| 346 | | |
|---|
| 347 | | node = capabilities.find(get_path) or capabilities.find(post_path) |
|---|
| 348 | | info[key] = node.get('onlineResource', None) |
|---|
| 349 | | return info |
|---|
| 350 | | |
|---|
| 351 | | def getFeatureTypeInfo(self): |
|---|
| 352 | | """ |
|---|
| 353 | | Returns the WFS Feature type information as a list of dictionaries. |
|---|
| 354 | | """ |
|---|
| 355 | | # Assume XMLSchema is used as the schema description language. |
|---|
| 356 | | info = [] |
|---|
| 357 | | for featuretype in self.getFeatureTypeNode().getiterator(nspath('FeatureType')): |
|---|
| 358 | | entry = {} |
|---|
| 359 | | # Loop over simple text nodes |
|---|
| 360 | | for tag in ('Name', 'Title', 'Abstract', 'SRS'): |
|---|
| 361 | | entry[tag.lower()] = featuretype.findtext(nspath(tag)) |
|---|
| 362 | | |
|---|
| 363 | | # LatLongBoundingBox |
|---|
| 364 | | entry['latlongboundingbox'] = [] |
|---|
| 365 | | for latlong in featuretype.findall(nspath('LatLongBoundingBox')): |
|---|
| 366 | | entry['latlongboundingbox'].append('%s,%s,%s,%s' % (latlong.get('minx'), |
|---|
| 367 | | latlong.get('miny'), |
|---|
| 368 | | latlong.get('maxx'), |
|---|
| 369 | | latlong.get('maxy'))) |
|---|
| 370 | | |
|---|
| 371 | | # MetadataURL |
|---|
| 372 | | entry['metadataurl'] = [] |
|---|
| 373 | | for metadataurl in featuretype.findall(nspath('MetadataURL')): |
|---|
| 374 | | entry['metadataurl'].append(metadataurl.text) |
|---|
| 375 | | |
|---|
| 376 | | info.append(entry) |
|---|
| 377 | | return info |
|---|
| 378 | | |
|---|
| 379 | | |
|---|