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

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

Incomplete - # 22558: Formatting of start/end times to conform to OpenSearch? time extension
 http://team.ceda.ac.uk/trac/ceda/ticket/22558

fix a None item in time filtering

  • 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        if start is not None or stop is not None:
121            res = self._filter_time(res, start, stop)
122        return res
123
124    def _filter_bbox(self, collections, bbox):
125        if bbox is None:
126            return collections
127        res = []
128        for collection in collections:
129            if len(collection.geographicExtent) > 0:
130                collection_geometry = getGeograpicExtentGeometry(collection.geographicExtent[0])
131                if collection_geometry is not None \
132                        and intersectGeometries(bbox, collection_geometry, self):
133                    res.append(self.searchSelectiveLoad(CEDA_ObservationCollection, \
134                            collection.id, ['geographicExtent.*']))
135        return res
136
137    def _filter_time(self, collections, start, end):
138        if start is None and end is None:
139            return collections
140        res = []       
141        for collection in collections:
142            for phenomenon in collection.phenomenonTime:
143                #is a TM_Instant?
144                if hasattr(phenomenon, 'ceda_timestamp'):
145                    #is a TM_Period?                   
146                    if hasattr(phenomenon, 'start') and hasattr(phenomenon, 'end'):
147                        if start is not None \
148                                and end is None \
149                                and start < phenomenon.start.ceda_timestamp:
150                            res.append(collection)
151                            break
152                        if start is None \
153                                and end is not None \
154                                and end > phenomenon.end.ceda_timestamp:
155                            res.append(collection)
156                            break                       
157                        if start is not None \
158                                and end is not None \
159                                and start < phenomenon.start.ceda_timestamp \
160                                and end > phenomenon.end.ceda_timestamp:
161                            res.append(collection)
162                            break
163                   
164                    #then is a TM_Instant?
165                    if start is not None \
166                                and end is None \
167                                and start < phenomenon.ceda_timestamp:
168                            res.append(collection)
169                            break
170                    if start is None \
171                                and end is not None \
172                                and end > phenomenon.ceda_timestamp:
173                            res.append(collection)
174                            break                                               
175                    if start is not None \
176                            and end is not None \
177                            and start < phenomenon.ceda_timestamp \
178                            and end > phenomenon.ceda_timestamp:
179                        res.append(collection)
180                        break
181
182        return res
183
184    def retrieveGUIDFromInstance(self, instance):
185        """
186            Returns the CedaGUID object associated with the given instance.
187            @param instance: instance of CEDA_Observation or CEDA_ObservationCollection 
188        """
189        if instance is None:
190            return None
191        if type(instance) == CEDA_ObservationCollection:
192            return self._session.query(CedaGUID).\
193                filter(CedaGUID.ceda_observationcollection==instance.id).first()
194        elif type(instance) == CEDA_Observation:
195            return self._session.query(CedaGUID).\
196                filter(CedaGUID.ceda_observation==instance.id).first()           
197
198    def getInstanceFromGUID(self, guid):
199        """
200            Returns a CEDA_Observation or a CEDA_ObservationCollection from their GUID
201            @param guid: the object guid
202            @return: the associated instance or None
203        """
204        ceda_guid = self.search(CedaGUID, guid)
205        if ceda_guid:
206            if ceda_guid.ceda_observation:
207                return self.search(CEDA_Observation, ceda_guid.ceda_observation)
208            elif ceda_guid.ceda_observationcollection:
209                return self.search(CEDA_ObservationCollection,
210                                   ceda_guid.ceda_observationcollection)
211        return None
212
213    def search(self, clazz, inst_key):
214        """
215            Searches a required instance by id
216            @param clazz: the class type to search for
217            @param inst_key: the instance id for which the search is done.
218                                If None return all the instances of the type
219            @return the required instance(s)
220        """       
221        if inst_key:
222            ret = EPB.search(clazz, inst_key, self._session)
223        else:
224            ret = EPB.search(clazz, None, self._session).all()
225        return ret
226     
227    def _search(self, clazz, inst_id):
228        return EPB.search(clazz, inst_id, self._session)     
229     
230    def searchSelectiveLoadByInstance(self, instance, attributes):
231        id_key = identity_key(instance=instance)
232        if id_key:
233            return self.searchSelectiveLoad(id_key[0], id_key[1], attributes)
234        return None
235     
236    def searchSelectiveLoad(self, clazz, inst_id, attributes):
237        """
238            Searches a required instance by id loading selectively \
239            the specified fields. The parameter "attributes" is a single string or
240            a list of attributes
241            owned by the instance of "clazz". Furthermore such list may contain
242            also the children of the main attributes. For example "attrs" may look
243            like
244            ['resultAccumulation', 'identifier.authority',
245            'resultTime.position.dateTime8601.month', \
246                      'relatedParty.party', 'result.source.function', 'permission', \
247                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
248                      'inSupportOf.abstract', 'dataLineage']
249            the first parameter refers to the main class so is equivalent to
250            clazz.resultAccumulation
251            the second parameter is equivalent to invoke
252            clazz.identifier.authority
253            As single string "attributes" could be as well just 'identifier.authority'
254            @param clazz: the class type to search for
255            @param inst_id: the instance id for which the search is done
256            @param attributes: a single string or a list of attributes to load
257            @param session: a session to use for the query. By default a new one
258                        is created automatically at start and closed at the end
259            @return the required instance             
260        """               
261        ret = EPB.searchSelectiveLoad(clazz, inst_id, attributes, self._session)
262        return ret   
263   
264    def loadAttributes(self, instance, attributes):
265        """
266            Returns the attribute of an instance. The parameter "attributes"
267            is a single string or a list of attributes
268            owned by the instance of "clazz". Furthermore such list may contain
269            also the children of the main attributes. For example "attrs" may look
270            like
271            ['resultAccumulation', 'identifier.authority',
272            'resultTime.position.dateTime8601.month', \
273                      'relatedParty.party', 'result.source.function', 'permission', \
274                      'geographicExtent', 'phenomenonTime', 'keywords', 'description', \
275                      'inSupportOf.abstract', 'dataLineage']
276            the first parameter refers to the main class so is equivalent to
277            clazz.resultAccumulation
278            the second parameter is equivalent to invoke
279            clazz.identifier.authority
280            As single string "attributes" could be as well just 'identifier.authority'
281            @param instance: an instance containing the appropriate id
282            @param attributes: the attribute value required
283            @param session: the session to use for the operation
284            @return: the given instance filled with the required attributes.                     
285        """
286        instance = self._session.merge(instance)
287        EPB.loadAttributes(instance, attributes, self._session)                 
288        return instance
289
290    def executeNative(self, sqlNative):
291        return EPB.executeNative(sqlNative, self._session) 
292   
293def getGeograpicExtentGeometry(ge):
294    '''
295        Creates the appropriate postgis geometry from a EX_GeographicExtent
296        @param ge: an EX_GeographicExtent instance
297        @return: a postgix text geometry
298    '''
299    if isinstance(ge, EX_GeographicBoundingBox):
300        return create_st_setSRID(ge.westBoundLongitude, ge.southBoundLatitude, \
301                       ge.eastBoundLongitude, ge.northBoundLatitude)
302    return None
Note: See TracBrowser for help on using the repository browser.