Example #1
0
def findGrantsInsideNeighborhood(grants, protection_point, entity_type):
    """Finds grants inside protection entity neighborhood.

  Args:
    grants: An iterable of CBSD grants of type |data.CbsdGrantInfo|.
    protection_point: The location of a protected entity as (longitude, latitude) tuple.
    entity_type: The entity type (|data.ProtectedEntityType|).
  Returns:
    grants_inside: a list of grants, each one being a namedtuple of type
                   |data.CbsdGrantInfo|, of all CBSDs inside the neighborhood
                   of the protection constraint.
  """
    # Initialize an empty list
    grants_inside = []

    # Loop over each CBSD grant
    for grant in grants:
        # Compute distance from CBSD location to protection constraint location
        dist_km, _, _ = vincenty.GeodesicDistanceBearing(
            grant.latitude, grant.longitude, protection_point[1],
            protection_point[0])

        # Check if CBSD is inside the neighborhood of protection constraint
        if dist_km <= _DISTANCE_PER_PROTECTION_TYPE[entity_type][
                grant.cbsd_category == 'B']:
            grants_inside.append(grant)

    return grants_inside
Example #2
0
def getFssNeighboringGwbl(gwbl_records, fss_records):
    """Returns the list of all FSS within 150km of GWBL and operating below 3700MHz.

  Args:
    gwbl_records: List of GWBL record dictionaries.
    fss_records: List of FSS records dictionaries.
  """
    list_of_fss_neighboring_gwbl = []
    gwbl_locations = [
        (gwbl_record['record']['deploymentParam'][0]['installationParam']
         ['longitude'], gwbl_record['record']['deploymentParam'][0]
         ['installationParam']['latitude']) for gwbl_record in gwbl_records
    ]

    for fss_record in fss_records:
        fss_params = fss_record['record']['deploymentParam'][0]
        fss_low_freq = fss_params['operationParam']['operationFrequencyRange'][
            'lowFrequency']
        if fss_low_freq >= 3700e6:
            continue
        fss_latitude = fss_params['installationParam']['latitude']
        fss_longitude = fss_params['installationParam']['longitude']
        for gwbl_longitude, gwbl_latitude in gwbl_locations:
            # TODO: If speed limiting, quick filtering to speed up things with approx dist bound.
            # Exact distance filtering
            # Get the distance of the FSS entity from the GWBL polygon
            distance, _, _ = vincenty.GeodesicDistanceBearing(
                fss_latitude, fss_longitude, gwbl_latitude, gwbl_longitude)
            # Get the list of FSS entity that are 150kms from the GWBL area
            if distance <= FSS_GWBL_PROTECTION_DISTANCE_KM:
                list_of_fss_neighboring_gwbl.append(fss_record)
                break
    return list_of_fss_neighboring_gwbl
Example #3
0
  def __call__(self,
               lat_cbsd,
               lon_cbsd,
               height_cbsd,
               lat_rx,
               lon_rx,
               height_rx,
               cbsd_indoor=False,
               reliability=0.5,
               freq_mhz=3625.,
               its_elev=None,
               region=None,
               is_height_cbsd_amsl=False):
    """See `CalcItmPropagationLoss()` for specification."""
    if self.dist_type == 'L1':
      dist = np.abs(lat_rx - lat_cbsd) + np.abs(lon_rx - lon_cbsd)
    elif self.dist_type == 'L2':
      dist = np.sqrt((lat_rx - lat_cbsd)**2 + (lon_rx - lon_cbsd)**2)
    else:
      dist, _, _ = vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd, lat_rx, lon_rx)

    path_loss = self.factor * dist + self.offset
    bearing_cbsd = np.arctan2(lon_rx - lon_cbsd, lat_rx - lat_cbsd) * 180. / np.pi
    bearing_rx = 180 + bearing_cbsd
    if bearing_cbsd < 0: bearing_cbsd +=360
    return wf_itm._PropagResult(
          db_loss=(path_loss if np.isscalar(reliability) else
          np.ones(len(reliability)) * path_loss),
          incidence_angles=wf_itm._IncidenceAngles(
          hor_cbsd=bearing_cbsd, ver_cbsd=0, hor_rx=bearing_rx, ver_rx=0),
          internals={})
