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(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 calc_virtual_height(beam, radius, elv=list(), elv_attr="elv", dist=list(), dist_attr="slist", dist_units=None, dist_adjust=False, hop=list(), hop_attr="hop", model=None, max_vh=400.0): """Calculate the virtual height for a specified slant range using elevation or a model Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time radius : (float) Earth radius in km elv : (list or numpy.ndarray) List containing elevation in degrees, or nothing to load the elevation from the beam (default=list()) elv_attr : (string) Name of beam attribute containing the elevation (default="elv") dist : (list or numpy.ndarray) List containing the slant distance from the radar to the reflection location of the backscatter, or nothing to load the distance from the beam. (default=list()) dist_attr : (str) Name of beam attribute containing the slant distance. (default="slist") dist_units : (string or NoneType) Units of the slant distance to backscatter location data. May supply "km", "m", or None. None indicates that the distance is in range gate bins. (default=None) dist_adjust : (bool) Denotes whether or not the slant distance has been adjusted for hop. (default=False) hop : (list or numpy.ndarray) List containing the hop for each point. If empty, will assume 0.5-hop (default=list()) hop_attr : (string) Name of beam attribute containing the hop (default="hop") model : (str or NoneType) Calculate virtual height using elevation (None) or model? (default=None) Available models ---------------- 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 only total measured slant range) max_vh : (float) Maximum height for longer slant ranges in Standard model (default=400) Returns -------- height : (numpy.array) An array of floats of the same size as the myBeam.fit.slist list, containing the virtual height for each range gate or NaN if a virtual height could not be calculated Notes -------- Specifying a single earth radius introduces additional error into the resulting heights. If the terrestrial radius at the radar location is used, this error is on the order of 0.01-0.1 km (much smaller than the difference between the real and virtual height). The available models are only """ import davitpy.pydarn.sdio as sdio import davitpy.utils.model_vheight as vhm s_model = ["S", "IS", "GS"] c_model = ["C", "F1", "F3", "E1"] #--------------------------------- # Check the input if not isinstance(beam, sdio.radDataTypes.beamData): logging.error('the beam must be a beamData class') return None if not isinstance(radius, float): logging.error('the radius must be a float') return None if not (model is None or model in s_model or model in c_model): logging.error('unknown model [{:}]'.format(model)) return None #--------------------------------- # Load the slant range/distance if len(dist) == 0 or (len(elv) > 0 and len(elv) != len(dist)): try: dist = getattr(beam.fit, dist_attr) if dist is None: logging.error('no range/distance data available') return None except: logging.error('no range attribute [{:s}]'.format(dist_attr)) return None if len(dist) == 0: logging.error("unable to calculate h' without slant range") return None #--------------------------------- # Load elevation if not model in c_model: estr = None if len(elv) == 0 or len(elv) != len(dist): try: elv = getattr(beam.fit, elv_attr) if model is None: if elv is None: logging.error('no elevation available') return None elif len(dist) != len(elv): logging.error('unequal distance and elevation arrays') return None else: if elv is None: estr = 'no elevation available' elif len(dist) != len(elv): estr = 'unequal distance and elevation arrays' except: estr = 'no elevation attribute [{:s}]'.format(elv_attr) if model is None: logging.error(estr) return None if estr is not None: logging.warn(estr) elv = [None for d in dist] if model is None and (len(elv) == 0 or len(elv) != len(dist)): logging.error("unable to load matching elevation and distance lists") return None #--------------------------------- # Load hop if len(hop) == 0 or (len(dist) > 0 and len(hop) != len(dist)): estr = None try: hop = getattr(beam.fit, hop_attr) if hop is None: estr = 'no hop available' except: estr = 'no hop attribute [{:s}]'.format(hop_attr) if estr is not None: logging.warn(estr) if model in c_model: hop = [None for d in dist] else: hop = [0.5 for d in dist] #--------------------------------------------------------------------- # If the hop attribute was actually the groundscatter flag, adjust if hop_attr == "gflg": hop = [1.0 if gg == 1 else 0.5 for gg in hop] #--------------------------------------------------------------------- # Ensure that the range/distance (and error) are in km if dist_units is None: # Convert from range gates to km dist = list(5.0e-10 * scicon.c * (np.array(dist) * beam.prm.smsep + beam.prm.lagfr)) elif dist_units is "m": # Convert from meters to km dist = [d / 1000.0 for d in dist] elif dist_units is not "km": logging.error('unknown range unit [{:s}]'.format(dist_units)) return None #----------------------------------------------------------------------- # Cycle through the beams and elevations, calculating the virtual height height = np.empty(shape=(len(dist), ), dtype=float) * np.nan for i, d in enumerate(dist): if model in s_model: # Calculate height using standard model hh = hop[i] if model != "S": hh = 0.5 if model == "IS" else 1.0 height[i] = vhm.standard_vhm(d, adjusted_sr=dist_adjust, max_vh=max_vh, hop=hh, elv=elv[i]) elif model in c_model: # Calculate height using Chisham model cm = None if model == "C" else model height[i] = vhm.chisham_vhm(d, vhmtype=cm, hop_output=False) elif model is None and not np.isnan(elv[i]): # Calculate height by assuming a spherical earth and solving the # law of cosines for an obtuse triangle with sides radius, # radius + height, and distance, where the angle between the side of # length distance and radius + height is equal to 90 deg - elevation if not dist_adjust: d /= hop[i] * 2.0 hsqrt = np.sqrt(d**2 + radius**2 + 2.0 * d * radius * np.sin(np.radians(elv[i]))) height[i] = hsqrt - radius return height
def calc_virtual_height(beam, radius, elv=list(), elv_attr="elv", dist=list(), dist_attr="slist", dist_units=None, dist_adjust=False, hop=list(), hop_attr="hop", model=None, max_vh=400.0): """Calculate the virtual height for a specified slant range using elevation or a model Parameters ----------- beam : (class `pydarn.sdio.radDataTypes.beamData`) Data for a single radar and beam along all range gates at a given time radius : (float) Earth radius in km elv : (list or numpy.ndarray) List containing elevation in degrees, or nothing to load the elevation from the beam (default=list()) elv_attr : (string) Name of beam attribute containing the elevation (default="elv") dist : (list or numpy.ndarray) List containing the slant distance from the radar to the reflection location of the backscatter, or nothing to load the distance from the beam. (default=list()) dist_attr : (str) Name of beam attribute containing the slant distance. (default="slist") dist_units : (string or NoneType) Units of the slant distance to backscatter location data. May supply "km", "m", or None. None indicates that the distance is in range gate bins. (default=None) dist_adjust : (bool) Denotes whether or not the slant distance has been adjusted for hop. (default=False) hop : (list or numpy.ndarray) List containing the hop for each point. If empty, will assume 0.5-hop (default=list()) hop_attr : (string) Name of beam attribute containing the hop (default="hop") model : (str or NoneType) Calculate virtual height using elevation (None) or model? (default=None) Available models ---------------- 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 only total measured slant range) max_vh : (float) Maximum height for longer slant ranges in Standard model (default=400) Returns -------- height : (numpy.array) An array of floats of the same size as the myBeam.fit.slist list, containing the virtual height for each range gate or NaN if a virtual height could not be calculated Notes -------- Specifying a single earth radius introduces additional error into the resulting heights. If the terrestrial radius at the radar location is used, this error is on the order of 0.01-0.1 km (much smaller than the difference between the real and virtual height). The available models are only """ import davitpy.pydarn.sdio as sdio import davitpy.utils.model_vheight as vhm s_model = ["S", "IS", "GS"] c_model = ["C", "F1", "F3", "E1"] #--------------------------------- # Check the input if not isinstance(beam, sdio.radDataTypes.beamData): logging.error('the beam must be a beamData class') return None if not isinstance(radius, float): logging.error('the radius must be a float') return None if not (model is None or model in s_model or model in c_model): logging.error('unknown model [{:}]'.format(model)) return None #--------------------------------- # Load the slant range/distance if len(dist) == 0 or (len(elv) > 0 and len(elv) != len(dist)): try: dist = getattr(beam.fit, dist_attr) if dist is None: logging.error('no range/distance data available') return None except: logging.error('no range attribute [{:s}]'.format(dist_attr)) return None if len(dist) == 0: logging.error("unable to calculate h' without slant range") return None #--------------------------------- # Load elevation if not model in c_model: estr = None if len(elv) == 0 or len(elv) != len(dist): try: elv = getattr(beam.fit, elv_attr) if model is None: if elv is None: logging.error('no elevation available') return None elif len(dist) != len(elv): logging.error('unequal distance and elevation arrays') return None else: if elv is None: estr = 'no elevation available' elif len(dist) != len(elv): estr = 'unequal distance and elevation arrays' except: estr = 'no elevation attribute [{:s}]'.format(elv_attr) if model is None: logging.error(estr) return None if estr is not None: logging.warn(estr) elv = [None for d in dist] if model is None and (len(elv) == 0 or len(elv) != len(dist)): logging.error("unable to load matching elevation and distance lists") return None #--------------------------------- # Load hop if len(hop) == 0 or (len(dist) > 0 and len(hop) != len(dist)): estr = None try: hop = getattr(beam.fit, hop_attr) if hop is None: estr = 'no hop available' except: estr = 'no hop attribute [{:s}]'.format(hop_attr) if estr is not None: logging.warn(estr) if model in c_model: hop = [None for d in dist] else: hop = [0.5 for d in dist] #--------------------------------------------------------------------- # If the hop attribute was actually the groundscatter flag, adjust if hop_attr == "gflg": hop = [1.0 if gg == 1 else 0.5 for gg in hop] #--------------------------------------------------------------------- # Ensure that the range/distance (and error) are in km if dist_units is None: # Convert from range gates to km dist = list(5.0e-10 * scicon.c * (np.array(dist) * beam.prm.smsep + beam.prm.lagfr)) elif dist_units is "m": # Convert from meters to km dist = [d / 1000.0 for d in dist] elif dist_units is not "km": logging.error('unknown range unit [{:s}]'.format(dist_units)) return None #----------------------------------------------------------------------- # Cycle through the beams and elevations, calculating the virtual height height = np.empty(shape=(len(dist),), dtype=float) * np.nan for i,d in enumerate(dist): if model in s_model: # Calculate height using standard model hh = hop[i] if model != "S": hh = 0.5 if model == "IS" else 1.0 height[i] = vhm.standard_vhm(d, adjusted_sr=dist_adjust, max_vh=max_vh, hop=hh, elv=elv[i]) elif model in c_model: # Calculate height using Chisham model cm = None if model == "C" else model height[i] = vhm.chisham_vhm(d, vhmtype=cm, hop_output=False) elif model is None and not np.isnan(elv[i]): # Calculate height by assuming a spherical earth and solving the # law of cosines for an obtuse triangle with sides radius, # radius + height, and distance, where the angle between the side of # length distance and radius + height is equal to 90 deg - elevation if not dist_adjust: d /= hop[i] * 2.0 hsqrt = np.sqrt(d**2 + radius**2 + 2.0 * d * radius * np.sin(np.radians(elv[i]))) height[i] = hsqrt - radius return height