source: SHX/trunk/src/SeismicHandler/modules/stations.py @ 312

Revision 312, 9.8 KB checked in by marcus, 12 years ago (diff)

More station handling (defect).

  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2008-2010 Marcus Walther (walther@szgrf.bgr.de)
4#
5# This file is part of Seismic Handler eXtended (SHX)
6# Full details can be found at project website http://www.seismic-handler.org/
7#
8# SHX is free software; you can redistribute it and/or modify
9# it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
10# the Free Software Foundation; either version 3 of the License, or
11# (at your option) any later version.
12#
13# SHX is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with SHX (see license.txt); if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
21
22import sqlalchemy as sa
23import sqlalchemy.orm as orm
24from sqlalchemy.ext.declarative import declarative_base
25
26from obspy.core.utcdatetime import UTCDateTime
27from SeismicHandler.config import Settings
28from SeismicHandler.basics import Singleton
29from SeismicHandler.modules.log import getLogger
30
31Base = declarative_base()
32
33class ChannelMeta(Base):
34    """
35    Table holding stations inventory for single channels (e.g. GR.GRA1..BHZ).
36    """
37    __tablename__ = "meta"
38
39    # internal use only
40    id = sa.Column(sa.Integer, primary_key=True)
41
42    # GR.GRA1..BHZ
43    channel = sa.Column(sa.String(length=15))
44    network = sa.Column(sa.String(length=2), index=True)
45    station = sa.Column(sa.String(length=5), index=True)
46    location = sa.Column(sa.String(length=2), index=True)
47    stream = sa.Column(sa.String(length=2), index=True)
48    component = sa.Column(sa.String(length=1), index=True)
49    # SQL does not always support datetime with microseconds
50    ondate = sa.Column(sa.DateTime, index=True)
51    ondatems = sa.Column(sa.Integer, index=True)
52    offdate = sa.Column(sa.DateTime, index=True)
53    offdatems = sa.Column(sa.Integer, index=True)
54    latitude = sa.Column(sa.Numeric(precision=13, length=10))
55    longitude = sa.Column(sa.Numeric(precision=13, length=10))
56    elevation = sa.Column(sa.Float(precision=2))
57    depth = sa.Column(sa.Float(precision=2), default=0.)
58    gain = sa.Column(sa.Float(precision=5))
59    zeros = sa.Column(sa.Text)
60    poles = sa.Column(sa.Text)
61
62    # channel + start must be unique
63    __table_args__ = (
64        sa.UniqueConstraint("channel", "ondate"), {}
65    )
66
67    def __init__(self, network, station, location, stream, component, \
68                       latitude, longitude, elevation, depth, gain, poles, \
69                       zeros, start, end=None):
70
71        self.channel = ".".join([network, station, location, \
72                                                  "".join([stream, component])])
73
74        keys = locals().keys()
75        del keys[keys.index("self")]
76
77        # must be handled separately
78        del keys[keys.index("start")]
79        del keys[keys.index("end")]
80
81        for i in keys:
82            setattr(self, i, locals()[i])
83
84        # handle start & end
85        # always cut off ms part since most systems do not support it
86        self.ondate = (start - start.microsecond/1e6).datetime
87        self.ondatems = start.microsecond
88
89        if end:
90            self.offdate = (end - end.microsecond/1e6).datetime
91            self.offdatems = end.microsecond
92
93    def __repr__(self):
94        if self.offdate:
95            return "<Channel:%s %s-%s>" % (
96                self.channel,
97                self.ondate.strftime("%Y-%m-%d"),
98                self.offdate.strftime("%Y-%m-%d")
99            )
100        else:
101            return "<Channel:%s %s-NOW>" % (
102                self.channel,
103                self.ondate.strftime("%Y-%m-%d")
104            )
105
106    def __getattr__(self, name):
107        """
108        Special handling for start and end date, combination of network and
109        station (netstation) and location, stream and component (lsc).
110        """
111        if name.lower() not in ["start", "end", "netstation", "lsc"]:
112            return self.__getattribute__(name)
113
114        if name.lower() == "netstation":
115            return ".".join([self.__dict__["network"], self.__dict__["station"]])
116
117        if name.lower() == "lsc":
118            return ".".join([
119                self.__dict__["location"],
120                self.__dict__["stream"],
121                self.__dict__["component"]
122            ])
123
124        # build UTCDateTime from o[n|ff]date[ms]
125        if name.lower() == "start":
126            return UTCDateTime(self.__dict__["ondate"]) + \
127                                                 self.__dict__["ondatems"] / 1e6
128       
129        if not self.__dict__["offdate"]:
130            return None
131
132        return UTCDateTime(self.__dict__["offdate"]) + \
133                                                self.__dict__["offdatems"] / 1e6
134
135    def __compare(self, other):
136        """
137        Compare station information. All fields except for id and those starting
138        with underscore are compared.
139
140        FIR information is optional and not included!
141        """
142
143        for i in self.__dict__:
144            if i.startswith("_") or i == "id":
145                continue
146
147            if getattr(self, i) != getattr(other, i):
148                return False
149
150        # fir information is optional XXX
151           
152        return True
153
154    def __eq__(self, other):
155        return self.__compare(other)
156
157    def __ne__(self, other):
158        return not self.__compare(other)
159
160class FiR(Base):
161    """
162    Table holding finite impulse responses.
163    """
164    __tablename__ = "fir"
165
166    # internal use only
167    id = sa.Column(sa.Integer, primary_key=True)
168    channel_id = sa.Column(sa.Integer, sa.ForeignKey('meta.id'))
169
170    stage = sa.Column(sa.Integer, index=True)
171    coefficients = sa.Column(sa.Text)
172
173    channel = orm.relation(ChannelMeta, backref=orm.backref('fir', order_by=stage))
174
175    # ChannelMeta_id + stage must be unique
176    __table_args__ = (
177        sa.UniqueConstraint("channel_id", "stage"), {}
178    )
179
180    def __init__(self, stage, coefficients):
181        self.stage = stage
182        self.coefficients = coefficients
183
184    def __repr__(self):
185        return "<FIR:%s>" % self.channel
186
187class Stations(object):
188    """
189    Supply station read/write access.
190    """
191    __metaclass__ = Singleton
192   
193    stations = {}
194
195    dbsessions = {}
196
197    def __init__(self):
198        self.logger = getLogger("stations")
199       
200        self.read()
201
202    def __getitem__(self, name):
203        """
204        """
205        pass
206
207    def __setitem__(self, name, data):
208        """
209        Save or update channel information.
210        """
211        if not isinstance(data, ChannelMeta):
212            raise Exception("Only channel specific information can be updated "
213                            "or saved!")
214
215    def read(self):
216        """
217        Init / refresh station information.
218        """
219        # read only information
220        data = []
221        for db in Settings.config.inventory.readonly:
222            data += self.__readDB(db)
223
224        # data base access r/w
225        self.dbreadwrite = Settings.config.inventory.database[0]
226        data += self.__readDB()
227       
228        for i in data:
229            if not i.netstation in self.stations:
230                self.stations[i.netstation] = {}
231
232            try:
233                self.stations[i.netstation][i.lsc].append(i)
234            except KeyError:
235                self.stations[i.netstation][i.lsc] = [i]
236
237#        print self.stations
238
239    def __readDB(self, db=None):
240        """
241        Read raw channel data from given database.
242        """
243        if not db:
244            db = self.dbreadwrite
245
246        # init db session only if necessary
247        if not self.dbsessions.get(db, None):
248            engine = sa.create_engine(db)
249
250            tabledata = Base.metadata
251            tabledata.create_all(engine)
252
253            Session = orm.sessionmaker(bind=engine)
254            self.dbsessions[db] = Session()
255
256        return self.dbsessions[db].query(ChannelMeta).order_by("ondate").all()
257
258    def __getitem__(self, channeldate):
259        """
260        Return channel meta data from channel code and time information.
261        """
262        channel, dt = channeldate
263        print "xxx", channel
264        # check for overlapping data from rw to readonly db
265        # first: identical datasets
266#        double = set(data) & set(data_read)
267#        print double
268#        for i in double:
269#            if data[i] != data_read[i]:
270#                self.logger.critital("Concurrent data found for channel: %s "
271#                                     "WARNING: It's very likely that you use "
272#                                     "outdated channel information!",
273#                                     i
274#                                    )
275#            if data_read[i].fir and data_read[i].fir != data[i].fir:
276#                self.logger.warning("Concurrent FIR data for channel: %s ",
277#                                     i
278#                                    )
279
280
281    def add(self, station):
282        """
283        Add station to database.
284        """
285        # XXX check for update -> dirty session
286
287        GRA1 = ChannelMeta(
288            network="GR",
289            station="GRA1",
290            location="",
291            stream="BH",
292            component="Z",
293            latitude=49.691888,
294            longitude=11.221720,
295            elevation=499.5,
296            depth=0,
297            gain=0.6e9,
298            zeros="(0.0,0.0) (0.0,0.0)",
299            poles="(-0.037004,0.037016) (-0.037004,-0.037016)",
300            start=UTCDateTime("2006-05-10T16:00:00.000"),
301        )
302        # GRA1.fir = [FiR(0, "0 1 2 3 4 5 6"), FiR(1, "1 2 3 4 5")]
303
304        session = self.dbsessions[self.dbreadwrite]
305        session.add(station)
306
307        try:
308            session.commit()
309        except:
310            session.rollback()
311
312if __name__ == "__main__":
313    stations = Stations()
314    print stations[("GR.GRA1..BHZ", UTCDateTime())]
Note: See TracBrowser for help on using the repository browser.