def GetClosestCanadianBorderPoint(latitude, longitude, max_dist_km):
  """Returns the closest point on the canadian border within some distance.

  Args:
    latitude: The point latitude (degrees).
    longitude: The point longitude (degrees).
    max_dist_km: The max distance for the border point (km).

  Returns:
    Either:
      None if no border point is within the requested `max dist_km`
      or a (lat, lon, distance_km, bearing) tuple of the closest point in US/Canada border:
        * lat, lon: closest point coordinates.
        * distance_km: the distance to closest point (km).
        * bearing: the bearing of the closest point (degrees, clockwise relative to north).
  """
  # Prefiltering of the border for quicker processing
  one_degree = WGS_EQUATORIAL_RADIUS_KM2 * 2 * np.pi / 360. * np.cos(latitude*np.pi/180.)
  max_delta = max_dist_km / one_degree * 1.1
  border_cap = zones.GetUsCanadaBorder().intersection(
      sgeo.Point(longitude, latitude).buffer(max_delta))
  if not border_cap or 'LineString' not in border_cap.type:
    return None
  # Find closest point
  points_dists = [(vincenty.GeodesicDistanceBearing(latitude, longitude, point[1], point[0]),
                   point)
                  for point in zip(*border_cap.xy)]
  closest = min(points_dists)
  closest_dist = closest[0][0]
  if closest_dist > max_dist_km:
    return None
  closest_bearing = closest[0][1]
  return closest[1][1], closest[1][0], closest_dist, closest_bearing
def ResampleUsCanadaBorderLineString(ls, step_m):
  """Resamples a |shapely.LineString|.

  This keeps original linestring vertices, and add new vertices in between
  so as 2 vertices are no more than `step` meters, using either:
    - vincenty great circle method
    - linear interpolation for specific latitude of 49 degree

  Args:
    ls: A |shapely.LineString|
    step: The maximum distance between 2 vertices
  """
  vertices = zip(*ls.xy)
  step_km = step_m / 1000.
  out_vertices = []
  for vertex0, vertex1 in itertools.izip(vertices[0:-1], vertices[1:]):
    dist_km, _, _ = vincenty.GeodesicDistanceBearing(
        vertex0[1], vertex0[0], vertex1[1], vertex1[0])
    if dist_km < step_km:
      out_vertices.append(vertex0)
    else:
      num_points = int(dist_km / step_km) + 2
      if abs(vertex0[1] - 49) < 1e-3 and abs(vertex1[1] - 49) < 1e-3:
        # departing less than 70m from the 49degree north latitude
        lats = np.interp(range(num_points), [0, num_points-1], [vertex0[1], vertex1[1]])
        lons = np.interp(range(num_points), [0, num_points-1], [vertex0[0], vertex1[0]])
      else:
        lats, lons = vincenty.GeodesicSampling(
            vertex0[1], vertex0[0], vertex1[1], vertex1[0], num_points)

      points = zip(lats, lons)
      out_vertices.extend([(point[1], point[0]) for point in points[:-1]])

  out_vertices.append(vertex1)  # add the last vertex
  return sgeo.LineString(out_vertices)
Example #6
0
    def TerrainProfile(self,
                       lat1,
                       lon1,
                       lat2,
                       lon2,
                       target_res_meter=-1,
                       target_res_arcsec=1,
                       do_interp=True,
                       max_points=-1):
        """Returns the terrain profile between two points.

    The path is calculated using Vincenty method for obtaining the geodesic
    between the 2 points. The profile is returned at equally spaced points
    along the geodesic as close as possible of the target resolution.
    The target resolution can be either passed in meters, or in arcseconds
    (in which case it is converted to an equivalent resolution at equator).

    Inputs:
      lat1, lon1: coordinates of starting point (in degrees).
      lat2, lon2: coordinates of final point (in degrees).
      target_res_meter: target resolution between points (in meters).
        If unspecified, uses 'target_res_arcsec' instead.
      target_res_arcsec: target resolution between 2 point (in arcsec).
        Only used if 'target_res_meter' unspecified.
      do_interp: if True (default), use bilinear interpolation on terrain data.
      max_points: if positive, resolution extended if number of points is beyond
                  this number.

    Returns:
      an elevation profile in the ITS format as an array:
         elev[0] = number of terrain points - 1 (i.e., number of intervals)
         elev[1] = distance between sample points (meters)
         elev[2]...elev[npts+1] = Terrain elevation (meters)
    """

        if target_res_meter < 0:
            target_res_meter = _RADIUS_EARTH_METERS * np.radians(
                target_res_arcsec / 3600.)

        # Distance between end points (m)
        dist, _, _ = vincenty.GeodesicDistanceBearing(lat1, lon1, lat2, lon2)
        dist *= 1000.

        num_points = np.ceil(dist / float(target_res_meter)) + 1
        if max_points > 0 and num_points > max_points:
            num_points = max_points
        if num_points < 2:
            num_points = 2

        resolution = dist / float(num_points - 1)
        lats, lons = vincenty.GeodesicSampling(lat1, lon1, lat2, lon2,
                                               num_points)
        elev = [num_points - 1, resolution]
        elev.extend(self.GetTerrainElevation(lats, lons, do_interp))
        return elev
