def MakeLatLngPairs(n_pairs, dmin_meters=0, dmax_meters=150000., lat_min=32.8, lat_max=41.0, lng_min=-117.0, lng_max=-80): """Generates random pairs of points within distance. Each pair of points are within a bounding box, and at at controllable distance from each other. Inputs: n_pairs: number of pairs to generate. d_min_meters, d_max_meters: range of distance for each pair of points (default 0 to 150km) lat_min, lat_max, lng_min, lng_max: bounding box that contain pair of points By default uses a bounding box fully included in continental US border (but not covering all US). Returns: an array of tuple (lat1,lng1, lat2, lng2) of size n_pairs """ pair_points = [] while len(pair_points) < n_pairs: lat1 = np.random.uniform(lat_min, lat_max) lng1 = np.random.uniform(lng_min, lng_max) bearing = np.random.uniform(0., 360.) dist = np.random.uniform(dmin_meters, dmax_meters) lat2, lng2, _ = vincenty.GeodesicPoint(lat1, lng1, dist, bearing) if (lat2 < lat_min or lat2 > lat_max or lng2 < lng_min or lng2 > lng_max): continue pair_points.append((lat1, lng1, lat2, lng2)) return pair_points
def test_computeFssBlocking(self): # Mock things propag and FSS antenna. -70dBm at 30km wf_itm.CalcItmPropagationLoss = testutils.FakePropagationPredictor( dist_type='REAL', factor=1.0, offset=70 - 30.0) antenna.GetFssAntennaGains = mock.create_autospec( antenna.GetFssAntennaGains, return_value=2.8) # Create FSS and a CBSD at 30km fss_point, fss_info, _ = data.getFssInfo(TestAggInterf.fss_record) fss_freq_range = (3650e6, 3750e6) cbsd_lat, cbsd_lon, _ = vincenty.GeodesicPoint(fss_point[1], fss_point[0], 30, 0) cbsd = entities.CBSD_TEMPLATE_CAT_A_OUTDOOR._replace( latitude=cbsd_lat, longitude=cbsd_lon) grant = entities.ConvertToCbsdGrantInfo([cbsd], 3640, 3680)[0] constraint = data.ProtectionConstraint( fss_point[1], fss_point[0], 3550e6, fss_freq_range[0], data.ProtectedEntityType.FSS_BLOCKING) itf = interf.computeInterferenceFssBlocking(grant, constraint, fss_info, grant.max_eirp) self.assertAlmostEqual( itf, 20 + # EIRP/MHZ 10 + # 10MHz effective bandwidth -70 # pathloss + 2.8 # FSS antenna gain - 3.1634, # FSS mask loss for adjacent 10MHz 4)
def test_SimplePpaCircle(self): # Configuring for -96dBm circle at 16km includes wf_hybrid.CalcHybridPropagationLoss = testutils.FakePropagationPredictor( dist_type='REAL', factor=1.0, offset=(96+30-0.1) - 16.0) expected_ppa = sgeo.Polygon( [vincenty.GeodesicPoint( TestPpa.devices[0]['installationParam']['latitude'], TestPpa.devices[0]['installationParam']['longitude'], dist_km=16.0, bearing=angle)[1::-1] # reverse to lng,lat for angle in xrange(360)]) ppa_zone = ppa.PpaCreationModel(TestPpa.devices, TestPpa.pal_records) ppa_zone = json.loads(ppa_zone) self.assertAlmostSamePolygon( utils.ToShapely(ppa_zone), expected_ppa, 0.001)
def GenerateCbsdList(n_cbsd, template_cbsd, ref_latitude, ref_longitude, min_distance_km=1, max_distance_km=150, min_angle=0, max_angle=360): """Generate a random list of CBSDs from a CBSD template. The CBSD are randomly generated in a circular sector around a reference point, and using a CBSD template. If the template contains a list of azimuth, then multi-sector CBSD are built with the given azimuths. Otherwise a random azimuth is used. Inputs: n_cbsd: Number of CBSD to generate. template_cbsd: A |Cbsd| namedtuple used as a template for all non location parameters. ref_latitude: Reference point latitude (degrees). ref_longitude: Reference point longitude (degrees). min_distance_km: Minimum distance of CBSD from central point (km). Default 1km. max_distance_km: Maximum distance of CBSD from central point (km). Default 150km. min_angle: Minimum angle of the circular sector (degrees). Default 0 max_angle: Maximum angle of the circular sector (degrees). Default 360 Note that 0 degree is the north, and the angle goes clockwise. Returns: a list of |Cbsd| namedtuple """ distances = np.random.uniform(min_distance_km, max_distance_km, n_cbsd) bearings = np.random.uniform(min_angle, max_angle, n_cbsd) t = template_cbsd azimuths = (t.antenna_azimuth if isinstance(t.antenna_azimuth, list) else [t.antenna_azimuth]) cbsds = [] for k in xrange(n_cbsd): lat, lng, _ = vincenty.GeodesicPoint(ref_latitude, ref_longitude, distances[k], bearings[k]) for azimuth in azimuths: cbsd = Cbsd(lat, lng, t.height_agl, t.is_indoor, t.category, t.eirp_dbm_mhz, int(azimuth + np.random.uniform(0, 360)) % 360, t.antenna_beamwidth, t.antenna_gain) cbsds.append(cbsd) return cbsds
def _GetPolygon(device): """Returns the PPA contour for a single CBSD device, as a shapely polygon.""" install_param = device['installationParam'] eirp_capability = install_param.get( 'eirpCapability', MAX_ALLOWABLE_EIRP_PER_10_MHZ_CAT_A if device['cbsdCategory'] == 'A' else MAX_ALLOWABLE_EIRP_PER_10_MHZ_CAT_B) # Compute all the Points in 0-359 every 200m up to 40km distances = np.arange(0.2, 40.1, 0.2) azimuths = np.arange(0.0, 360.0) latitudes, longitudes, _ = zip(*[ vincenty.GeodesicPoints(install_param['latitude'], install_param['longitude'], distances, azimuth) for azimuth in azimuths ]) # Compute the Gain for all Direction # Note: some parameters are optional for catA, so falling back to None (omni) then antenna_gains = antenna.GetStandardAntennaGains( azimuths, install_param['antennaAzimuth'] if 'antennaAzimuth' in install_param else None, install_param['antennaBeamwidth'] if 'antennaBeamwidth' in install_param else None, install_param['antennaGain']) # Get the Nlcd Region Type for Cbsd cbsd_region_code = drive.nlcd_driver.GetLandCoverCodes( install_param['latitude'], install_param['longitude']) cbsd_region_type = nlcd.GetRegionType(cbsd_region_code) # Compute the Path Loss, and contour based on Gain and Path Loss Comparing with Threshold # Smoothing Contour using Hamming Filter contour_dists_km = _HammingFilter([ _CalculateDbLossForEachPointAndGetContour(install_param, eirp_capability, ant_gain, cbsd_region_type, radial_lats, radial_lons) for radial_lats, radial_lons, ant_gain in zip(latitudes, longitudes, antenna_gains) ]) # Generating lat, lon for Contour contour_lats, contour_lons, _ = zip(*[ vincenty.GeodesicPoint(install_param['latitude'], install_param['longitude'], dists, az) for dists, az in zip(contour_dists_km, azimuths) ]) return sgeo.Polygon(zip(contour_lons, contour_lats)).buffer(0)
def CalcItmPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=0.5, freq_mhz=3625., its_elev=None, is_height_cbsd_amsl=False, return_internals=False): """Implements the WinnForum-compliant ITM point-to-point propagation model. According to WinnForum spec R2-SGN-17, R2-SGN-22 and R2-SGN-5 to 10. One can use this routine in 3 ways: reliability = -1 : to get the average path loss reliability in [0,1] : to get a pathloss for given quantile sequence of reliabilities: to get an array of pathloss. Used to obtain inverse CDF of the pathloss. Inputs: lat_cbsd, lon_cbsd, height_cbsd: Lat/lon (deg) and height AGL (m) of CBSD lat_rx, lon_rx, height_rx: Lat/lon (deg) and height AGL (m) of Rx point cbsd_indoor: CBSD indoor status - Default=False. reliability: Reliability. Default is 0.5 (median value) Different options: value in [0,1]: returns the CDF quantile -1: returns the mean path loss iterable sequence: returns a list of path losses freq_mhz: Frequency (MHz). Default is mid-point of band. its_elev: Optional profile to use (in ITM format). Default=None If not specified, it is extracted from the terrain. is_height_cbsd_amsl: If True, the CBSD height shall be considered as AMSL (Average mean sea level). return_internals: If True, returns internal variables. Returns: A namedtuple of: db_loss Path Loss in dB, either a scalar if reliability is scalar or a list of path losses if reliability is an iterable. incidence_angles: A namedtuple of hor_cbsd: Horizontal departure angle (bearing) from CBSD to Rx ver_cbsd: Vertical departure angle at CBSD hor_rx: Horizontal incidence angle (bearing) from Rx to CBSD ver_rx: Vertical incidence angle at Rx internals: A dictionary of internal data for advanced analysis (only if return_internals=True): itm_err_num: ITM error code from ItmErrorCode (see GetInfoOnItmCode). itm_str_mode: String containing description of dominant prop mode. dist_km: Distance between end points (km). prof_d_km ndarray of distances (km) - x values to plot terrain. prof_elev ndarray of terrain heightsheights (m) - y values to plot terrain, Raises: Exception if input parameters invalid or out of range. """ # Case of same points if (lat_cbsd == lat_rx and lon_cbsd == lon_rx): return _PropagResult(db_loss=0 if np.isscalar(reliability) else [0] * len(reliability), incidence_angles=_IncidenceAngles(0, 0, 0, 0), internals=None) # Sanity checks on input parameters if freq_mhz < 40.0 or freq_mhz > 10000: raise Exception('Frequency outside range [40MHz - 10GHz]') if is_height_cbsd_amsl: altitude_cbsd = drive.terrain_driver.GetTerrainElevation( lat_cbsd, lon_cbsd) height_cbsd = height_cbsd - altitude_cbsd # Ensure minimum height of 1 meter if height_cbsd < 1: height_cbsd = 1 if height_rx < 1: height_rx = 1 # Internal ITM parameters are always set to following values in WF version: confidence = 0.5 # Confidence (always 0.5) dielec = 25. # Dielectric constant (always 25.) conductivity = 0.02 # Conductivity (always 0.02) polarization = 1 # Polarization (always vertical = 1) mdvar = 13 # Get the terrain profile, using Vincenty great circle route, and WF # standard (bilinear interp; 1500 pts for all distances over 45 km) if its_elev is None: its_elev = drive.terrain_driver.TerrainProfile(lat1=lat_cbsd, lon1=lon_cbsd, lat2=lat_rx, lon2=lon_rx, target_res_meter=30., do_interp=True, max_points=1501) # Find the midpoint of the great circle path dist_km, bearing_cbsd, bearing_rx = vincenty.GeodesicDistanceBearing( lat_cbsd, lon_cbsd, lat_rx, lon_rx) latmid, lonmid, _ = vincenty.GeodesicPoint(lat_cbsd, lon_cbsd, dist_km / 2., bearing_cbsd) # Determine climate value, based on ITU-R P.617 method: climate = drive.climate_driver.TropoClim(latmid, lonmid) # If the common volume lies over the sea, the climate value to use depends # on the climate values at either end. A simple min() function should # properly implement the logic, since water is the max. if climate == 7: climate = min(drive.climate_driver.TropoClim(lat_cbsd, lon_cbsd), drive.climate_driver.TropoClim(lat_rx, lon_rx)) # Look up the refractivity at the path midpoint, if not explicitly provided refractivity = drive.refract_driver.Refractivity(latmid, lonmid) # Call ITM prop loss. reliabilities = reliability do_avg = False if np.isscalar(reliabilities) and reliability == -1: # Pathloss mean: average the value for 1% to 99% included reliabilities = np.arange(0.01, 1.0, 0.01) do_avg = True db_loss, ver_cbsd, ver_rx, str_mode, err_num = itm.point_to_point( its_elev, height_cbsd, height_rx, dielec, conductivity, refractivity, freq_mhz, climate, polarization, confidence, reliabilities, mdvar, False) if do_avg: db_loss = -10 * np.log10(np.mean(10**(-np.array(db_loss) / 10.))) # Add indoor losses if cbsd_indoor: if np.isscalar(db_loss): db_loss += 15 else: db_loss = [loss + 15 for loss in db_loss] # Create distance/terrain arrays for plotting if desired internals = None if return_internals: prof_d_km = (its_elev[1] / 1000.) * np.arange(len(its_elev) - 2) prof_elev = np.asarray(its_elev[2:]) internals = { 'itm_err_num': err_num, 'itm_str_mode': str_mode, 'dist_km': dist_km, 'prof_d_km': prof_d_km, 'prof_elev': prof_elev } return _PropagResult(db_loss=db_loss, incidence_angles=_IncidenceAngles( hor_cbsd=bearing_cbsd, ver_cbsd=ver_cbsd, hor_rx=bearing_rx, ver_rx=ver_rx), internals=internals)
def generate_FAD_2_default_config(self, filename): """Generates the WinnForum configuration for FAD_2""" # Create the actual config for FAD_2 # Load the esc sensor in SAS Test Harness esc_sensor = json.load( open( os.path.join('testcases', 'testdata', 'esc_sensor_record_0.json'))) # Load the device_c1 with registration in SAS Test Harness device_c1 = json.load( open(os.path.join('testcases', 'testdata', 'device_a.json'))) # Get a latitude and longitude within 40 kms from ESC. latitude, longitude, _ = vincenty.GeodesicPoint( esc_sensor['installationParam']['latitude'], esc_sensor['installationParam']['longitude'], 0.1, 30) # distance of 0.1 km from ESC at 30 degrees # Load the device_c1 in the neighborhood area of the ESC sensor device_c1['installationParam']['latitude'] = latitude device_c1['installationParam']['longitude'] = longitude # Load grant request for device_c1 grant_g1 = json.load( open(os.path.join('testcases', 'testdata', 'grant_0.json'))) grant_g1['operationParam']['maxEirp'] = 20 # Load one ppa in SAS Test Harness pal_record_0 = json.load( open(os.path.join('testcases', 'testdata', 'pal_record_0.json'))) pal_low_frequency = 3550000000 pal_high_frequency = 3560000000 ppa_record_0 = json.load( open(os.path.join('testcases', 'testdata', 'ppa_record_0.json'))) ppa_record_a, pal_records = makePpaAndPalRecordsConsistent( ppa_record_0, [pal_record_0], pal_low_frequency, pal_high_frequency, 'test_user_1') # Load device_c3 in SAS Test Harness device_c3 = json.load( open(os.path.join('testcases', 'testdata', 'device_c.json'))) # Load the device_c3 in the neighborhood area of the PPA device_c3['installationParam']['latitude'] = 38.821322 device_c3['installationParam']['longitude'] = -97.280040 # Load grant request for device_c3 in SAS Test Harness grant_g3 = json.load( open(os.path.join('testcases', 'testdata', 'grant_0.json'))) grant_g3['operationParam']['maxEirp'] = 20 # Update grant_g3 frequency to overlap with PPA zone frequency for C3 device grant_g3['operationParam']['operationFrequencyRange'] = { 'lowFrequency': 3550000000, 'highFrequency': 3555000000 } # Load the device_c2 with registration to SAS UUT device_c2 = json.load( open(os.path.join('testcases', 'testdata', 'device_b.json'))) # Get a latitude and longitude within 40 kms from ESC. latitude, longitude, _ = vincenty.GeodesicPoint( esc_sensor['installationParam']['latitude'], esc_sensor['installationParam']['longitude'], 0.1, 30) # distance of 0.1 km from ESC at 30 degrees device_c2['installationParam']['latitude'] = latitude device_c2['installationParam']['longitude'] = longitude # Creating conditionals for C2 and C4. self.assertEqual(device_c2['cbsdCategory'], 'B') conditional_parameters_c2 = { 'cbsdCategory': device_c2['cbsdCategory'], 'fccId': device_c2['fccId'], 'cbsdSerialNumber': device_c2['cbsdSerialNumber'], 'airInterface': device_c2['airInterface'], 'installationParam': device_c2['installationParam'], 'measCapability': device_c2['measCapability'] } del device_c2['cbsdCategory'] del device_c2['airInterface'] del device_c2['installationParam'] # Load grant request for device_c2 to SAS UUT grant_g2 = json.load( open(os.path.join('testcases', 'testdata', 'grant_0.json'))) grant_g2['operationParam']['maxEirp'] = 30 # Load the device_c4 with registration to SAS UUT. device_c4 = json.load( open(os.path.join('testcases', 'testdata', 'device_d.json'))) # Move device_c4 in the neighborhood area of the PPA device_c4['installationParam']['latitude'] = 38.8363 device_c4['installationParam']['longitude'] = -97.2642 device_c4['installationParam']['antennaBeamwidth'] = 0 # Creating conditionals for Cat B devices. self.assertEqual(device_c4['cbsdCategory'], 'B') conditional_parameters_c4 = { 'cbsdCategory': device_c4['cbsdCategory'], 'fccId': device_c4['fccId'], 'cbsdSerialNumber': device_c4['cbsdSerialNumber'], 'airInterface': device_c4['airInterface'], 'installationParam': device_c4['installationParam'], 'measCapability': device_c4['measCapability'] } del device_c4['cbsdCategory'] del device_c4['airInterface'] del device_c4['installationParam'] # Load grant request for device_c4 to SAS UUT grant_g4 = json.load( open(os.path.join('testcases', 'testdata', 'grant_0.json'))) grant_g4['operationParam']['maxEirp'] = 20 #Update grant_g4 frequency to overlap with PPA zone frequency for C4 device grant_g4['operationParam']['operationFrequencyRange'] = { 'lowFrequency': 3550000000, 'highFrequency': 3555000000 } conditionals = { 'registrationData': [conditional_parameters_c2, conditional_parameters_c4] } cbsd_records = [device_c1, device_c3] grant_record_list = [[grant_g1], [grant_g3]] ppa_records = [ppa_record_a] # Creating CBSD reference IDs of valid format cbsd_reference_id1 = generateCbsdReferenceId('test_fcc_id_x', 'test_serial_number_x') cbsd_reference_id2 = generateCbsdReferenceId('test_fcc_id_y', 'test_serial_number_y') cbsd_reference_ids = [[cbsd_reference_id1, cbsd_reference_id2]] # SAS test harness configuration sas_harness_config = { 'sasTestHarnessName': 'SAS-TH-1', 'hostName': getFqdnLocalhost(), 'port': getUnusedPort(), 'serverCert': getCertFilename('sas.cert'), 'serverKey': getCertFilename('sas.key'), 'caCert': "certs/ca.cert" } # Generate FAD Records for each record type like cbsd,zone and esc_sensor cbsd_fad_records = generateCbsdRecords(cbsd_records, grant_record_list) ppa_fad_records = generatePpaRecords(ppa_records, cbsd_reference_ids) esc_fad_records = [esc_sensor] sas_harness_dump_records = { 'cbsdRecords': cbsd_fad_records, 'ppaRecords': ppa_fad_records, 'escSensorRecords': esc_fad_records } config = { 'registrationRequestC2': device_c2, 'registrationRequestC4': device_c4, 'conditionalRegistrationData': conditionals, 'grantRequestG2': grant_g2, 'grantRequestG4': grant_g4, 'palRecords': pal_records, 'sasTestHarnessConfig': sas_harness_config, 'sasTestHarnessDumpRecords': sas_harness_dump_records } writeConfig(filename, config)
def CalcHybridPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=-1, freq_mhz=3625., region='RURAL', is_height_cbsd_amsl=False, return_internals=False): """Implements the Hybrid ITM/eHata NTIA propagation model. As specified by Winforum, see: R2-SGN-03, R2-SGN-04 through R2-SGN-10 and NTIA TR 15-517 Appendix A. Note that contrary to the ITM model, this function does not provide the possibility to retrieve a CDF of path losses, as it is not required in the intended use of that model (it shall be used currently only for protecting zones using average aggregated interference). Warning: Only 'reliability' values 0.5 (median) and -1 (average) are currently fully supported, as other values will not return the quantile when using internally the eHata model. Workaround is to apply it afterwards when the returned opcode=4, and using the standard deviation obtained with the 'GetEHataStdev()' routine. Inputs: lat_cbsd, lon_cbsd, height_cbsd: Lat/lon (deg) and height AGL (m) of CBSD lat_rx, lon_rx, height_rx: Lat/lon (deg) and height AGL (m) of Rx point cbsd_indoor: CBSD indoor status - Default=False. freq_mhz: Frequency (MHz). Default is mid-point of band. reliability: Reliability. Default is -1 (average value). Options: Value in [0,1]: returns the CDF quantile -1: returns the mean path loss region: Region type among 'URBAN', 'SUBURBAN, 'RURAL' is_height_cbsd_amsl: If True, the CBSD height shall be considered as AMSL (Average mean sea level). Returns: A namedtuple of: db_loss: Path Loss in dB. incidence_angles: A namedtuple of angles (degrees) hor_cbsd: Horizontal departure angle (bearing) from CBSD to Rx ver_cbsd: Vertical departure angle at CBSD hor_rx: Horizontal incidence angle (bearing) from Rx to CBSD ver_rx: Vertical incidence angle at Rx internals: A dictionary of internal data for advanced analysis (only if return_internals=True): hybrid_opcode: Opcode from HybridCode - See GetInfoOnHybridCodes() effective_height_cbsd: Effective CBSD antenna height itm_db_loss: Loss in dB for the ITM model. itm_err_num: ITM error code (see wf_itm module). itm_str_mode: Description (string) of dominant prop mode in ITM. dist_km: Distance between end points (km) prof_d_km ndarray of distances (km) - x values to plot terrain. prof_elev ndarray of terrain heightsheights (m) - y values to plot terrain, Raises: Exception if input parameters invalid or out of range. """ # Case of same points if (lat_cbsd == lat_rx and lon_cbsd == lon_rx): return _PropagResult(db_loss=0, incidence_angles=_IncidenceAngles(0, 0, 0, 0), internals=None) # Sanity checks on input parameters if freq_mhz < 40 or freq_mhz > 10000: raise Exception('Frequency outside range [40MHz - 10GHz].') if region not in ['RURAL', 'URBAN', 'SUBURBAN']: raise Exception('Region %s not allowed' % region) if reliability not in (-1, 0.5): raise Exception('Hybrid model only computes the median or the mean.') if is_height_cbsd_amsl: altitude_cbsd = drive.terrain_driver.GetTerrainElevation( lat_cbsd, lon_cbsd) height_cbsd = height_cbsd - altitude_cbsd # Get the terrain profile, using Vincenty great circle route, and WF # standard (bilinear interp; 1501 pts for all distances over 45 km) its_elev = drive.terrain_driver.TerrainProfile(lat1=lat_cbsd, lon1=lon_cbsd, lat2=lat_rx, lon2=lon_rx, target_res_meter=30., do_interp=True, max_points=1501) # Structural CBSD and mobile height corrections height_cbsd = max(height_cbsd, 20.) height_rx = 1.5 # Calculate the predicted ITM loss # and get the distance and profile for use in further logic db_loss_itm, incidence_angles, internals = wf_itm.CalcItmPropagationLoss( lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, False, reliability, freq_mhz, its_elev, return_internals=True) internals['itm_db_loss'] = db_loss_itm # Calculate the effective heights of the tx height_cbsd_eff = ehata.CbsdEffectiveHeights(height_cbsd, its_elev) internals['effective_height_cbsd'] = height_cbsd_eff # Use ITM if CBSD effective height greater than 200 m if height_cbsd_eff >= 200: return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_HIGH_HEIGHT, cbsd_indoor) # Set the environment code number. if region == 'URBAN': region_code = 23 elif region == 'SUBURBAN': region_code = 22 else: # 'RURAL': use ITM if not return_internals: return_internals = None return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_RURAL, cbsd_indoor) # The eHata offset to apply (only in case the mean is requested) offset_median_to_mean = _GetMedianToMeanOffsetDb(freq_mhz, region == 'URBAN') # Now process the different cases dist_km = internals['dist_km'] if not return_internals: return_internals = None if dist_km <= 0.1: # Use Free Space Loss db_loss = CalcFreeSpaceLoss(dist_km, freq_mhz, height_cbsd, height_rx) return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.FSL, cbsd_indoor) elif dist_km > 0.1 and dist_km < 1: # Use E-Hata Median Basic Prop Loss fsl_100m = CalcFreeSpaceLoss(0.1, freq_mhz, height_cbsd, height_rx) median_basic_loss = ehata.MedianBasicPropLoss(freq_mhz, height_cbsd, height_rx, 1, region_code) alpha = 1. + math.log10(dist_km) db_loss = fsl_100m + alpha * (median_basic_loss - fsl_100m) # TODO: validate the following approach with WinnForum participants: # Weight the offset as well from 0 (100m) to 1.0 (1km). if reliability == -1: db_loss += alpha * offset_median_to_mean return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.EHATA_FSL_INTERP, cbsd_indoor) elif dist_km >= 1 and dist_km <= 80: # Use best of E-Hata / ITM ehata_loss_med = ehata.ExtendedHata(its_elev, freq_mhz, height_cbsd, height_rx, region_code) if reliability == 0.5: ehata_loss = ehata_loss_med itm_loss_med = db_loss_itm else: ehata_loss = ehata_loss_med + offset_median_to_mean itm_loss_med = wf_itm.CalcItmPropagationLoss( lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, False, 0.5, freq_mhz, its_elev).db_loss if itm_loss_med >= ehata_loss_med: return _BuildOutput(db_loss_itm, incidence_angles, internals, HybridMode.ITM_DOMINANT, cbsd_indoor) else: return _BuildOutput(ehata_loss, incidence_angles, internals, HybridMode.EHATA_DOMINANT, cbsd_indoor) elif dist_km > 80: # Use the ITM with correction from E-Hata @ 80km # Calculate the ITM median and eHata median losses at 80km bearing = incidence_angles.hor_cbsd lat_80km, lon_80km, _ = vincenty.GeodesicPoint(lat_cbsd, lon_cbsd, 80., bearing) its_elev_80km = drive.terrain_driver.TerrainProfile( lat_cbsd, lon_cbsd, lat_80km, lon_80km, target_res_meter=30., do_interp=True, max_points=1501) ehata_loss_80km = ehata.ExtendedHata(its_elev_80km, freq_mhz, height_cbsd, height_rx, region_code) itm_loss_80km = wf_itm.CalcItmPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_80km, lon_80km, height_rx, False, 0.5, freq_mhz, its_elev_80km).db_loss J = max(ehata_loss_80km - itm_loss_80km, 0) db_loss = db_loss_itm + J return _BuildOutput(db_loss, incidence_angles, internals, HybridMode.ITM_CORRECTED, cbsd_indoor)