text: #    Copyright 2017 SAS Project Authors. All Rights Reserved.
  #
  #    Licensed under the Apache License, Version 2.0 (the "License");
  #    you may not use this file except in compliance with the License.
  #    You may obtain a copy of the License at
  #
  #        http://www.apache.org/licenses/LICENSE-2.0
  #
  #    Unless required by applicable law or agreed to in writing, software
  #    distributed under the License is distributed on an "AS IS" BASIS,
  #    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #    See the License for the specific language governing permissions and
  #    limitations under the License.
  
  """Simple data model for SAS entities, and utility routines.
  
  Used solely for running various examples of the protection logic, or scenario
  generating scripts.
  This is *NOT* designed to be used within the actual test harness.
  """
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  from collections import namedtuple
  import numpy as np
  import shapely.geometry as sgeo
  
  from reference_models.common import data
  from reference_models.geo import nlcd
  from reference_models.geo import vincenty
  
  # CBSD devices
  Cbsd = namedtuple('Cbsd',
                    ['latitude',
                     'longitude',
                     'height_agl',
                     'is_indoor',
                     'category',
                     'eirp_dbm_mhz',
                     'antenna_azimuth',
                     'antenna_beamwidth',
                     'antenna_gain'])
  
  # Typical CBSD templates
  # - Cat A Indoor - Omni - 23dBm EIRP
  CBSD_TEMPLATE_CAT_A_INDOOR = Cbsd(latitude=0,longitude=0,
                                    height_agl=3,
                                    is_indoor=True,
                                    category='A',
                                    eirp_dbm_mhz=13,
                                    antenna_azimuth=0,
                                    antenna_beamwidth=None,
                                    antenna_gain=0)
  
  # - Cat A Outdoor - Omni - 30dBm  EIRP
  CBSD_TEMPLATE_CAT_A_OUTDOOR = Cbsd(latitude=0,longitude=0,
                                     height_agl=6,
                                     is_indoor=False,
                                     category='A',
                                     eirp_dbm_mhz=20,
                                     antenna_azimuth=0,
                                     antenna_beamwidth=None,
                                     antenna_gain=6)
  
  # - Cat B Outdoor - Sectorized - 47dBm EIRP
  CBSD_TEMPLATE_CAT_B = Cbsd(latitude=0,longitude=0,
                             height_agl=10,
                             is_indoor=False,
                             category='B',
                             eirp_dbm_mhz=37,
                             antenna_azimuth=[0, 120, 240],
                             antenna_beamwidth=90,
                             antenna_gain=15)
  
  CBSD_TEMPLATE_CAT_B_OMNI = Cbsd(latitude=0,longitude=0,
                                  height_agl=10,
                                  is_indoor=False,
                                  category='B',
                                  eirp_dbm_mhz=37,
                                  antenna_azimuth=0,
                                  antenna_beamwidth=None,
                                  antenna_gain=8)
  
  # FSS earth station
  FssLicenseInfo = namedtuple('FssLicenseInfo',
                              ['latitude',
                               'longitude',
                               'height_agl',
                               'max_gain_dbi',
                               'satellite_arc_east_limit',
                               'satellite_arc_west_limit',
                               'elevation_east_limit',
                               'elevation_west_limit',
                               'azimuth_east_limit',
                               'azimuth_west_limit'])
  
  FssProtection = namedtuple('FssProtectionPoint',
                             ['latitude',
                              'longitude',
                              'height_agl',
                              'max_gain_dbi',
                              'pointing_azimuth',
                              'pointing_elevation'])
  
  
  
  
  
  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 range(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 GenerateCbsdsInPolygon(num_cbsds, template_cbsd, polygon,
                             nlcd_driver=None, urban_areas=None):
    """Returns a number of inland |Cbsd| within polygon, possibly in urban area.
  
    Args:
      template_cbsd: A |Cbsd| used as a template.
      polygon: A |shapely.Polygon| where to put the Cbsd.
      nlcd_driver: The land cover driver for detecting inland positions (optional).
      urban_areas: An optional  |shapely.MultiPolygon| defining the urban areas.
        If not specified, do not enforce the Cbsd to be in urban areas.
    """
    t = template_cbsd
    bounds = polygon.bounds
    cbsds = []
    total_asked, total_got = 0, 0
    ratio = 0.5
    while len(cbsds) < num_cbsds:
      n_block = int((num_cbsds - len(cbsds)) / ratio * 1.2)
      lngs = np.random.uniform(bounds[0], bounds[2], n_block)
      lats = np.random.uniform(bounds[1], bounds[3], n_block)
      in_points = sgeo.MultiPoint(list(zip(lngs, lats)))
      in_points = polygon.intersection(in_points)
      if in_points and urban_areas is not None:
        in_points = urban_areas.intersection(in_points)
      total_asked += n_block
      if not in_points:
        continue
      if isinstance(in_points, sgeo.Point):
        in_points = [in_points]
      total_got += len(in_points)
      ratio = total_got / float(total_asked)
      for point in in_points:
        if len(cbsds) > num_cbsds: break
        if nlcd_driver is not None:
          land_cover = nlcd_driver.GetLandCoverCodes(point.y, point.x)
          if land_cover < nlcd.LandCoverCodes.PERENNIAL_SNOW:
            continue
  
        azimuth_offset = np.random.uniform(0, 360)
        azimuths = (t.antenna_azimuth if isinstance(t.antenna_azimuth, list)
                    else [t.antenna_azimuth])
        for azimuth in azimuths:
          cbsds.append(Cbsd(point.y, point.x, t.height_agl, t.is_indoor, t.category,
                            t.eirp_dbm_mhz, azimuth,
                            t.antenna_beamwidth, t.antenna_gain))
  
  
    return cbsds
  
  
  # Utility routines to convert Cbsd object into registration and grant requests.
  def GetCbsdRegistrationRequest(cbsd):
    """Returns a CBSD registration request from the |Cbsd| object."""
    return {
        'cbsdCategory': cbsd.category,
        'installationParam': {
            'latitude': cbsd.latitude,
            'longitude': cbsd.longitude,
            'height': cbsd.height_agl,
            'heightType': 'AGL',
            'indoorDeployment': cbsd.is_indoor,
            'antennaBeamwidth': (int(cbsd.antenna_beamwidth) % 360
                                 if cbsd.antenna_beamwidth is not None
                                 else None),
            'antennaAzimuth': (int(cbsd.antenna_azimuth) % 360
                               if cbsd.antenna_azimuth is not None
                               else None),
            'antennaDowntilt': 0,
            'antennaGain': (int(cbsd.antenna_gain) if cbsd.antenna_gain
                            else 0)
        }
    }
  
  
  def GetCbsdGrantRequest(cbsd, min_freq_mhz, max_freq_mhz):
    """Returns a CBSD grant request from a |Cbsd| object and a min/max frequency.
    """
    return {
        'operationParam': {
            'operationFrequencyRange': {
                'lowFrequency': int(min_freq_mhz * 1e6),
                'highFrequency': int(max_freq_mhz * 1e6)
            },
            'maxEirp': int(cbsd.eirp_dbm_mhz)
        }
    }
  
  
  def ConvertToCbsdGrantInfo(cbsds, min_freq_mhz, max_freq_mhz, chunks_mhz=-1):
    """Converts |Cbsd| into |data.CbsdGrantInfo| lists.
  
    Args:
      cbsds: an iterable of |Cbsd|.
      min_freq_mhz: The minimum grant frequency (MHz).
      max_freq_mhz: The maximum grant frequency (MHz).
      chunks_mhz: The chunking size of the grants (MHz). If -1, no chunking done.
    Returns:
      A list of |data.CbsdGrantInfo| namedtuple.
    """
    reg_requests = []
    grant_requests = []
    for cbsd in cbsds:
      req_request = GetCbsdRegistrationRequest(cbsd)
      if chunks_mhz < 0:
        grant_request = GetCbsdGrantRequest(cbsd, min_freq_mhz, max_freq_mhz)
        reg_requests.append(req_request)
        grant_requests.append(grant_request)
      else:
        for min_freq in np.arange(min_freq_mhz, max_freq_mhz, chunks_mhz):
          max_freq = min(max_freq_mhz, min_freq + chunks_mhz)
          grant_request = GetCbsdGrantRequest(cbsd, min_freq, max_freq)
          reg_requests.append(req_request)
          grant_requests.append(grant_request)
  
    return data.getGrantsFromRequests(reg_requests, grant_requests)
  