def findGrantsInsideNeighborhood(grants, constraint, dpa_type,
                                 neighbor_distances):
    """Identify the CBSD grants in the neighborhood of protection constraint.

  Inputs:
    grants:         a list of CBSD |data.CbsdGrantInfo| grants.
    constraint:     protection constraint of type |data.ProtectionConstraint|
    dpa_type:       an enum member of class DpaType
    neighbor_distances: the neighborhood distances (Km) as a sequence:
      [cata_dist, catb_dist, cata_oob_dist, catb_oob_dist]

  Returns:
    A tuple of:
      grants_inside:  a list of |data.CbsdGrantInfo| grants, being inside the
                      neighborhood of that protection `constraint`.
      idxs_inside:    the indices of `grants_inside` in original `grant` list.
  """
    # Initialize an empty list
    grants_inside = []
    idxs_inside = []

    neighbor_dists = neighbor_distances[0:2]
    if dpa_type is DpaType.OUT_OF_BAND:
        neighbor_dists = neighbor_distances[2:]

    # Loop over each CBSD grant and filter the ones inside the neighborhood
    for k, grant in enumerate(grants):
        # Check frequency range
        if dpa_type is not DpaType.OUT_OF_BAND:
            overlapping_bw = (
                min(grant.high_frequency, constraint.high_frequency) -
                max(grant.low_frequency, constraint.low_frequency))
            if overlapping_bw <= 0:
                continue

        # Compute distance from CBSD location to protection constraint location
        dist_km, _, _ = vincenty.GeodesicDistanceBearing(
            grant.latitude, grant.longitude, constraint.latitude,
            constraint.longitude)

        # Check if CBSD is inside the neighborhood of protection constraint
        if dist_km > neighbor_dists[grant.cbsd_category == 'B']:
            continue

        grants_inside.append(grant)
        idxs_inside.append(k)

    return grants_inside, idxs_inside
Example #8
0
def getFssNeighboringCbsdsWithGrants(
        cbsds, fss_point, distance_km=FSS_GWBL_PROTECTION_DISTANCE_KM):
    """Returns the list of all CBSDs in the neighborhood of a FSS.

  The neighborhood is typically defined by all CBSD within 150 KMs and having
  at least one grant.

  Args:
    cbsds :  List of |CbsdData| dictionaries as defined in the SAS-SAS specification.
    fss_point: A tuple (longitude, latitude) of the FSS location.
    distance_km: The neighboring distance (km).
  """
    neighboring_cbsds_with_grants = []
    for cbsd in cbsds:
        if not cbsd['grants']:
            continue
        distance, _, _ = vincenty.GeodesicDistanceBearing(
            fss_point[1], fss_point[0],
            cbsd['registration']['installationParam']['latitude'],
            cbsd['registration']['installationParam']['longitude'])
        # Get the list of cbsds that are within 150kms from the FSS entity
        if distance <= distance_km and cbsd['grants']:
            neighboring_cbsds_with_grants.append(cbsd)
    return neighboring_cbsds_with_grants
Example #9
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)
                                        entities.CBSD_TEMPLATE_CAT_B,
                                        fss_info.latitude,
                                        fss_info.longitude,
                                        max_distance_km=200)
all_cbsds = []
all_cbsds.extend(cbsds_cat_a_indoor)
all_cbsds.extend(cbsds_cat_a_outdoor)
all_cbsds.extend(cbsds_cat_b)

#---------------------------------------------------------------
# Calculate the aggregated interference calculation seen by the FSS
#  - first select all CBSD within 150km according to R2-SGN-16
cbsds = []
for cbsd in all_cbsds:
  distance_km, _, _ = vincenty.GeodesicDistanceBearing(
      base_fss.latitude, base_fss.longitude,
      cbsd.latitude, cbsd.longitude)
  if distance_km <= 150:
    cbsds.append(cbsd)

#  - then calculate all path losses and received RSSI at FSS
#    (without FSS antenna taken into account)
cbsd_rssi = np.zeros(len(cbsds))
cbsd_incidence_angles = []

