source: mauRepo/HPFos/trunk/hpfos/HPFos/moles3epb.py @ 8567

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/mauRepo/HPFos/trunk/hpfos/HPFos/moles3epb.py@8567
Revision 8567, 13.8 KB checked in by mnagni, 7 years ago (diff)

Complete - # 22551: List resources / display single file
 http://team.ceda.ac.uk/trac/ceda/ticket/22551

Fixed the time start/stop query error

  • Property svn:mime-type set to text/plain
Line 
1'''
2BSD Licence
3Copyright (c) 2012, Science & Technology Facilities Council (STFC)
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without modification,
7are permitted provided that the following conditions are met:
8
9    * Redistributions of source code must retain the above copyright notice,
10        this list of conditions and the following disclaimer.
11    * Redistributions in binary form must reproduce the above copyright notice,
12        this list of conditions and the following disclaimer in the documentation
13        and/or other materials provided with the distribution.
14    * Neither the name of the Science & Technology Facilities Council (STFC)
15        nor the names of its contributors may be used to endorse or promote
16        products derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
22BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29Created on 10 Jan 2012
30
31@author: Maurizio Nagni
32'''
33from hpfos.libs.epb import EPB
34from ea_model.ceda_metadatamodel.ceda_observationcollection.ceda_observationcollection \
35    import CEDA_ObservationCollection
36from ea_model.ceda_metadatamodel.ceda_observation.ceda_observation \
37    import CEDA_Observation
38from ea_model.moles3_4.observationcollection.mo_observationcollection \
39    import MO_ObservationCollection
40from ea_model.moles3_4.observation.mo_observation import MO_Observation
41from sqlalchemy import Table, Column, ForeignKey, Integer, String
42from sqlalchemy.orm import mapper
43from hpfos.HPFos.ceda_guid import CedaGUID
44from sqlalchemy.orm.util import identity_key
45from ea_model.iso_19115_2006_metadata_corrigendum.extent_information.\
46ex_geographicboundingbox import EX_GeographicBoundingBox
47
48import logging
49from logging import StreamHandler
50from hpfos.libs.postgisutil import create_st_setSRID,\
51    intersectGeometries
52from hpfos.HPFos.db.tm_instant import customize_tm_instant
53
54class Moles3EPBFactory(EPB):
55   
56    def __init__(self, db_manager):
57        self._db_manager = db_manager
58        self._initCEDA_Customization()   
59       
60
61    def _initCEDA_Customization(self):
62        self._associateCEDA_GUID()
63        customize_tm_instant(self._db_manager.engine, self._db_manager.metadata)       
64       
65    def _associateCEDA_GUID(self):
66        guid_table = Table('ceda_guid', self._db_manager.metadata, \
67                           Column('id', String, primary_key=True), \
68                           Column('ceda_observationcollection', Integer,
69                                  ForeignKey('ceda_observationcollection.id')),
70                           Column('ceda_observation', Integer,
71                                  ForeignKey('ceda_observation.id')))
72        mapper(CedaGUID, guid_table)
73        self._db_manager.metadata.create_all()
74
75    def _get_session(self):
76        if self._db_manager is not None:
77            return self._db_manager.createDbSession()               
78        return None
79       
80    def createEPB(self):
81        return Moles3EPB(self._get_session())
82
83class Moles3EPB(object):
84
85    def __init__(self, session):
86        self._session = session
87        self.logging = logging.getLogger('Moles3EPB')
88        self.logging.addHandler(StreamHandler())
89        self.logging.setLevel(logging.INFO)
90       
91    def close(self):
92        return self._session.close()                         
93       
94    def getObservationCollections(self, bbox = None, keywords = '*',
95                                  start = None, stop = None):
96        """
97            Returns the stored CEDA_ObservationCollection
98            eventually filtering them against a postgis goemetry
99            @param bbox: a postgis geometry
100            @return: a list of CEDA_ObservationCollections or None if empty 
101        """
102       
103        collections = self._session.query(CEDA_ObservationCollection)       
104        if keywords != '*' and keywords is not None:
105            collections = collections.\
106                join(MO_ObservationCollection).join(MO_Observation.identifier). \
107                filter('md_identifier.code_search_vector @@ to_tsquery(:terms)').\
108                filter("mo_observationcollection.description @@ to_tsquery(:terms)").\
109                params(terms=keywords)
110        res = None
111        #collections = self._extract_observation_collection_by_title_keywords(keywords)
112        if collections is None:
113            return []   
114       
115        res = collections.all()
116        if bbox is None and start is None and stop is None:
117            return res
118
119        res = self._filter_bbox(collections, bbox);
120        res = self._filter_time(res, start, stop)
121        return res
122
123    def _filter_bbox(self, collections, bbox):
124        if bbox is None:
125            return collections
126        res = []
127        for collection in collections:
128            if len(collection.geographicExtent) > 0:
129                collection_geometry = getGeograpicExtentGeometry(collection.geographicExtent[0])
130                if collection_geometry is not None \
131                        and intersectGeometries(bbox, collection_geometry, self):
132                    res.append(self.searchSelectiveLoad(CEDA_ObservationCollection, \
133                            collection.id, ['geographicExtent.*']))
134        return res
135
136    def _filter_time(self, collections, start, end):
137        if start is None and end is None:
138            return collections
139        res = []       
140        for collection in collections:
141            for phenomenon in collection.phenomenonTime:
142                #is a TM_Instant?
143                if hasattr(phenomenon, 'ceda_timestamp'):
144                    #is a TM_Period?                   
145                    if hasattr(phenomenon, 'start') and hasattr(phenomenon, 'end'):
146                        if start is not None \
147                                and end is None \
148                                and start < phenomenon.start.ceda_timestamp:
149                            res.append(collection)
150                            break
151                        if start is None \
152                                and end is not None \
153                                and end > phenomenon.end.ceda_timestamp:
154                            res.append(collection)
155                            break                       
156                        if start is not None \
157                                and end is not None \
158                                and start < phenomenon.start.ceda_timestamp \
159                                and end > phenomenon.end.ceda_timestamp:
160                            res.append(collection)
161                            break
162                   
163                    #then is a TM_Instant?
164                    if start is not None \
165                                and end is None \
166                                and start < phenomenon.ceda_timestamp:
167                            res.append(collection)
168                            break
169                    if start is None \
170                                and end is not None \
171                                and end > phenomenon.ceda_timestamp:
172                            res.append(collection)
173                            break                                               
174                    if start is not None \
175                            and end is not None \
176                            and start < phenomenon.ceda_timestamp \
177                            and end > phenomenon.ceda_timestamp:
178                        res.append(collection)
179                        break
180
181        return res
182
183    def retrieveGUIDFromInstance(self, instance):
184        """
185            Returns the CedaGUID object associated with the given instance.
186            @param instance: instance of CEDA_Observation or CEDA_ObservationCollection 
187        """
188        if instance is None:
189            return None
190        if type(instance) == CEDA_ObservationCollection:
191            return self._session.query(CedaGUID).\
192                filter(CedaGUID.ceda_observationcollection==instance.id).first()
193        elif type(instance) == CEDA_Observation:
194            return self._session.query(CedaGUID).\
195                filter(CedaGUID.ceda_observation==instance.id).first()           
196
197    def getInstanceFromGUID(self, guid):
198        """
199            Returns a CEDA_Observation or a CEDA_ObservationCollection from their GUID
200            @param guid: the object guid
201            @return: the associated instance or None
202        """
203        ceda_guid = self.search(CedaGUID, guid)
204        if ceda_guid:
205            if ceda_guid.ceda_observation:
206                return self.search(CEDA_Observation, ceda_guid.ceda_observation)
207            elif ceda_guid.ceda_observationcollection:
208                return self.search(CEDA_ObservationCollection,
209                                   ceda_guid.ceda_observationcollection)
210        return None
211
212    def search(self, clazz, inst_key):
213        """
214            Searches a required instance by id
215            @param clazz: the class type to search for
216            @param inst_key: the instance id for which the search is done.
217                                If None return all the instances of the type
218            @return the required instance(s)
219        """       
220        if inst_key:
221            ret = EPB.search(clazz, inst_key, self._session)
222        else:
223            ret = EPB.search(clazz, None, self._session).all()
224        return ret
225     
226    def _search(self, clazz, inst_id):
227        return EPB.search(clazz, inst_id, self._session)     
228     
229    def searchSelectiveLoadByInstance(self, instance, attributes):
230        id_key = identity_key(instance=instance)
231        if id_key:
232            return self.searchSelectiveLoad(id_key[0], id_key[1], attributes)
233        return None
234     
235    def searchSelectiveLoad(self, clazz, inst_id, attributes):
236        """
237            Searches a required instance by id loading selectively \
238            the specified fields. The parameter "attributes" is a single string or
239            a list of attributes
240            owned by the instance of "clazz". Furthermore such list may contain
241            also the children of the main attributes. For example "attrs" may look
242            like
243            ['resultAccumulation', 'identifier.authority',
244            'resultTime.position.dateTime8601.month', \
245                      'relatedParty.party', 'result.source.function', 'permission', \
246                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
247                      'inSupportOf.abstract', 'dataLineage']
248            the first parameter refers to the main class so is equivalent to
249            clazz.resultAccumulation
250            the second parameter is equivalent to invoke
251            clazz.identifier.authority
252            As single string "attributes" could be as well just 'identifier.authority'
253            @param clazz: the class type to search for
254            @param inst_id: the instance id for which the search is done
255            @param attributes: a single string or a list of attributes to load
256            @param session: a session to use for the query. By default a new one
257                        is created automatically at start and closed at the end
258            @return the required instance             
259        """               
260        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
261        return ret   
262   
263    def loadAttributes(self, instance, attributes):
264        """
265            Returns the attribute of an instance. The parameter "attributes"
266            is a single string or a list of attributes
267            owned by the instance of "clazz". Furthermore such list may contain
268            also the children of the main attributes. For example "attrs" may look
269            like
270            ['resultAccumulation', 'identifier.authority',
271            'resultTime.position.dateTime8601.month', \
272                      'relatedParty.party', 'result.source.function', 'permission', \
273                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
274                      'inSupportOf.abstract', 'dataLineage']
275            the first parameter refers to the main class so is equivalent to
276            clazz.resultAccumulation
277            the second parameter is equivalent to invoke
278            clazz.identifier.authority
279            As single string "attributes" could be as well just 'identifier.authority'
280            @param instance: an instance containing the appropriate id
281            @param attributes: the attribute value required
282            @param session: the session to use for the operation
283            @return: the given instance filled with the required attributes.                     
284        """
285        instance = self._session.merge(instance)
286        EPB.loadAttributes(instance, attributes, self._session)                 
287        return instance
288
289    def executeNative(self, sqlNative):
290        return EPB.executeNative(sqlNative, self._session) 
291   
292def getGeograpicExtentGeometry(ge):
293    '''
294        Creates the appropriate postgis geometry from a EX_GeographicExtent
295        @param ge: an EX_GeographicExtent instance
296        @return: a postgix text geometry
297    '''
298    if isinstance(ge, EX_GeographicBoundingBox):
299        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
300                       ge.eastBoundLongitude, ge.northBoundLatitude)
301    return None
Note: See TracBrowser for help on using the repository browser.