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 Parameters ---------- 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 : Optional[datetime.datetime object] python datetime object (defaults to today) Returns ------- dict A dictionnary with keys: radars : a list of radar objects (:class:`radar`) dist: a list of distance from radar to given point (1 perradar) 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 davitpy.utils import geoPack as geo import numpy as np 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 dist_pnt = geo.calcDistPnt(site.geolat, site.geolon, site.alt, distLat=lat, distLon=lon, distAlt=300.) # Skip if radar too far if dist_pnt['dist'] > distMax: continue # minAz = (site.boresite % 360.)-abs(site.bmsep)*site.maxbeam/2 # maxAz = (site.boresite % 360.)+abs(site.bmsep)*site.maxbeam/2 ext_fov = abs(site.bmsep) * site.maxbeam / 2 pt_bo = [ np.cos(np.radians(site.boresite)), np.sin(np.radians(site.boresite)) ] pt_az = [ np.cos(np.radians(dist_pnt['az'])), np.sin(np.radians(dist_pnt['az'])) ] delt_az = np.degrees(np.arccos(np.dot(pt_bo, pt_az))) # Skip if out of azimuth range if not abs(delt_az) <= ext_fov: continue if np.sign(np.cross(pt_bo, pt_az)) >= 0: beam = int(site.maxbeam / 2 + round(delt_az / site.bmsep) - 1) else: beam = int(site.maxbeam / 2 - round(delt_az / site.bmsep)) # Update output found = True out['radars'].append(self.radars[irad]) out['dist'].append(dist_pnt['dist']) out['beam'].append(beam) if found: return out else: return found
def calcFieldPnt(tr_glat, tr_glon, tr_alt, boresight, beam_off, slant_range, adjusted_sr=True, elevation=None, altitude=None, hop=None, model=None, coords='geo', gs_loc="G", max_vh=400.0, fov_dir='front', eval_loc=False): """Calculate coordinates of field point given the radar coordinates and boresight, the pointing direction deviation from boresight and elevation angle, and the field point slant range and altitude. Either the elevation or the altitude must be provided. If none is provided, the altitude is set to 300 km and the elevation evaluated to accomodate altitude and range. Parameters ---------- tr_glat transmitter latitude [degree, N] tr_glon transmitter longitude [degree, E] tr_alt transmitter altitude [km] boresight boresight azimuth [degree, E] beam_off beam azimuthal offset from boresight [degree] slant_range slant range [km] adjusted_sr : Optional(bool) Denotes whether or not the slant range is the total measured slant range (False) or if it has been adjusted to be the slant distance to last ionospheric reflection point (True). (default=True) elevation : Optional[float] elevation angle [degree] (estimated if None) altitude : Optional[float] altitude [km] (default 300 km) hop : Optional[float] backscatter hop (ie 0.5, 1.5 for ionospheric; 1.0, 2.0 for ground) model : Optional[str] IS : for standard ionopsheric scatter projection model (ignores hop) GS : for standard ground scatter projection model (ignores hop) S : for standard projection model (uses hop) E1 : for Chisham E-region 1/2-hop ionospheric projection model F1 : for Chisham F-region 1/2-hop ionospheric projection model F3 : for Chisham F-region 1-1/2-hop ionospheric projection model C : for Chisham projection model (ionospheric only, ignores hop, requires total measured slant range) None : if you trust your elevation or altitude values. more to come coords 'geo' (more to come) gs_loc : (str) Provide last ground scatter location 'G' or ionospheric refraction location 'I' for groundscatter (default='G') max_vh : (float) Maximum height for longer slant ranges in Standard model (default=400) fov_dir : (str) 'front' (default) or 'back'. Specifies fov direction eval_loc : (bool) Evaluate the calcualted location based on reasonable tolerances (True) or accept the first calculation (False). Using True gives better locations, but restricts data at the furthest range gates. (default=False) Returns --------- geo_dict['geoLat'] : (float or np.ndarray) Field point latitude(s) in degrees or np.nan if error geo_dict['geoLon'] : (float or np.ndarray) Field point longitude(s) in degrees or np.nan if error """ from davitpy.utils import Re, geoPack import davitpy.utils.model_vheight as vhm # Only geo is implemented. if coords != "geo": logging.error("Only geographic (geo) is implemented in calcFieldPnt.") return np.nan, np.nan # Use model to get altitude if desired xalt = np.nan calt = None if model is not None: if model in ['IS', 'GS', 'S']: # The standard model can be used with or without an input altitude # or elevation. Returns an altitude that has been adjusted to # comply with common scatter distributions if hop is None: if model == "S": # Default to ionospheric backscatter if hop not specified hop = 0.5 else: hop = 0.5 if model == "IS" else 1.0 xalt = vhm.standard_vhm(slant_range, adjusted_sr=adjusted_sr, max_vh=max_vh, hop=hop, alt=altitude, elv=elevation) else: # The Chisham model uses only the total slant range to determine # altitude based on years of backscatter data at SAS. Performs # significantly better than the standard model for ionospheric # backscatter, not tested on groundscatter if adjusted_sr: logging.error("Chisham model needs total slant range") return np.nan, np.nan # Use Chisham model to calculate virtual height cmodel = None if model == "C" else model xalt, shop = vhm.chisham_vhm(slant_range, cmodel, hop_output=True) # If hop is not known, set using model divisions if hop is None: hop = shop # If hop is greater than 1/2, the elevation angle needs to be # calculated from the ground range rather than the virtual height if hop > 0.5: calt = float(xalt) elif elevation is None or np.isnan(elevation): if hop is None or adjusted_sr: logging.error("Total slant range and hop needed with measurements") return np.nan, np.nan if altitude is None or np.isnan(altitude): logging.error("No observations supplied") return np.nan, np.nan # Adjust slant range if there is groundscatter and the location # desired is the ionospheric reflection point asr = slant_range if hop == np.floor(hop) and gs_loc == "I": asr *= 1.0 - 1.0 / (2.0 * hop) # Adjust altitude if it's unrealistic if asr < altitude: altitude = asr - 10 xalt = altitude # Use model altitude to determine elevation angle and then the location, # or if elevation angle was supplied, find the location if not np.isnan(xalt): # Since we have a modeled or measured altitude, start by setting the # Earth radius below field point to Earth radius at radar (lat, lon, tr_rad) = geoPack.geodToGeoc(tr_glat, tr_glon) rad_pos = tr_rad # Iterate until the altitude corresponding to the calculated elevation # matches the desired altitude. Assumes straight-line path to last # ionospheric scattering point, so adjust slant range if necessary # for groundscatter asr = slant_range shop = hop if not adjusted_sr and hop == np.floor(hop) and gs_loc == "I": asr *= 1.0 - 1.0 / (2.0 * hop) shop = hop - 0.5 # Set safty counter and iteratively determine location maxn = 30 hdel = 100.0 htol = 0.5 if (slant_range >= 800.0 and model != 'GS') or shop > 1.0: htol = 5.0 n = 0 while n < maxn: tr_dist = tr_rad + tr_alt if calt is not None: # Adjust elevation angle for any hop > 1 (Chisham et al. 2008) pos_dist = rad_pos + calt phi = np.arccos((tr_dist**2 + pos_dist**2 - asr**2) / (2.0 * tr_dist * pos_dist)) beta = np.arcsin((tr_dist * np.sin(phi / (shop * 2.0))) / (asr / (shop * 2.0))) tel = np.pi / 2.0 - beta - phi / (shop * 2.0) if xalt == calt: xalt = np.sqrt(tr_rad**2 + asr**2 + 2.0 * asr * tr_rad * np.sin(tel)) - tr_rad tel = np.degrees(tel) else: # pointing elevation (spherical Earth value) [degree] tel = np.arcsin(((rad_pos + xalt)**2 - tr_dist**2 - asr**2) / (2.0 * tr_dist * asr)) tel = np.degrees(tel) # estimate off-array-normal azimuth (because it varies slightly # with elevation) [degree] boff = calcAzOffBore(tel, beam_off, fov_dir=fov_dir) # pointing azimuth taz = boresight + boff # calculate position of field point geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt, dist=asr, el=tel, az=taz) # Update Earth radius rad_pos = geo_dict['distRe'] # stop if the altitude is what we want it to be (or close enough) new_hdel = abs(xalt - geo_dict['distAlt']) if new_hdel <= htol or not eval_loc: break # stop unsuccessfully if the altitude difference hasn't improved if abs(new_hdel - hdel) < 1.0e-3: n = maxn # Prepare the next iteration hdel = new_hdel n += 1 if n >= maxn: estr = 'Accuracy on height calculation ({}) not '.format(htol) estr = '{:s}reached quick enough. Returning nan, nan.'.format(estr) logging.warning(estr) return np.nan, np.nan else: return geo_dict['distLat'], geo_dict['distLon'] elif elevation is not None: # No projection model (i.e., the elevation or altitude is so good that # it gives you the proper projection by simple geometric # considerations). Using no models simply means tracing based on # trustworthy elevation or altitude if hop is None or adjusted_sr: logging.error("Hop and total slant range needed with measurements") return np.nan, np.nan if np.isnan(elevation): logging.error("No observations provided") return np.nan, np.nan shop = hop - 0.5 if hop == np.floor(hop) and gs_loc == "I" else hop asr = slant_range if hop > 0.5 and hop != shop: asr *= 1.0 - 1.0 / (2.0 * hop) # The tracing is done by calcDistPnt boff = calcAzOffBore(elevation, beam_off, fov_dir=fov_dir) geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt, dist=asr, el=elevation, az=boresight + boff) return geo_dict['distLat'], geo_dict['distLon']
def calcFieldPnt(tr_glat, tr_glon, tr_alt, boresight, beam_off, slant_range, adjusted_sr=True, elevation=None, altitude=None, hop=None, model=None, coords='geo', gs_loc="G", max_vh=400.0, fov_dir='front'): """Calculate coordinates of field point given the radar coordinates and boresight, the pointing direction deviation from boresight and elevation angle, and the field point slant range and altitude. Either the elevation or the altitude must be provided. If none is provided, the altitude is set to 300 km and the elevation evaluated to accomodate altitude and range. Parameters ---------- tr_glat transmitter latitude [degree, N] tr_glon transmitter longitude [degree, E] tr_alt transmitter altitude [km] boresight boresight azimuth [degree, E] beam_off beam azimuthal offset from boresight [degree] slant_range slant range [km] adjusted_sr : Optional(bool) Denotes whether or not the slant range is the total measured slant range (False) or if it has been adjusted to be the slant distance to last ionospheric reflection point (True). (default=True) elevation : Optional[float] elevation angle [degree] (estimated if None) altitude : Optional[float] altitude [km] (default 300 km) hop : Optional[float] backscatter hop (ie 0.5, 1.5 for ionospheric; 1.0, 2.0 for ground) model : Optional[str] IS : for standard ionopsheric scatter projection model (ignores hop) GS : for standard ground scatter projection model (ignores hop) S : for standard projection model (uses hop) E1 : for Chisham E-region 1/2-hop ionospheric projection model F1 : for Chisham F-region 1/2-hop ionospheric projection model F3 : for Chisham F-region 1-1/2-hop ionospheric projection model C : for Chisham projection model (ionospheric only, ignores hop, requires total measured slant range) None : if you trust your elevation or altitude values. more to come coords 'geo' (more to come) gs_loc : (str) Provide last ground scatter location 'G' or ionospheric refraction location 'I' for groundscatter (default='G') max_vh : (float) Maximum height for longer slant ranges in Standard model (default=400) fov_dir 'front' (default) or 'back'. Specifies fov direction Returns --------- geo_dict['geoLat'] : (float or np.ndarray) Field point latitude(s) in degrees or np.nan if error geo_dict['geoLon'] : (float or np.ndarray) Field point longitude(s) in degrees or np.nan if error """ from davitpy.utils import Re, geoPack import davitpy.utils.model_vheight as vhm # Only geo is implemented. if coords != "geo": logging.error("Only geographic (geo) is implemented in calcFieldPnt.") return np.nan, np.nan # Use model to get altitude if desired xalt = np.nan calt = None if model is not None: if model in ['IS', 'GS', 'S']: # The standard model can be used with or without an input altitude # or elevation. Returns an altitude that has been adjusted to # comply with common scatter distributions if hop is None: if model == "S": # Default to ionospheric backscatter if hop not specified hop = 0.5 else: hop = 0.5 if model == "IS" else 1.0 xalt = vhm.standard_vhm(slant_range, adjusted_sr=adjusted_sr, max_vh=max_vh, hop=hop, alt=altitude, elv=elevation) else: # The Chisham model uses only the total slant range to determine # altitude based on years of backscatter data at SAS. Performs # significantly better than the standard model for ionospheric # backscatter, not tested on groundscatter if adjusted_sr: logging.error("Chisham model needs total slant range") return np.nan, np.nan # Use Chisham model to calculate virtual height cmodel = None if model == "C" else model xalt, shop = vhm.chisham_vhm(slant_range, cmodel, hop_output=True) # If hop is not known, set using model divisions if hop is None: hop = shop # If hop is greater than 1/2, the elevation angle needs to be # calculated from the ground range rather than the virtual height if hop > 0.5: calt = float(xalt) elif elevation is None or np.isnan(elevation): if hop is None or adjusted_sr: logging.error("Total slant range and hop needed with measurements") return np.nan, np.nan if altitude is None or np.isnan(altitude): logging.error("No observations supplied") return np.nan, np.nan # Adjust slant range if there is groundscatter and the location # desired is the ionospheric reflection point asr = slant_range if hop == np.floor(hop) and gs_loc == "I": asr *= 1.0 - 1.0 / (2.0 * hop) # Adjust altitude if it's unrealistic if asr < altitude: altitude = asr - 10 xalt = altitude # Use model altitude to determine elevation angle and then the location, # or if elevation angle was supplied, find the location if not np.isnan(xalt): # Since we have a modeled or measured altitude, start by setting the # Earth radius below field point to Earth radius at radar (lat, lon, tr_rad) = geoPack.geodToGeoc(tr_glat, tr_glon) rad_pos = tr_rad # Iterate until the altitude corresponding to the calculated elevation # matches the desired altitude. Assumes straight-line path to last # ionospheric scattering point, so adjust slant range if necessary # for groundscatter asr = slant_range shop = hop if not adjusted_sr and hop == np.floor(hop) and gs_loc == "I": asr *= 1.0 - 1.0 / (2.0 * hop) shop = hop - 0.5 # Set safty counter and iteratively determine location maxn = 30 hdel = 100.0 htol = 0.5 if (slant_range >= 800.0 and model != 'GS') or shop > 1.0: htol = 5.0 n = 0 while n < maxn: tr_dist = tr_rad + tr_alt if calt is not None: # Adjust elevation angle for any hop > 1 (Chisham et al. 2008) pos_dist = rad_pos + calt phi = np.arccos((tr_dist**2 + pos_dist**2 - asr**2) / (2.0 * tr_dist * pos_dist)) beta = np.arcsin((tr_dist * np.sin(phi / (shop * 2.0))) / (asr / (shop * 2.0))) tel = np.pi / 2.0 - beta - phi / (shop * 2.0) if xalt == calt: xalt = np.sqrt(tr_rad**2 + asr**2 + 2.0 * asr * tr_rad * np.sin(tel)) - tr_rad tel = np.degrees(tel) else: # pointing elevation (spherical Earth value) [degree] tel = np.arcsin(((rad_pos + xalt)**2 - tr_dist**2 - asr**2) / (2.0 * tr_dist * asr)) tel = np.degrees(tel) # estimate off-array-normal azimuth (because it varies slightly # with elevation) [degree] boff = calcAzOffBore(tel, beam_off, fov_dir=fov_dir) # pointing azimuth taz = boresight + boff # calculate position of field point geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt, dist=asr, el=tel, az=taz) # Update Earth radius rad_pos = geo_dict['distRe'] # stop if the altitude is what we want it to be (or close enough) new_hdel = abs(xalt - geo_dict['distAlt']) if new_hdel <= htol: break # stop unsuccessfully if the altitude difference hasn't improved if abs(new_hdel - hdel) < 1.0e-3: n = maxn # Prepare the next iteration hdel = new_hdel n += 1 if n >= maxn: estr = 'Accuracy on height calculation ({}) not '.format(htol) estr = '{:s}reached quick enough. Returning nan, nan.'.format(estr) logging.warning(estr) return np.nan, np.nan else: return geo_dict['distLat'], geo_dict['distLon'] elif elevation is not None: # No projection model (i.e., the elevation or altitude is so good that # it gives you the proper projection by simple geometric # considerations). Using no models simply means tracing based on # trustworthy elevation or altitude if hop is None or adjusted_sr: logging.error("Hop and total slant range needed with measurements") return np.nan, np.nan if np.isnan(elevation): logging.error("No observations provided") return np.nan, np.nan shop = hop - 0.5 if hop == np.floor(hop) and gs_loc == "I" else hop asr = slant_range if hop > 0.5 and hop != shop: asr *= 1.0 - 1.0 / (2.0 * hop) # The tracing is done by calcDistPnt boff = calcAzOffBore(elevation, beam_off, fov_dir=fov_dir) geo_dict = geoPack.calcDistPnt(tr_glat, tr_glon, tr_alt, dist=asr, el=elevation, az=boresight + boff) return geo_dict['distLat'], geo_dict['distLon']
def calcFieldPnt(tGeoLat, tGeoLon, tAlt, boreSight, boreOffset, slantRange, elevation=None, altitude=None, model=None, coords='geo', fov_dir='front'): """ Calculate coordinates of field point given the radar coordinates and boresight, the pointing direction deviation from boresight and elevation angle, and the field point slant range and altitude. Either the elevation or the altitude must be provided. If none is provided, the altitude is set to 300 km and the elevation evaluated to accomodate altitude and range. **INPUTS**: * **tGeoLat**: transmitter latitude [degree, N] * **tGeoLon**: transmitter longitude [degree, E] * **tAlt**: transmitter altitude [km] * **boreSight**: boresight azimuth [degree, E] * **boreOffset**: offset from boresight [degree] * **slantRange**: slant range [km] * **elevation**: elevation angle [degree] (estimated if None) * **altitude**: altitude [km] (default 300 km) * **model**: * **'IS'**: for ionopsheric scatter projection model * **'GS'**: for ground scatter projection model * **None**: if you trust your elevation or altitude data * ... more to come * **coords**: 'geo' (more to come) * **fov_dir**: 'front' (default) or 'back'. Specifies fov direction """ from math import asin from davitpy.utils import Re, geoPack # Make sure we have enough input stuff # if (not model) and (not elevation or not altitude): model = 'IS' # Only geo is implemented. assert(coords == "geo"), \ "Only geographic (geo) is implemented in calcFieldPnt." # Now let's get to work # Classic Ionospheric/Ground scatter projection model if model in ['IS','GS']: # Make sure you have altitude (even if it isn't used), because these # 2 projection models rely on it if not elevation and not altitude: # Set default altitude to 300 km altitude = 300.0 elif elevation and not altitude: # If you have elevation but not altitude, then you calculate # altitude, and elevation will be adjusted anyway altitude = np.sqrt(Re**2 + slantRange**2 + 2. * slantRange * Re * np.sin(np.radians(elevation))) - Re # Now you should have altitude (and maybe elevation too, but it won't # be used in the rest of the algorithm). Adjust altitude so that it # makes sense with common scatter distribution xAlt = altitude if model == 'IS': if altitude > 150. and slantRange <= 600.: xAlt = 115. elif altitude > 150. and slantRange > 600. and slantRange <= 800.: xAlt = 115. + (slantRange - 600.) / 200. * (altitude - 115.) elif model == 'GS': if altitude > 150. and slantRange <= 300: xAlt = 115. elif altitude > 150. and slantRange > 300. and slantRange <= 500.: xAlt = 115. + (slantRange - 300.) / 200. * (altitude - 115.) if slantRange < 150.: xAlt = slantRange / 150. * 115. # To start, set Earth radius below field point to Earth radius at radar (lat,lon,tRe) = geoPack.geodToGeoc(tGeoLat, tGeoLon) RePos = tRe # Iterate until the altitude corresponding to the calculated elevation # matches the desired altitude n = 0 # safety counter while True: # pointing elevation (spherical Earth value) [degree] tel = np.degrees(asin(((RePos+xAlt)**2 - (tRe+tAlt)**2 - slantRange**2)/(2. * (tRe+tAlt) * slantRange))) # estimate off-array-normal azimuth (because it varies slightly # with elevation) [degree] boff = calcAzOffBore(tel, boreOffset, fov_dir=fov_dir) # pointing azimuth taz = boreSight + boff # calculate position of field point dictOut = geoPack.calcDistPnt(tGeoLat, tGeoLon, tAlt, dist=slantRange, el=tel, az=taz) # Update Earth radius RePos = dictOut['distRe'] # stop if the altitude is what we want it to be (or close enough) n += 1 if abs(xAlt - dictOut['distAlt']) <= 0.5 or n > 2: return dictOut['distLat'], dictOut['distLon'] break # No projection model (i.e., the elevation or altitude is so good that it # gives you the proper projection by simple geometric considerations) elif not model: # Using no models simply means tracing based on trustworthy elevation # or altitude if not altitude: altitude = np.sqrt(Re**2 + slantRange**2 + 2. * slantRange * Re * np.sin(np.radians(elevation))) - Re if not elevation: if(slantRange < altitude): altitude = slantRange - 10 elevation = np.degrees(asin(((Re+altitude)**2 - (Re+tAlt)**2 - slantRange**2) / (2. * (Re + tAlt) * slantRange))) # The tracing is done by calcDistPnt dict = geoPack.calcDistPnt(tGeoLat, tGeoLon, tAlt, dist=slantRange, el=elevation, az=boreSight+boreOffset) return dict['distLat'], dict['distLon']
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 Parameters ---------- 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 : Optional[datetime.datetime object] python datetime object (defaults to today) Returns ------- dict A dictionnary with keys: radars : a list of radar objects (:class:`radar`) dist: a list of distance from radar to given point (1 perradar) 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 davitpy.utils import geoPack as geo import numpy as np 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 dist_pnt = geo.calcDistPnt(site.geolat, site.geolon, site.alt, distLat=lat, distLon=lon, distAlt=300.) # Skip if radar too far if dist_pnt['dist'] > distMax: continue # minAz = (site.boresite % 360.)-abs(site.bmsep)*site.maxbeam/2 # maxAz = (site.boresite % 360.)+abs(site.bmsep)*site.maxbeam/2 ext_fov = abs(site.bmsep) * site.maxbeam / 2 pt_bo = [np.cos(np.radians(site.boresite)), np.sin(np.radians(site.boresite))] pt_az = [np.cos(np.radians(dist_pnt['az'])), np.sin(np.radians(dist_pnt['az']))] delt_az = np.degrees(np.arccos(np.dot(pt_bo, pt_az))) # Skip if out of azimuth range if not abs(delt_az) <= ext_fov: continue if np.sign(np.cross(pt_bo, pt_az)) >= 0: beam = int(site.maxbeam / 2 + round(delt_az / site.bmsep) - 1) else: beam = int(site.maxbeam / 2 - round(delt_az / site.bmsep)) # Update output found = True out['radars'].append(self.radars[irad]) out['dist'].append(dist_pnt['dist']) out['beam'].append(beam) if found: return out else: return found
def calcFieldPnt(tGeoLat, tGeoLon, tAlt, boreSight, boreOffset, slantRange, elevation=None, altitude=None, model=None, coords='geo', fov_dir='front'): """Calculate coordinates of field point given the radar coordinates and boresight, the pointing direction deviation from boresight and elevation angle, and the field point slant range and altitude. Either the elevation or the altitude must be provided. If none is provided, the altitude is set to 300 km and the elevation evaluated to accomodate altitude and range. Parameters ---------- tGeoLat transmitter latitude [degree, N] tGeoLon transmitter longitude [degree, E] tAlt transmitter altitude [km] boreSight boresight azimuth [degree, E] boreOffset offset from boresight [degree] slantRange slant range [km] elevation : Optional[float] elevation angle [degree] (estimated if None) altitude : Optional[float] altitude [km] (default 300 km) model : Optional[str] IS : for ionopsheric scatter projection model GS : for ground scatter projection model None : if you trust your elevation or altitude values. more to come coords 'geo' (more to come) fov_dir 'front' (default) or 'back'. Specifies fov direction """ from math import asin from davitpy.utils import Re, geoPack # Make sure we have enough input stuff # if (not model) and (not elevation or not altitude): model = 'IS' # Only geo is implemented. assert(coords == "geo"), \ "Only geographic (geo) is implemented in calcFieldPnt." # Now let's get to work # Classic Ionospheric/Ground scatter projection model if model in ['IS', 'GS']: # Make sure you have altitude (even if it isn't used), because these # 2 projection models rely on it if not elevation and not altitude: # Set default altitude to 300 km altitude = 300.0 elif elevation and not altitude: # If you have elevation but not altitude, then you calculate # altitude, and elevation will be adjusted anyway altitude = np.sqrt(Re**2 + slantRange**2 + 2. * slantRange * Re * np.sin(np.radians(elevation))) - Re # Now you should have altitude (and maybe elevation too, but it won't # be used in the rest of the algorithm). Adjust altitude so that it # makes sense with common scatter distribution xAlt = altitude if model == 'IS': if altitude > 150. and slantRange <= 600.: xAlt = 115. elif altitude > 150. and slantRange > 600. and slantRange <= 800.: xAlt = 115. + (slantRange - 600.) / 200. * (altitude - 115.) elif model == 'GS': if altitude > 150. and slantRange <= 300: xAlt = 115. elif altitude > 150. and slantRange > 300. and slantRange <= 500.: xAlt = 115. + (slantRange - 300.) / 200. * (altitude - 115.) if slantRange < 150.: xAlt = slantRange / 150. * 115. # To start, set Earth radius below field point to Earth radius at radar (lat, lon, tRe) = geoPack.geodToGeoc(tGeoLat, tGeoLon) RePos = tRe # Iterate until the altitude corresponding to the calculated elevation # matches the desired altitude n = 0 # safety counter while True: # pointing elevation (spherical Earth value) [degree] tel = np.degrees( asin(((RePos + xAlt)**2 - (tRe + tAlt)**2 - slantRange**2) / (2. * (tRe + tAlt) * slantRange))) # estimate off-array-normal azimuth (because it varies slightly # with elevation) [degree] boff = calcAzOffBore(tel, boreOffset, fov_dir=fov_dir) # pointing azimuth taz = boreSight + boff # calculate position of field point dictOut = geoPack.calcDistPnt(tGeoLat, tGeoLon, tAlt, dist=slantRange, el=tel, az=taz) # Update Earth radius RePos = dictOut['distRe'] # stop if the altitude is what we want it to be (or close enough) n += 1 if abs(xAlt - dictOut['distAlt']) <= 0.5 or n > 2: return dictOut['distLat'], dictOut['distLon'] break # No projection model (i.e., the elevation or altitude is so good that it # gives you the proper projection by simple geometric considerations) elif not model: # Using no models simply means tracing based on trustworthy elevation # or altitude if not altitude: altitude = np.sqrt(Re**2 + slantRange**2 + 2. * slantRange * Re * np.sin(np.radians(elevation))) - Re if not elevation: if (slantRange < altitude): altitude = slantRange - 10 elevation = np.degrees( asin(((Re + altitude)**2 - (Re + tAlt)**2 - slantRange**2) / (2. * (Re + tAlt) * slantRange))) # The tracing is done by calcDistPnt dict = geoPack.calcDistPnt(tGeoLat, tGeoLon, tAlt, dist=slantRange, el=elevation, az=boreSight + boreOffset) return dict['distLat'], dict['distLon']
def lat_distribution(tdiff, ref_lat, hard, phi0, phi0e, fovflg, bm_az, tfreq, dist): '''Returns the squared sum of the difference between the mean and the specified latitude and the standard deviation of the backscatter latitudes Parameters ----------- tdiff : (float) tdiff in microseconds ref_lat : (float) reference latitude in degrees hard : (class davitpy.pydarn.radar.radStruct.site) radar hardware data phi0 : (list) phase lags in radians phi0e : (list) phase lag errors in radians fovflg : (list) field-of-view flags bm_az : (list) Azimuthal angle between the beam and the radar boresite at zero elevation (radians) tfreq : (list) transmission frequencies in kHz dist : (list) slant distance from radar to ionospheric reflection point in km Returns --------- ff : (float) standard deviation of the latitude distribution Notes ------- Calculates equation 3 in Burrell et al. (2016) ''' import davitpy.utils.geoPack as geo import davitpy.pydarn.proc.fov.calc_elevation as celv import davitpy.pydarn.radar.radFov as rfov # Ensure that TDIFF is a single number and not a list or array element try: len(tdiff) tdiff = tdiff[0] except: pass if not isinstance(tdiff, float): tdiff = float(tdiff) # Calculate the elevation and latitude try: # elevation is in radians elv = np.array( celv.calc_elv_list(phi0, phi0e, fovflg, bm_az, tfreq, hard.interfer, tdiff)) elv = np.degrees(elv) # Correction to boresight azimuth due to elevation angle fov_dir = {1: "front", -1: "back"} az = np.array([ rfov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i, e in enumerate(elv) ]) # Calculate location loc = geo.calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(dist)) lat = loc['distLat'][~np.isnan(loc['distLat'])] # Remove nan #-------------------------------------------------------------------- # When the distribution is approximately gaussian, the error depends # on the location of the peak and the amount of spread. Testing the # standard deviation prevents the formation of a bimodal distribution # who each flank the desired location # # Sum the errors ff = np.nan if len(lat) == 0 else np.sqrt((lat.mean() - ref_lat)**2 + lat.std()**2) except: ff = np.nan # Return the square root of the summed squared errors return ff
def lat_distribution(tdiff, ref_lat, hard, phi0, phi0e, fovflg, bm_az, tfreq, dist): '''Returns the squared sum of the difference between the mean and the specified latitude and the standard deviation of the backscatter latitudes Parameters ----------- tdiff : (float) tdiff in microseconds ref_lat : (float) reference latitude in degrees hard : (class davitpy.pydarn.radar.radStruct.site) radar hardware data phi0 : (list) phase lags in radians phi0e : (list) phase lag errors in radians fovflg : (list) field-of-view flags bm_az : (list) Azimuthal angle between the beam and the radar boresite at zero elevation (radians) tfreq : (list) transmission frequencies in kHz dist : (list) slant distance from radar to ionospheric reflection point in km Returns --------- ff : (float) standard deviation of the latitude distribution Notes ------- Calculates equation 3 in Burrell et al. (2016) ''' import davitpy.utils.geoPack as geo import davitpy.pydarn.proc.fov.calc_elevation as celv import davitpy.pydarn.radar.radFov as rfov # Ensure that TDIFF is a single number and not a list or array element try: len(tdiff) tdiff = tdiff[0] except: pass if not isinstance(tdiff, float): tdiff = float(tdiff) # Calculate the elevation and latitude try: # elevation is in radians elv = np.array(celv.calc_elv_list(phi0, phi0e, fovflg, bm_az, tfreq, hard.interfer, tdiff)) elv = np.degrees(elv) # Correction to boresight azimuth due to elevation angle fov_dir = {1:"front", -1:"back"} az = np.array([rfov.calcAzOffBore(e, np.degrees(bm_az[i]), fov_dir[fovflg[i]]) + hard.boresite for i,e in enumerate(elv)]) # Calculate location loc = geo.calcDistPnt(hard.geolat, hard.geolon, hard.alt, az=az, el=elv, dist=np.array(dist)) lat = loc['distLat'][~np.isnan(loc['distLat'])] # Remove nan #-------------------------------------------------------------------- # When the distribution is approximately gaussian, the error depends # on the location of the peak and the amount of spread. Testing the # standard deviation prevents the formation of a bimodal distribution # who each flank the desired location # # Sum the errors ff = np.nan if len(lat) == 0 else np.sqrt((lat.mean() - ref_lat)**2 + lat.std()**2) except: ff = np.nan # Return the square root of the summed squared errors return ff