Changeset 1095

Show
Ignore:
Timestamp:
05/09/08 07:16:18
Author:
domlowe
Message:

Merging branches/wcstemp into trunk, and combining with authorization code

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • OWSLib/trunk/owslib/interfaces.py

    r954 r1095  
    1 # Do not import zope.interfaces 
     1 
     2# Follows the 4 aspects of service metadata 
     3 
     4class IServiceIdentificationMetadata: 
     5    """OO-interface to service identification metadata. 
     6    """ 
     7 
     8    type = property("""Service name (string): "WMS", "WFS", or "WCS".""") 
     9    version = property("""Version of service protocol (string).""") 
     10    title = property("""Human-targeted title of service (string).""") 
     11    abstract = property("""Text describing the service (string).""") 
     12    keywords = property("""Keyword list (list).""") 
     13    accessconstraints = property("""Explanation of access constraints associated with service (string).""") 
     14    fees = property("""Explanation of fees associated with service (string).""") 
     15     
     16 
     17class IServiceProviderMetadata: 
     18    """OO-interface to service provider metadata. 
     19    """ 
     20 
     21    name = property("""Provider's name (string).""") 
     22    url = property("""URL for provider's web site (string).""") 
     23    contact = property("""How to contact the service provider (string).""") 
    224 
    325 
    4 class IService: 
     26class IServiceOperations: 
     27    """OO-interface to service operations metadata. 
     28    """ 
     29 
     30    operations = property("""List of operation descriptors (list). These must implement IOperationMetadata (below).""") 
     31    exceptions = property("""List of exception formats (list).""") 
     32 
     33 
     34class IServiceContents: 
     35    """OO-interface to service contents metadata. 
     36    """ 
     37 
     38    contents = property("""List of content descriptors (list). These must implement IServiceContent (below).""") 
     39 
     40 
     41# IServiceMetadata aggregates the 4 aspects above 
     42 
     43class IServiceMetadata(IServiceOperations, IServiceContents): 
     44    """OWS Metadata. 
     45 
     46    operations and contents properties are inherited. 
     47    """ 
     48 
     49    identification = property("""Object that implements IServiceIdentificationMetadata.""") 
     50    provider = property("""Object that implements IServiceProviderMetadata.""") 
     51 
     52 
     53# A Service has an online resource URL as well as metadata collections 
     54 
     55class IService(IServiceMetadata): 
    556    """The OGC Web Service interface. 
    657    """ 
    758 
    859    url = property("""Online resource URL (string)""") 
    9     # XXX: here or in capabilities? 
    10     version = property("""Protocol version (string)""") 
    11     capabilities = property("""Implementation of IServiceMetadata (object)""") 
    1260 
     61 
     62# 3 specific service types are described below: WMS, WFS, and WCS 
    1363 
    1464class IWebMapService(IService): 
     
    4898    pass 
    4999 
    50  
    51 # Follows the 4 aspects of service metadata 
    52  
    53 class IServiceIdentificationMetadata: 
    54     """OO-interface to service identification metadata. 
    55     """ 
    56  
    57     service = property("""Service name (string): "WMS", "WFS", or "WCS".""") 
    58     # XXX: here or in service root? 
    59     version = property("""Version of service protocol (string).""") 
    60     title = property("""Human-targeted title of service (string).""") 
    61     abstract = property("""Text describing the service (string).""") 
    62     keywords = property("""Keyword list (list).""") 
    63     rights = property("""Explanation of rights associated with service (string).""") 
    64      
    65  
    66 class IServiceProviderMetadata: 
    67     """OO-interface to service provider metadata. 
    68     """ 
    69  
    70     provider = property("""Provider's name (string).""") 
    71     url = property("""URL for provider's web site (string).""") 
    72     contact = property("""How to contact the service provider (string).""") 
    73  
    74  
    75 class IServiceOperationsMetadata: 
    76     """OO-interface to service operations metadata. 
    77     """ 
    78  
    79     operations = property("""List of operation descriptors (list). These must implement IOperationMetadata (below).""") 
    80     exceptions = property("""List of exception formats (list).""") 
    81  
    82  
    83 class IServiceContentsMetadata: 
    84     """OO-interface to service contents metadata. 
    85     """ 
    86  
    87     contents = property("""List of content descriptors (list). These must implement IServiceContent (below).""") 
    88  
    89  
    90 # IServiceMetadata aggregates the 4 aspects above 
    91  
    92 class IServiceMetadata( 
    93     IServiceIdentificationMetadata, IServiceProviderMetadata, 
    94     IServiceOperationsMetadata, IServiceContentsMetadata 
    95     ): 
    96     """OWS Metadata. 
    97     """ 
    98100 
    99101 
     
    145147    email : string 
    146148    """ 
    147  
    148  
    149  
  • OWSLib/trunk/owslib/wfs.py

    r660 r1095  
    4747    """ 
    4848     
     49    def __getitem__(self,name): 
     50        ''' check contents dictionary to allow dict like access to service layers''' 
     51        if name in self.__getattribute__('contents').keys(): 
     52            return self.__getattribute__('contents')[name] 
     53        else: 
     54            raise KeyError, "No content named %s" % name 
     55     
     56     
    4957    def __init__(self, url, version='1.0.0', xml=None): 
    5058        """Initialize.""" 
     
    5260        self.version = version 
    5361        self._capabilities = None 
     62        reader = WFSCapabilitiesReader(self.version) 
    5463        if xml: 
    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       
    6598    def getcapabilities(self): 
    6699        """Request and return capabilities document from the WFS as a  
    67         file-like object.""" 
     100        file-like object. 
     101        NOTE: this is effectively redundant now""" 
    68102        reader = WFSCapabilitiesReader(self.version) 
    69103        return urlopen(reader.capabilities_url(self.url)) 
     104     
     105    def items(self): 
     106        '''supports dict-like items() access''' 
     107        items=[] 
     108        for item in self.contents: 
     109            items.append((item,self.contents[item])) 
     110        return items 
    70111     
    71112    def getfeature(self, typename=None, filter=None, bbox=None, featureid=None, 
     
    99140        3) featureid (direct access to known features) 
    100141        """ 
    101         md = self.capabilities 
    102         base_url = md.getOperationByName('{http://www.opengis.net/wfs}GetFeature').methods[method]['url'] 
     142        base_url = self.getOperationByName('{http://www.opengis.net/wfs}GetFeature').methods[method]['url'] 
    103143        request = {'service': 'WFS', 'version': self.version, 'request': 'GetFeature'} 
    104144         
     
    149189            return u 
    150190 
    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  
    191191    def getOperationByName(self, name): 
    192192        """Return a named content item.""" 
     
    196196        raise KeyError, "No operation named %s" % name 
    197197 
     198class 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              
     221class 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 
     242class 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        name = elem.find(nspath('ContactPersonPrimary/ContactPerson')) 
     248        if name is not None: 
     249            self.name=name.text 
     250        else: 
     251            self.name=None 
     252        email = elem.find(nspath('ContactElectronicMailAddress')) 
     253        if email is not None: 
     254            self.email=email.text 
     255        else: 
     256            self.email=None 
     257 
     258        self.address = self.city = self.region = None 
     259        self.postcode = self.country = None 
     260 
     261        address = elem.find(nspath('ContactAddress')) 
     262        if address is not None: 
     263            street = address.find(nspath('Address')) 
     264            if street is not None: self.address = street.text 
     265 
     266            city = address.find(nspath('City')) 
     267            if city is not None: self.city = city.text 
     268 
     269            region = address.find(nspath('StateOrProvince')) 
     270            if region is not None: self.region = region.text 
     271 
     272            postcode = address.find(nspath('PostCode')) 
     273            if postcode is not None: self.postcode = postcode.text 
     274 
     275            country = address.find(nspath('Country')) 
     276            if country is not None: self.country = country.text 
     277 
     278        organization = elem.find(nspath('ContactPersonPrimary/ContactOrganization')) 
     279        if organization is not None: self.organization = organization.text 
     280        else:self.organization = None 
     281 
     282        position = elem.find(nspath('ContactPosition')) 
     283        if position is not None: self.position = position.text 
     284        else: self.position = None 
     285 
    198286 
    199287class ContentMetadata: 
     
    205293    def __init__(self, elem, parent): 
    206294        """.""" 
    207         self.name = elem.find(nspath('Name')).text 
     295        self.id = elem.find(nspath('Name')).text 
    208296        self.title = elem.find(nspath('Title')).text 
    209297        # bboxes 
     
    249337 
    250338 
    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  
    380339class WFSCapabilitiesReader(object): 
    381340    """Read and parse capabilities document into a lxml.etree infoset 
     
    417376        request = self.capabilities_url(url) 
    418377        u = urlopen(request) 
    419         return WFSCapabilitiesInfoset(etree.fromstring(u.read())) 
     378        return etree.fromstring(u.read()) 
    420379 
    421380    def readString(self, st): 
     
    427386        if not isinstance(st, str): 
    428387            raise ValueError("String must be of type string, not %s" % type(st)) 
    429         return WFSCapabilitiesInfoset(etree.fromstring(st)
    430      
     388        return etree.fromstring(st
     389     
  • OWSLib/trunk/owslib/wms.py

    r1092 r1095  
    4141    """ 
    4242     
     43    def __getitem__(self,name): 
     44        ''' check contents dictionary to allow dict like access to service layers''' 
     45        if name in self.__getattribute__('contents').keys(): 
     46            return self.__getattribute__('contents')[name] 
     47        else: 
     48            raise KeyError, "No content named %s" % name 
     49 
     50     
    4351    def __init__(self, url, version='1.1.1', xml=None,  
    44                 username=None, password=None 
    45                 ): 
     52                username=None, password=None 
     53                ): 
    4654        """Initialize.""" 
    4755        self.url = url 
     
    5159        self._capabilities = None 
    5260        self._open = urlopen 
    53  
     61         
    5462        if self.username and self.password: 
    5563            # Provide login information in order to use the WMS server 
     
    6169            opener = build_opener(auth_handler) 
    6270            self._open = opener.open 
    63  
    64         # initialize from saved capability document 
    65         elif xml: 
    6671            reader = WMSCapabilitiesReader( 
    6772                self.version, url=self.url, un=self.username, pw=self.password 
    6873                ) 
    69             self._capabilities = ServiceMetadata(reader.readString(xml)) 
     74            self._capabilities = reader.readString(self.url) 
     75        else: 
     76            reader = WMSCapabilitiesReader(self.version) 
     77            if xml: 
     78                #read from stored xml 
     79                self._capabilities = reader.readString(xml) 
     80            else: 
     81                #read from non-password protected server 
     82                self._capabilities = reader.read(self.url) 
     83                 
     84        
     85        #build metadata objects 
     86        self._buildMetadata() 
    7087 
    7188    def _getcapproperty(self): 
     
    7794        return self._capabilities 
    7895    capabilities = property(_getcapproperty, None) 
     96 
     97    def _buildMetadata(self):          
     98        ''' set up capabilities metadata objects ''' 
     99         
     100        #serviceIdentification metadata 
     101        serviceelem=self._capabilities.find('Service/') 
     102        self.identification=ServiceIdentification(serviceelem, self.version)    
     103         
     104        #serviceProvider metadata 
     105        self.provider=ServiceProvider(serviceelem)    
    79106             
     107        #serviceOperations metadata  
     108        self.operations=[] 
     109        for elem in self._capabilities.find('Capability/Request').getchildren(): 
     110            self.operations.append(OperationMetadata(elem)) 
     111           
     112        #serviceContents metadata: our assumption is that services use a top-level  
     113        #layer as a metadata organizer, nothing more. 
     114        self.contents={} 
     115        caps = self._capabilities.find('Capability') 
     116        for elem in caps.findall('Layer'): 
     117            cm=ContentMetadata(elem) 
     118            self.contents[cm.id]=cm        
     119            for subelem in elem.findall('Layer'): 
     120                subcm=ContentMetadata(subelem, cm) 
     121                self.contents[subcm.id]=subcm  
     122         
     123        #exceptions 
     124        self.exceptions = [f.text for f \ 
     125                in self._capabilities.findall('Capability/Exception/Format')] 
     126             
     127             
     128    def items(self): 
     129        '''supports dict-like items() access''' 
     130        items=[] 
     131        for item in self.contents: 
     132            items.append((item,self.contents[item])) 
     133        return items 
     134     
    80135    def getcapabilities(self): 
    81136        """Request and return capabilities document from the WMS as a  
    82         file-like object.""" 
     137        file-like object. 
     138        NOTE: this is effectively redundant now""" 
    83139         
    84140        reader = WMSCapabilitiesReader( 
     
    139195 
    140196        """         
    141         md = self.capabilities 
    142         base_url = md.getOperationByName('GetMap').methods[method]['url'] 
     197        base_url = self.getOperationByName('GetMap').methods[method]['url'] 
    143198        request = {'version': self.version, 'request': 'GetMap'} 
    144199         
     
    182237    def getfeatureinfo(self): 
    183238        raise NotImplementedError 
    184          
    185          
    186 class ServiceMetadata(object): 
    187     """Abstraction for WMS metadata. 
    188      
    189     Implements IServiceMetadata. 
    190     """ 
    191  
    192     def __init__(self, infoset): 
    193         """Initialize from an element tree.""" 
    194         self._root = infoset.getroot() 
    195         # properties 
    196         self.service = self._root.find('Service/Name').text 
    197         self.title = self._root.find('Service/Title').text 
    198         abstract = self._root.find('Service/Abstract') 
     239     
     240    def getOperationByName(self, name):  
     241        """Return a named content item.""" 
     242        for item in self.operations: 
     243            if item.name == name: 
     244                return item 
     245        raise KeyError, "No operation named %s" % name 
     246     
     247class ServiceIdentification(object): 
     248    ''' Implements IServiceIdentificationMetadata ''' 
     249     
     250    def __init__(self, infoset, version): 
     251        self._root=infoset 
     252        self.type = self._root.find('Name').text 
     253        self.version = version 
     254        self.title = self._root.find('Title').text 
     255        abstract = self._root.find('Abstract') 
    199256        if abstract is not None: 
    200                 self.abstract = self._root.find('Service/Abstract').text 
     257                self.abstract = abstract.text 
    201258        else: 
    202259                self.abstract = None 
    203         self.link = self._root.find('Service/OnlineResource').attrib.get('{http://www.w3.org/1999/xlink}href', '') 
    204          
    205         # operations [] 
    206         self.operations = [] 
    207         for elem in self._root.findall('Capability/Request/*'): 
    208             self.operations.append(OperationMetadata(elem)) 
    209  
    210         # exceptions 
    211         self.exceptions = [f.text for f \ 
    212                 in self._root.findall('Capability/Exception/Format')] 
    213          
    214         # contents: our assumption is that services use a top-level layer 
    215         # as a metadata organizer, nothing more. 
    216         caps = self._root.find('Capability') 
    217         self.layers = [] 
    218         for elem in caps.findall('Layer'): 
    219                 self.layers.append(ContentMetadata(elem)) 
    220  
    221         # keywords 
    222         self.keywords = [f.text for f in self._root.findall('Service/KeywordList/Keyword')] 
    223          
    224         # contact person 
    225         contact = self._root.find('Service/ContactInformation') 
     260        self.keywords = [f.text for f in self._root.findall('KeywordList/Keyword')] 
     261        accessconstraints=self._root.find('AccessConstraints') 
     262        if accessconstraints is not None: 
     263            self.accessconstraints = accessconstraints.text 
     264        fees = self._root.find('Fees') 
     265        if fees is not None: 
     266            self.fees = fees.text 
     267              
     268class ServiceProvider(object): 
     269    ''' Implements IServiceProviderMetatdata ''' 
     270    def __init__(self, infoset): 
     271        self._root=infoset 
     272        name=self._root.find('ContactInformation/ContactPersonPrimary/ContactOrganization') 
     273        if name is not None: 
     274            self.name=name.text 
     275        else: 
     276            self.name=None 
     277        self.url=self._root.find('OnlineResource').attrib.get('{http://www.w3.org/1999/xlink}href', '') 
     278        #contact metadata 
     279        contact = self._root.find('ContactInformation') 
    226280        ## sometimes there is a contact block that is empty, so make 
    227281        ## sure there are children to parse 
    228         if contact is not None and contact.getchildren()
    229             self.provider = ContactMetadata(contact) 
     282        if contact is not None and contact.getchildren() != []
     283            self.contact = ContactMetadata(contact) 
    230284        else: 
    231             self.provider = None 
    232  
    233     @property 
    234     def contents(self): 
    235         """backwards compatible flat list of contents""" 
    236         return list(self._rcontents(self)) 
    237     def _rcontents(self, layer): 
    238         for l in layer.layers: 
    239             yield l 
    240             for l in self._rcontents(l): 
    241                 yield l 
     285            self.contact = None 
    242286             
    243287    def getContentByName(self, name): 
     
    255299        raise KeyError, "No operation named %s" % name 
    256300 
    257  
    258301class ContentMetadata: 
    259302        """ 
    260         Abstraction for WMS metadata. 
    261  
    262         Implements IMetadata. 
     303        Abstraction for WMS layer metadata. 
     304 
     305        Implements IContentMetadata. 
    263306        """ 
    264307        def __init__(self, elem, parent=None): 
     
    272315                        else: 
    273316                                setattr(self, key.lower(), None) 
    274  
     317                self.id=self.name #conform to new interface 
    275318                # bboxes 
    276319                b = elem.find('BoundingBox') 
    277                 if b is not None: 
     320                self.boundingBox = None 
     321                if b is not None: 
    278322                        self.boundingBox = ( 
    279323                                float(b.attrib['minx']), 
     
    284328                        ) 
    285329                elif self.parent: 
     330                    if hasattr(self.parent, 'boundingBox'): 
    286331                        self.boundingBox = self.parent.boundingBox 
    287                 else: 
    288                         self.boundingBox = None 
    289  
     332                     
    290333                b = elem.find('LatLonBoundingBox') 
    291334                if b is not None: 
     
    300343                else: 
    301344                        self.boundingBoxWGS84 = None 
    302  
    303345                # crs options 
    304346                self.crsOptions = [] 
     
    311353                                        self.crsOptions.append(srs) 
    312354                elif self.parent: 
    313                        self.crsOptions = self.parent.crsOptions 
     355                        self.crsOptions = self.parent.crsOptions 
    314356                else: 
    315357                        raise ValueError('%s no SRS available!?' % (elem,)) 
     
    341383 
    342384class OperationMetadata: 
    343     """Abstraction for WMS metadata. 
    344      
    345     Implements IMetadata. 
     385    """Abstraction for WMS OperationMetadata. 
     386     
     387    Implements IOperationMetadata. 
    346388    """ 
    347389    def __init__(self, elem): 
     
    360402        """ 
    361403        def __init__(self, elem): 
    362                 self.name = elem.find('ContactPersonPrimary/ContactPerson').text 
    363                 self.email = elem.find('ContactElectronicMailAddress').text  
    364  
     404                name = elem.find('ContactPersonPrimary/ContactPerson') 
     405                if name is not None: 
     406                    self.name=name.text 
     407                else: 
     408                    self.name=None 
     409                email = elem.find('ContactElectronicMailAddress') 
     410                if email is not None: 
     411                    self.email=email.text 
     412                else: 
     413                    self.email=None 
    365414                self.address = self.city = self.region = None 
    366415                self.postcode = self.country = None 
     
    392441 
    393442 
    394 class WMSCapabilitiesInfoset: 
    395     """High-level container for WMS Capabilities based on lxml.etree 
    396     """ 
    397  
    398     def __init__(self, infoset): 
    399         """Initialize""" 
    400         self._infoset = infoset 
    401  
    402     def getroot(self): 
    403         return self._infoset 
    404  
    405     def getservice(self): 
    406         return self._infoset.find('Service') 
    407  
    408     def servicename(self): 
    409         e_service = self.getservice() 
    410         return e_service.find('Name').text 
    411  
    412     def servicetitle(self): 
    413         e_service = self.getservice() 
    414         return e_service.find('Title').text 
    415  
    416     def getmapformats(self): 
    417         e_getmap = self._infoset.find('Capability/Request/GetMap') 
    418         formats = () 
    419         for f in e_getmap.getiterator('Format'): 
    420             formats = formats + (f.text,) 
    421         return formats 
    422  
    423     def layersrs(self): 
    424         e_layer = self._infoset.find('Capability/Layer') 
    425         srs = () 
    426         for s in e_layer.getiterator('SRS'): 
    427             srs = srs + (s.text,) 
    428         return srs 
    429  
    430     def layernames(self): 
    431         names = () 
    432         for n in self._infoset.findall('Capability/Layer/Layer/Name'): 
    433             names = names + (n.text,) 
    434         return names 
    435  
    436     def layertitles(self): 
    437         titles = () 
    438         for n in self._infoset.findall('Capability/Layer/Layer/Title'): 
    439             titles = titles + (n.text,) 
    440         return titles 
    441  
    442     def getLayerInfo(self): 
    443         info = {} 
    444         for layer in self._infoset.findall('Capability/Layer/Layer'): 
    445             if layer.findall('Title'): 
    446                 info[layer.findall('Title')[0].text] = layer.findall('Style') 
    447         return info 
    448  
    449     def bounds(self, name): 
    450         """Returns the bounds of the specified layer as a tuple. 
    451  
    452         Like (minx, miny, maxx, maxy, epsg) 
    453         """ 
    454         top_layer = self._infoset.find('Capability/Layer') 
    455         for layer in top_layer.findall('Layer'): 
    456             n = layer.find('Name') 
    457             if n.text == name: 
    458                 # First check for a BoundingBox 
    459                 b = layer.find('BoundingBox') 
    460                 if b is not None: 
    461                     return (float(b.attrib['minx']), float(b.attrib['miny']), 
    462                             float(b.attrib['maxx']), float(b.attrib['maxy']), 
    463                             b.attrib['SRS']) 
    464                 else: 
    465                     b = layer.find('LatLonBoundingBox') 
    466                     #import pdb; pdb.set_trace() 
    467                     if b is not None: 
    468                         return (float(b.attrib['minx']),float(b.attrib['miny']), 
    469                                 float(b.attrib['maxx']),float(b.attrib['maxy']), 
    470                                 'EPSG:4326') 
    471                 # Look at the top level layer 
    472                 b = top_layer.find('BoundingBox') 
    473                 if b is not None: 
    474                     return (float(b.attrib['minx']), float(b.attrib['miny']), 
    475                             float(b.attrib['maxx']), float(b.attrib['maxy']), 
    476                             b.attrib['SRS']) 
    477                 else: 
    478                     b = top_layer.find('LatLonBoundingBox') 
    479                     if b is not None: 
    480                         return (float(b.attrib['minx']),float(b.attrib['miny']), 
    481                                 float(b.attrib['maxx']),float(b.attrib['maxy']), 
    482                                 'EPSG:4326') 
    483         # If we haven't returned a bbox, raise an exception 
    484         raise CapabilitiesError, "No bounding box specified for layer %s" % name 
    485                  
    486443         
    487444class WMSCapabilitiesReader: 
     
    529486    def read(self, service_url): 
    530487        """Get and parse a WMS capabilities document, returning an 
    531         instance of WMSCapabilitiesInfoset 
     488        elementtree instance 
    532489 
    533490        service_url is the base url, to which is appended the service, 
     
    536493        request = self.capabilities_url(service_url) 
    537494        u = self._open(request) 
    538         return WMSCapabilitiesInfoset(etree.fromstring(u.read())) 
     495        return etree.fromstring(u.read()) 
    539496 
    540497    def readString(self, st): 
    541         """Parse a WMS capabilities document, returning an 
    542         instance of WMSCapabilitiesInfoset 
     498        """Parse a WMS capabilities document, returning an elementtree instance 
    543499 
    544500        string should be an XML capabilities document 
     
    546502        if not isinstance(st, str): 
    547503            raise ValueError("String must be of type string, not %s" % type(st)) 
    548         return WMSCapabilitiesInfoset(etree.fromstring(st)
     504        return etree.fromstring(st
    549505 
    550506