# Copyright (C) 2012 VT SuperDARN Lab
# Full license can be found in LICENSE.txt
"""
.. module:: radStruct
:synopsis: Load and browse radar information
.. moduleauthor:: Sebastien
*********************
**Module**: pydarn.radar.radStruct
*********************
Radar structures
**Classes**:
* :class:`pydarn.radar.radStruct.network`: radar.dat and hdw.dat information from all the radars
* :class:`pydarn.radar.radStruct.radar`: radar.dat and hdw.dat information
* :class:`pydarn.radar.radStruct.site`: hdw.dat information
"""
# *************************************************************
[docs]class network(object):
"""This class stores information from all radars according to their hdw.dat and radar.dat
This information is read from the radar.sqlite files provided with the pydarn module.
**Members**:
* **nradar** (int): number of radars in class
* **radars** (list): list of :class:`radar` objects
**Methods**:
* :func:`network.getRadarById`
* :func:`network.getRadarByName`
* :func:`network.getRadarByCode`
* :func:`network.getRadarsByPosition`
* :func:`network.getAllCodes`
**Example**:
::
obj = pydarn.radar.network()
.. note:: To add your own radar information to this class you can use the :func:`radInfoIO.radarRead` and the :func:`radInfoIO.hdwRead`. Then, manually append the output of these functions to this object.
written by Sebastien, 2012-08
"""
def __init__(self):
"""Default class constructor
**Belongs to**: :class:`network`
**Args**:
* **None**
**Returns**:
* **network** (obj)
written by Sebastien, 2012-08
"""
import sqlite3 as lite
import os
self.radars = []
# Get DB name
rad_path = os.path.dirname( os.path.abspath( __file__ ) )
dbname = os.path.join(rad_path, 'radars.sqlite')
if not os.path.isfile(dbname):
print "%s not found" % dbname
return
with lite.connect(dbname) as conn:
cur = conn.cursor()
cur.execute("select count() from rad")
self.nradar = cur.fetchone()[0]
cur.execute("select id from rad")
rows = cur.fetchall()
for row in rows:
self.radars.append(radar())
self.radars[-1].fillFromSqlite(dbname, row[0])
def __len__(self):
"""Object length (number of radars)
**Belongs to**: :class:`network`
**Args**:
* **None**
**Returns**:
* **number of radars** (int)
**Example**:
::
len(obj)
written by Sebastien, 2012-08
"""
return self.nradar
def __str__(self):
"""Object string representation
**Belongs to**: :class:`network`
**Args**:
* **None**
**Returns**:
* **a string** (str)
**Example**:
::
print(obj)
written by Sebastien, 2012-08
"""
outstring = "Network information object: \
\n\tTotal radars: {:d}".format(self.nradar)
for iRad in range( self.nradar ):
if self.radars[iRad].status == 1:
status = 'active'
elif self.radars[iRad].status == -1:
status = 'offline'
elif self.radars[iRad].status == 0:
status = 'planned'
else:
status = '{}'.format(self.radars[iRad].status)
hemi = 'South' if self.radars[iRad].sites[0].geolat < 0 else 'North'
outstring += '\n\t\t({}) - [{}][{}] {} ({})'.format(hemi,
self.radars[iRad].id,
self.radars[iRad].code[0],
self.radars[iRad].name,
status)
return outstring
[docs] def getRadarById(self, id):
"""Get a specific radar from its ID
**Belongs to**: :class:`network`
**Args**:
* **id** (int): radar ID
**Returns**:
* **radar** (:class:`radar`)
**Example**:
::
# To get the Blackstone radar by its ID
radar = obj.getRadarById(33)
written by Sebastien, 2012-08
"""
radar = self.getRadarBy(id, by='id')
return radar
[docs] def getRadarByName(self, name):
"""Get a specific radar from its name
**Belongs to**: :class:`network`
**Args**:
* **name** (str): radar full name
**Returns**:
* **radar** (:class:`radar`)
**Example**:
::
# To get the Blackstone radar by its name
radar = obj.getRadarById('Blackstone')
written by Sebastien, 2012-08
"""
radar = self.getRadarBy(name, by='name')
return radar
[docs] def getRadarByCode(self, code):
"""Get a specific radar from its 3-letter code
**Belongs to**: :class:`network`
**Args**:
* **code** (str): radar 3-letter code
**Returns**:
* **radar** (:class:`radar`)
**Example**:
::
# To get the Blackstone radar by its code
radar = obj.getRadarById('bks')
written by Sebastien, 2012-08
"""
radar = self.getRadarBy(code, by='code')
return radar
[docs] def getRadarBy(self, radN, by):
"""Get a specific radar from its name/code/id
This method is the underlying function behing getRadarByCode,
getRadarByName and getRadarById
**Belongs to**: :class:`network`
**Args**:
* **radN** (str/int): radar identifier (either code, name or id)
* **by** (str): look-up method: 'code', 'name', 'id'
**Returns**:
* **radar** (:class:`radar`)
written by Sebastien, 2012-08
"""
found = False
for iRad in xrange( self.nradar ):
if by.lower() == 'code':
for ic in xrange(self.radars[iRad].cnum):
if self.radars[iRad].code[ic].lower() == radN.lower():
found = True
return self.radars[iRad]
break
elif by.lower() == 'name':
if self.radars[iRad].name.lower() == radN.lower():
found = True
return self.radars[iRad]
break
elif by.lower() == 'id':
if self.radars[iRad].id == radN:
found = True
return self.radars[iRad]
break
else:
print 'getRadarBy: invalid method by {}'.format(by)
break
if not found:
print 'getRadarBy: could not find radar {}: {}'.format(by, radN)
return found
[docs] def getRadarsByPosition(self, lat, lon, alt, distMax=4000., datetime=None):
"""Get a list of radars able to see a given point on Earth
**Belongs to**: :class:`network`
**Args**:
* **lat**: latitude of given point in geographic coordinates
* **lon**: longitude of given point in geographic coordinates
* **alt**: altitude of point above the Earth's surface in km
* **[distMax]**: maximum distance of given point from radar
* **[datetime]**: python datetime object (defaults to today)
**Returns**:
* A dictionnary with keys:
* 'radars': a list of radar objects (:class:`radar`)
* 'dist': a list of distance from radar to given point (1 per radar)
* 'beam': a list of beams (1 per radar) seeing the given point
**Example**:
::
radars = obj.getRadarsByPosition(67., 134., 300.)
written by Sebastien, 2012-08
"""
from datetime import datetime as dt
from utils import geoPack as geo
from numpy import sin, cos, arccos, dot, cross, sign
from math import radians, degrees
if not datetime: datetime = dt.utcnow()
found = False
out = {'radars': [],
'dist': [],
'beam': []}
for iRad in xrange( self.nradar ):
site = self.radars[iRad].getSiteByDate(datetime)
# Skip if radar inactive at date
if (not site) and (self.radars[iRad].status != 1): continue
if not (self.radars[iRad].stTime <= datetime <= self.radars[iRad].edTime): continue
# Skip if radar in other hemisphere
if site.geolat*lat < 0.: continue
distPnt = geo.calcDistPnt(site.geolat, site.geolon, site.alt,
distLat=lat, distLon=lon, distAlt=300.)
# Skip if radar too far
if distPnt['dist'] > distMax: continue
# minAz = (site.boresite % 360.)-abs(site.bmsep)*site.maxbeam/2
# maxAz = (site.boresite % 360.)+abs(site.bmsep)*site.maxbeam/2
extFov = abs(site.bmsep)*site.maxbeam/2
ptBo = [cos(radians(site.boresite)), sin(radians(site.boresite))]
ptAz = [cos(radians(distPnt['az'])), sin(radians(distPnt['az']))]
deltAz = degrees( arccos( dot(ptBo, ptAz) ) )
# Skip if out of azimuth range
if not abs(deltAz) <= extFov: continue
if sign(cross(ptBo, ptAz)) >= 0:
beam = int( site.maxbeam/2 + round( deltAz/site.bmsep ) - 1 )
else:
beam = int( site.maxbeam/2 - round( deltAz/site.bmsep ) )
# Update output
found = True
out['radars'].append(self.radars[iRad])
out['dist'].append(distPnt['dist'])
out['beam'].append(beam)
if found: return out
else: return found
[docs] def getAllCodes(self, datetime=None, hemi=None):
"""Get a list of all active radar codes
**Belongs to**: :class:`network`
**Args**:
* **[datetime]**: python datetime object (defaults to today)
* **[hemi]**: 'north' or 'south' defaults to both
**Returns**:
* **codes** (list): A list of 3-letter codes
written by Sebastien, 2012-08
"""
from datetime import datetime as dt
if not datetime: datetime = dt.utcnow()
codes = []
for iRad in xrange( self.nradar ):
tcod = self.radars[iRad].getSiteByDate(datetime)
if (tcod) and (self.radars[iRad].status == 1) \
and (self.radars[iRad].stTime <= datetime <= self.radars[iRad].edTime):
if (hemi == None) or \
(hemi.lower() == 'south' and tcod.geolat < 0) or \
(hemi.lower() == 'north' and tcod.geolat >= 0):
codes.append(self.radars[iRad].code[0])
return codes
# *************************************************************
[docs]class radar(object):
"""Reads radar.dat file and hdw.dat for a given radar and fills a radar structure
**Members**:
* **id** (int): radar ID
* **status** (int): radar status (active, inactive or planned)
* **cnum** (int): number of code names (usually 2, a 3-letter and 1-letter)
* **code** (list): list of radar codes (usually 2, a 3-letter and 1-letter)
* **name** (str): radar name
* **operator** (str): PI institution
* **hdwfname** (str): hdw.dat file name
* **stTime** (datetime.datetime): date of first lights
* **edTime** (datetime.datetime): last day of operations
* **snum** (int): number of site objects (i.e. number of updates to the hdw.dat)
* **sites** (list): list of :class:`site` objects
**Methods**:
* :func:`radar.fillFromSqlite`
* :func:`radar.getSiteByDate`
**Example**:
::
obj = pydarn.radar.radar()
written by Sebastien, 2012-08
"""
#__slots__ = ('id', 'status', 'cnum', 'code', 'name', 'operator', 'hdwfname', 'stTime', 'edTime', 'snum', 'site')
def __init__(self, code=None, radId=None):
"""Default class constructor
If no argument is passed, the object is initialized to 0
**Belongs to**: :class:`radar`
**Args**:
* [**code**] (str): 3-letter radar code
* [**radId**] (int): radar ID
**Returns**:
* **radar** (:class:`radar`)
.. note:: you should provide either **code** OR **radId**, not both
written by Sebastien, 2012-08
"""
import sqlite3 as lite
import pickle
import os
self.id = 0
self.status = 0
self.cnum = 0
self.code = []
self.name = u''
self.operator = u''
self.hdwfname = u''
self.stTime = 0.0
self.edTime = 0.0
self.snum = 0
self.sites = [site()]
# If a radar is requested...
if code or radId:
rad_path = os.path.dirname( os.path.abspath( __file__ ) )
dbname = os.path.join(rad_path, 'radars.sqlite')
if not os.path.isfile(dbname):
print "%s not found" % dbname
return
# if the radar code was provided, look for corresponding id
if code:
with lite.connect(dbname) as conn:
cur = conn.cursor()
cur.execute('SELECT id,code FROM rad')
rows = cur.fetchall()
for row in rows:
if code in pickle.loads(row[1].encode('ascii')):
radId = row[0]
self.fillFromSqlite(dbname, radId)
[docs] def fillFromSqlite(self, dbname, radId):
"""fill radar structure from sqlite DB
**Belongs to**: :class:`radar`
**Args**:
* **dbname** (str): sqlite database path/name
* **radID** (int): radar ID
**Returns**:
* **None**
written by Sebastien, 2013-02
"""
from datetime import datetime
import sqlite3 as lite
import pickle
import os
if not os.path.isfile(dbname):
print "%s not found" % dbname
return
with lite.connect(dbname, detect_types=lite.PARSE_DECLTYPES) as conn:
cur = conn.cursor()
cur.execute('SELECT * FROM rad WHERE id=?', (radId,))
row = cur.fetchone()
if not row:
print 'Radar not found in DB: {}'.format(radId)
return
self.id = row[0]
self.cnum = row[1]
self.code = pickle.loads(row[2].encode('ascii'))
self.name = row[3]
self.operator = row[4]
self.hdwfname = row[5]
self.status = row[6]
self.stTime = row[7]
self.edTime = row[8]
self.snum = row[9]
for ist in range(self.snum):
self.sites[ist].fillFromSqlite(dbname, radId, ind=ist)
self.sites.append(site())
del self.sites[-1]
def __len__(self):
""" Object length (number of site updates)
"""
return self.snum
def __str__(self):
"""Object string representation
**Belongs to**: :class:`radar`
**Args**:
* **None**
**Returns**:
* **a string** (str)
**Example**:
::
print(obj)
written by Sebastien, 2012-08
"""
outstring = 'id: {0} \
\nstatus: {1} \
\ncnum: {2} \
\ncode: {3} \
\nname: {4} \
\noperator: {5} \
\nhdwfname: {6} \
\nstTime: {7} \
\nedTime: {8} \
\nsnum: {9} \
\nsites: {10} elements'.format(self.id, \
self.status, \
self.cnum, \
[c for c in self.code], \
self.name, \
self.operator, \
self.hdwfname, \
self.stTime.date(), \
self.edTime.date(), \
self.snum, \
len(self.sites))
return outstring
[docs] def getSiteByDate(self, datetime):
"""Get a specific radar site at a given date
**Belongs to**: :class:`network`
**Args**:
* **datetime** (datetime.datetime)
**Returns**:
* **site** (:class:`site`)
**Example**:
::
# To get the Blackstone radar configuration today
site = obj.getSiteByDate(datetime.datetime.utcnow())
written by Sebastien, 2012-08
"""
found = False
for iSit in range( self.snum ):
if self.sites[iSit].tval == -1:
found = True
return self.sites[iSit]
break
elif self.sites[iSit].tval >= datetime:
found = True
return self.sites[iSit]
if not found:
print 'getSiteByDate: could not get SITE for date {}'.format(datetime)
return found
# *************************************************************
[docs]class site(object):
"""Reads hdw.dat for a given radar and fills a SITE structure
**Members**:
* **tval** (datetime.datetime): last date and time operating with these parameters
* **geolat** (float): main array latitude [deg]
* **geolon** (float): main array longitude [deg]
* **alt** (float): main array altitude [km]
* **boresite** (float): boresight azimuth [deg]
* **bmsep** (float): beam separation [deg]
* **vdir** (int): velocity sign
* **atten** (float): Analog Rx attenuator step [dB]
* **tdiff** (float): Propagation time from interferometer array antenna to phasing matrix [us]
* **phidiff** (float): phase sign for interferometric calculations
* **interfer** (list): Interferometer offset [x, y, z]
* **recrise** (float): Analog Rx rise time [us]
* **maxatten** (int): maximum number of analog attenuation stages
* **maxgate** (int): maximum number of range gates (assuming 45km gates)
* **maxbeam** (int): maximum number of beams
**Methods**:
* :func:`site.fillFromSqlite`
* :func:`site.beamToAzim`
* :func:`site.azimToBeam`
**Example**:
::
obj = pydarn.radar.site()
written by Sebastien, 2012-08
"""
def __init__(self, radId=None, code=None, dt=None):
"""Default class constructor
**Belongs to**: :class:`site`
**Args**:
* [**radId**] (int): radar ID
* [**code**] (str): 3-letter radar code
* [**dt**] (datetime.datetime): date and time of radar configurationation
**Returns**:
* **site** (:class:`site`)
.. note:: you should provide either **code** OR **radId**, not both
written by Sebastien, 2012-08
"""
import sqlite3 as lite
import pickle
import os
self.tval = 0.0
self.geolat = 0.0
self.geolon = 0.0
self.alt = 0.0
self.boresite = 0.0
self.bmsep = 0.0
self.vdir = 0
self.atten = 0.0
self.tdiff = 0.0
self.phidiff = 0.0
self.interfer = [0.0, 0.0, 0.0]
self.recrise = 0.0
self.maxatten = 0
self.maxgate = 0
self.maxbeam = 0
if radId or code:
rad_path = os.path.dirname( os.path.abspath( __file__ ) )
dbname = os.path.join(rad_path, 'radars.sqlite')
if not os.path.isfile(dbname):
print "%s not found" % dbname
return
# if the radar code was provided, look for corresponding id
if code:
with lite.connect(dbname) as conn:
cur = conn.cursor()
cur.execute('SELECT id,code FROM rad')
rows = cur.fetchall()
for row in rows:
if code in pickle.loads(row[1].encode('ascii')):
radId = row[0]
self.fillFromSqlite(dbname, radId, dt=dt)
[docs] def fillFromSqlite(self, dbname, radId, ind=-1, dt=None):
"""fill site structure from sqlite databse
**Belongs to**: :class:`site`
**Args**:
* **dbname** (str): sqlite database path/name
* **radID** (int): radar ID
* [**ind**] (int): site index; defaults to most recent configuration
* [**dt**] (datetime.datetime)
**Returns**:
* **None**
written by Sebastien, 2013-02
"""
from datetime import datetime
import sqlite3 as lite
import pickle
import os
if not os.path.isfile(dbname):
print "%s not found" % dbname
return
with lite.connect(dbname, detect_types=lite.PARSE_DECLTYPES) as conn:
cur = conn.cursor()
if dt:
cur.execute('SELECT * FROM hdw WHERE id=? and tval>=? ORDER BY tval ASC', (radId, dt))
row = cur.fetchone()
else:
cur.execute('SELECT * FROM hdw WHERE id=? ORDER BY tval ASC', (radId,))
row = cur.fetchall()[ind]
self.id = row[0]
self.tval = row[1]
self.geolat = row[2]
self.geolon = row[3]
self.alt = row[4]
self.boresite = row[5]
self.bmsep = row[6]
self.vdir = row[7]
self.tdiff = row[8]
self.phidiff = row[9]
self.recrise = row[10]
self.atten = row[11]
self.maxatten = row[12]
self.maxgate = row[13]
self.maxbeam = row[14]
self.interfer = pickle.loads(row[15].encode('ascii'))
def __len__(self):
"""
Object length
"""
return 1
def __str__(self):
"""
Object string representation
"""
outstring = 'tval: {0} \
\ngeolat: {1:5.2f} \
\ngeolon: {2:5.2f} \
\nalt: {3:6.2f} \
\nboresite: {4:5.2f} \
\nbmsep: {5:5.2f} \
\nvdir: {6} \
\natten: {7:5.2f} \
\ntdiff: {8:6.4f} \
\nphidiff: {9:3.1f} \
\ninterfer: [{10:5.2f}, {11:5.2f}, {12:5.2f}] \
\nrecrise: {13:5.3f} \
\nmaxatten: {14} \
\nmaxgate: {15} \
\nmaxbeam: {16}'.format(self.tval, \
self.geolat, \
self.geolon, \
self.alt, \
self.boresite, \
self.bmsep, \
self.vdir, \
self.atten, \
self.tdiff, \
self.phidiff, \
self.interfer[0], self.interfer[1], self.interfer[2], \
self.recrise, \
self.maxatten, \
self.maxgate, \
self.maxbeam)
return outstring
[docs] def beamToAzim(self, beam):
'''Get azimuth of given beam
**Args**:
* **beam** (int): beam number
**Returns**:
* **azim** (float): beam azimuth
'''
return self.boresite - ((self.maxbeam-1)/2. - beam)*self.bmsep
[docs] def azimToBeam(self, azim):
'''Get azimuth of given beam
**Args**:
* **azim** (float): beam azimuth [deg. East]
**Returns**:
* **beam** (int): beam number
'''
import numpy as np
saz = np.sin( np.radians(azim - self.boresite) )
caz = np.cos( np.radians(azim - self.boresite) )
delta = np.degrees( np.arctan2(saz, caz) )
beam = np.round( delta/self.bmsep + (self.maxbeam-1)/2. )
return np.int_(beam)