for k, cbsd in enumerate(cbsds):
  if not (k%10):
    sys.stdout.write('*')
    sys.stdout.flush()
  db_loss, incidence_angles, _ = wf_itm.CalcItmPropagationLoss(
      cbsd.latitude, cbsd.longitude, cbsd.height_agl,
    def test_WINNF_FT_S_QPR_8(self, config_filename):
        """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of
      the FCC Field Offices with multiple grants.
    """
        config = loadConfig(config_filename)
        self.assertValidConfig(
            config, {
                'registrationRequest': dict,
                'conditionalRegistrationData': list,
                'grantRequest1': dict,
                'grantRequest2': dict
            })

        # Whitelist FCC ID and User ID.
        self._sas_admin.InjectFccId(
            {'fccId': config['registrationRequest']['fccId']})
        self._sas_admin.InjectUserId(
            {'userId': config['registrationRequest']['userId']})

        # Pre-load conditional registration data.
        if config['conditionalRegistrationData']:
            self._sas_admin.PreloadRegistrationData(
                {'registrationData': config['conditionalRegistrationData']})

        # Step 1: Register CBSD.
        request = {'registrationRequest': [config['registrationRequest']]}
        response = self._sas.Registration(request)['registrationResponse']

        # Check registration response.
        self.assertEqual(len(response), 1)
        if response[0]['response']['responseCode'] != 0:
            return  # SAS passes immediately in this case.
        cbsd_id = response[0]['cbsdId']
        del request, response

        # Step 2: Request first grant.
        config['grantRequest1']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest1']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse'][0]
        # Check if the first grant response is successful.
        grant1_approved = response['response']['responseCode'] == 0
        del request, response

        # Step 3: Request second grant
        config['grantRequest2']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest2']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse'][0]
        # Check if the second grant response is successful.
        grant2_approved = response['response']['responseCode'] == 0
        del request, response

        if not (grant1_approved or grant2_approved):
            logging.info('Both grant requests were rejected')
            return  # SAS passes immediately in this case.

        # Calculate the closest FCC office
        lat_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['latitude']
        lon_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['longitude']
        distance_offices = [
            vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd,
                                             office['latitude'],
                                             office['longitude'])[0]
            for office in self.fcc_offices
        ]
        index_closest = np.argmin(distance_offices)
        closest_fcc_office = self.fcc_offices[index_closest]
        logging.info('Closest FCC Office Lat: %f',
                     closest_fcc_office['latitude'])
        logging.info('Closest FCC Office Long: %f',
                     closest_fcc_office['longitude'])
        # Calculate bearing and ant_gain
        _, bearing, _ = vincenty.GeodesicDistanceBearing(
            lat_cbsd, lon_cbsd, closest_fcc_office['latitude'],
            closest_fcc_office['longitude'])
        logging.info('Bearing: %f', bearing)
        ant_gain_dbi = antenna.GetStandardAntennaGains(
            bearing, config['conditionalRegistrationData'][0]
            ['installationParam']['antennaAzimuth'],
            config['conditionalRegistrationData'][0]['installationParam']
            ['antennaBeamwidth'], config['conditionalRegistrationData'][0]
            ['installationParam']['antennaGain'])
        logging.info('Ant Gain: %f dBi', ant_gain_dbi)
        max_ant_gain_dbi = (config['conditionalRegistrationData'][0]
                            ['installationParam']['antennaGain'])
        logging.info('Max Ant Gain: %f dBi', max_ant_gain_dbi)

        # Gather values required for calculating Total EIRP
        grant1_eirp = 0
        if grant1_approved:
            p1 = config['grantRequest1']['operationParam']['maxEirp']
            logging.info('Grant 1 Max Eirp: %f dBm/MHz', p1)
            bw1 = (config['grantRequest1']['operationParam']
                   ['operationFrequencyRange']['highFrequency'] -
                   config['grantRequest1']['operationParam']
                   ['operationFrequencyRange']['lowFrequency']) / 1.e6
            logging.info('Grant 1 Bandwidth: %f', bw1)
            grant1_eirp = (10**(p1 / 10.0)) * bw1
            logging.info('Grant 1 nominal EIRP is %f', grant1_eirp)

        grant2_eirp = 0
        if grant2_approved:
            p2 = config['grantRequest2']['operationParam']['maxEirp']
            logging.info('Grant 2 Max Eirp: %f dBm/MHz', p2)
            bw2 = (config['grantRequest2']['operationParam']
                   ['operationFrequencyRange']['highFrequency'] -
                   config['grantRequest2']['operationParam']
                   ['operationFrequencyRange']['lowFrequency']) / 1.e6
            logging.info('Grant 2 Bandwidth: %f', bw2)
            grant2_eirp = (10**(p2 / 10.0)) * bw2
            logging.info('Grant 2 nominal EIRP is %f', grant2_eirp)

        # Step 4: Calculate Total EIRP
        total_eirp_dbm = ant_gain_dbi - max_ant_gain_dbi + (
            10 * np.log10(grant1_eirp + grant2_eirp))
        logging.info('Total EIRP is %f dBm', total_eirp_dbm)

        # CHECK: Total EIRP of all approved grants is <= 49.15 dBm
        self.assertLessEqual(total_eirp_dbm, 49.15)
    def test_WINNF_FT_S_QPR_7(self, config_filename):
        """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of
      the FCC Field Offices.
    """
        config = loadConfig(config_filename)
        # Very light checking of the config file.
        self.assertValidConfig(
            config, {
                'registrationRequest': dict,
                'conditionalRegistrationData': list,
                'grantRequest': dict
            })

        # Whitelist FCC ID and User ID.
        self._sas_admin.InjectFccId(
            {'fccId': config['registrationRequest']['fccId']})
        self._sas_admin.InjectUserId(
            {'userId': config['registrationRequest']['userId']})

        # Pre-load conditional registration data.
        if config['conditionalRegistrationData']:
            self._sas_admin.PreloadRegistrationData(
                {'registrationData': config['conditionalRegistrationData']})

        # Register CBSD.
        request = {'registrationRequest': [config['registrationRequest']]}
        response = self._sas.Registration(request)['registrationResponse']

        # Check registration response.
        self.assertEqual(len(response), 1)
        if response[0]['response']['responseCode'] != 0:
            return  # SAS passes immediately in this case.
        cbsd_id = response[0]['cbsdId']
        del request, response

        # Calculate the closest FCC office
        lat_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['latitude']
        lon_cbsd = config['conditionalRegistrationData'][0][
            'installationParam']['longitude']
        distance_offices = [
            vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd,
                                             office['latitude'],
                                             office['longitude'])[0]
            for office in self.fcc_offices
        ]
        index_closest = np.argmin(distance_offices)
        closest_fcc_office = self.fcc_offices[index_closest]
        logging.info('Closest FCC office Lat: %f',
                     closest_fcc_office['latitude'])
        logging.info('Closest FCC office Long: %f',
                     closest_fcc_office['longitude'])
        # Calculate bearing and ant_gain
        _, bearing, _ = vincenty.GeodesicDistanceBearing(
            lat_cbsd, lon_cbsd, closest_fcc_office['latitude'],
            closest_fcc_office['longitude'])
        ant_gain = antenna.GetStandardAntennaGains(
            bearing, config['conditionalRegistrationData'][0]
            ['installationParam']['antennaAzimuth'],
            config['conditionalRegistrationData'][0]['installationParam']
            ['antennaBeamwidth'], config['conditionalRegistrationData'][0]
            ['installationParam']['antennaGain'])
        logging.info('ant_gain is %f dBi', ant_gain)
        # Gather values required for calculating EIRP
        p = config['grantRequest']['operationParam']['maxEirp']
        logging.info('Grant maxEirp is %f', p)
        max_ant_gain = (config['conditionalRegistrationData'][0]
                        ['installationParam']['antennaGain'])
        logging.info('max_ant_gain is %f dBi', max_ant_gain)
        bw = (config['grantRequest']['operationParam']
              ['operationFrequencyRange']['highFrequency'] -
              config['grantRequest']['operationParam']
              ['operationFrequencyRange']['lowFrequency']) / 1.e6
        logging.info('bw is %f MHz', bw)
        # Calculate EIRP to verify grant response
        eirp = (p - max_ant_gain + ant_gain + (10 * np.log10(bw)))
        logging.info('EIRP is %f dBm', eirp)

        # If successfully registered, CBSD sends a grant request
        config['grantRequest']['cbsdId'] = cbsd_id
        grant_request = config['grantRequest']
        request = {'grantRequest': [grant_request]}
        response = self._sas.Grant(request)['grantResponse']
        # Check grant response
        self.assertEqual(len(response), 1)
        # If EIRP <= 49.15 dBm = SUCCESS
        if eirp <= 49.15:
            self.assertEqual(response[0]['response']['responseCode'], 0)
        # If EIRP > 49.15 dBm = INTERFERENCE
        else:
            self.assertEqual(response[0]['response']['responseCode'], 400)