SSRF Translator Demonstration

Easily translate wireless service data from any raw format into SSRF XML documents. Here we provide a simple demonstration showing how to translate Industry Canada BDBS Broadcast Data records into SSRF documents.

/*
 * Copyright 2014 Key Bridge Global LLC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.openssrf.translate.canada;

import java.util.*;
import us.gov.dod.standard.ssrf.SSRF;
import us.gov.dod.standard.ssrf._3_1.Antenna;
import us.gov.dod.standard.ssrf._3_1.allotment.POCInformation;
import us.gov.dod.standard.ssrf._3_1.antenna.AntMode;
import us.gov.dod.standard.ssrf._3_1.antenna.AntPattern;
import us.gov.dod.standard.ssrf._3_1.antenna.AntPatternPoint;
import us.gov.dod.standard.ssrf._3_1.antenna.Nomenclature;
import us.gov.dod.standard.ssrf._3_1.common.CaseNum;
import us.gov.dod.standard.ssrf._3_1.contact.Address;
import us.gov.dod.standard.ssrf._3_1.location.Point;
import us.gov.dod.standard.ssrf._3_1.transmitter.Power;
import us.gov.dod.standard.ssrf._3_1.transmitter.TxMode;

/**
 * Example translator class implementation to convert Industry Canada BDBS
 * record into a SSRF Assignment.
 * <p>
 */
public class SSRFTranslatorBDBS {

  /**
   * CANADA - the default territory for this translator.
   */
  private static final ListCAO TERRITORY = ListCAO.CAN;

  /**
   * Translate a list of Service records into a single SSRF message.
   * <p>
   * @param serviceRecords a list of Canada Station service records
   * @return a single SSRF record containing the Canada Station elements
   */
  public static SSRF translate(List<Object> serviceRecords) {
    SSRF ssrf = new SSRF();
    for (Object serviceRecord : serviceRecords) {
      CanadaStation canadaStation = (CanadaStation) serviceRecord;
      ssrf.withAssignment(assignment(canadaStation));
    }
    return ssrf;
  }

  /**
   * Translate a Canada Station to an SSRF record.
   * <p>
   * @param serviceRecord a Canada Station service record
   * @return the service record, translated into SSRF
   */
  public static SSRF translate(Object serviceRecord) {
    return new SSRF().withAssignment(assignment((CanadaStation) serviceRecord));
  }

  /**
   * Construct an SSRF Assignment from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Assignment assignment(CanadaStation canadaStation) {
    /**
     * Assignment requires EffectiveDateTime, Configuration, Station, Link.
     */
    return new Assignment()
      .withEntryDateTime(canadaStation.getStCreat())
      .withLastChangeDateTime(canadaStation.getStMod())
      .withEffectiveDateTime(canadaStation.getStCreat())
      .withAssignmentAuthority(ListCHN.USER_ASSIGNED)
      .withDataSource(ListUDA.INDUSTRY_CANADA)
      .withFCCFileNum(String.valueOf(canadaStation.getDocFile()))
      .withOriginalAssignmentDate(canadaStation.getStCreat())
      .withUsageType(ListCUT.APPROVED_PERMANENT)
      .withTypeOfService(ListUTY.BROADCAST)
      .withCaseNum(new CaseNum()
        .withCountry(TERRITORY)
        .withType("callsign")
        .withIdentifier(canadaStation.getCanadaStationPK().getCallSign()))
      .withSupplementaryDetails(canadaStation.getNetwork())
      .withLastChangeDateTime(canadaStation.getStMod())
      .withLastChangeDateTime(canadaStation.getStMod())
      .withConfiguration(configuration(canadaStation))
      .withStation(station(canadaStation))
      .withLink(link(canadaStation))
      .withExpirationDateTime(canadaStation.getStMod());
  }

  /**
   * Construct a list of SSRF Antenna from a list of Canada Antenna records.
   * <p>
   * @param canadaAntenna the source record list
   * @return the SSRF type
   */
  private static Antenna antenna(CanadaStation canadaStation) {
    Antenna antenna = new Antenna()
      .withEntryDateTime(canadaStation.getStCreat())
      .withGeneric(ListCBO.NO)
      .withAntType(ListCAT.OTHER)
      .withNomenclature(new Nomenclature()
        .withName("MFR not declared")
        .withType(ListCTO.CIVILIAN_COMMERCIAL));
    /**
     * Canada Antenna records have supplemental pattern data that _MAY_ be
     * assigned with different polarizations. Put each antenna record into a
     * separate "mode".
     */
    for (ca.gc.ic.broadcast.entity.Antenna ca : canadaStation.getAntennaList()) {
      antenna.withAntMode(new AntMode()
        .withModeID(ca.getPattKey() + "-" + ca.getPattType())
        .withPolarisationType("H".equalsIgnoreCase(ca.getPolarization()) ? ListCPO.HORIZONTAL_LINEAR : ListCPO.VERTICAL_LINEAR)
        .withModeUse(ListCAU.TRANSMIT_ONLY)
        .withMotionType(ListCAD.DIRECTIONAL)
        .withSectBlanking(ListCBO.NO)
        .withBeamType(ListCBD.SHAPED_BEAM)
        .withAntPattern(antPattern(ca)));
    }
    return antenna;
  }

  /**
   * Build an SSRF Antenna Pattern from a Canada Antenna record
   * <p>
   * @param antenna a Canada Antenna record
   * @return an SSRF Antenna Pattern
   */
  private static AntPattern antPattern(ca.gc.ic.broadcast.entity.Antenna antenna) {
    AntPattern antPattern = new AntPattern()
      .withCalculated("THEORETICAL".equalsIgnoreCase(antenna.getPattType()) ? ListCBO.YES : ListCBO.NO);
    /**
     * Set the pattern and type.
     */
    if ("H".equalsIgnoreCase(antenna.getPolarization())) {
      antPattern.withType(ListCAP.HH);
    } else if ("V".equalsIgnoreCase(antenna.getPolarization())) {
      antPattern.withType(ListCAP.VV);
    }
    /**
     * Add the pattern.
     * <p>
     * Developer note: The Canada 'apatdat' table has about 33 records with gain
     * values greater than zero (but always less than 0.10). Since the unit of
     * measure is dBi, which is always less than zero, force these invalid
     * positive values to zero.
     * <p/>
     * Developer note: Canada radiation pattern data is recorded in off-angle
     * (double value) headings. However, since the BRIEF and PRECISE records are
     * merged in the builder above round the heading to the nearest integer
     * angle. This will eliminate very close angle gain values in the
     * polarization map, which if present can cause any subsequent antenna
     * pattern interpolation to go haywire.
     * <p/>
     * The Canada 'apatdat' table stores antenna radiation pattern values as
     * gain in dB above the ERPVPK (ERP Visual Peak). The SSRF spec calls for
     * antenna radiation pattern values to be stored as decibels relative to the
     * main beam gain. These are equivalent and no conversion is necessary.
     */
    for (RadiationPattern radiationPattern : antenna.getRadiationPatternList()) {
      antPattern.withAntPatternPoint(new AntPatternPoint()
        .withDir(Double.valueOf(Math.round(radiationPattern.getAngle())))
        .withGain(radiationPattern.getGain() > 0
          ? 0
          : radiationPattern.getGain())
      );
    }
    return antPattern;
  }

  /**
   * Construct a SSRF Configuration from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Configuration configuration(CanadaStation canadaStation) {
    return new Configuration()
      .withConfigID(canadaStation.getCanadaStationPK().getBanner().name() + ":" + canadaStation.getCanadaStationPK().getCallSign())
      .withDescription(canadaStation.getStationClass().getDescription())
      .withConfigFreq(new ConfigFreq()
        .withFreqMin(canadaStation.getFrequency())
        .withInBand(ListCBO.YES)
        .withPriority(ListCPS.PRIMARY))
      .withUsage(new Usage()
        .withEqpFnct(ListCEF.BROADCAST_RADIOTELEVISION)
        .withStnClass(ListUSC.BT)
        .withRadioService(ListCSN.BROADCASTING_SERVICE))
      .withTxRef(new TxRef()
        .withTransmitter(transmitter(canadaStation))
        .withTxModeRef(new TxModeRef()
          .withModeID(canadaStation.getStationType().name()))
        .withTxAntModeRef(new TxAntModeRef()
          .withAntenna(antenna(canadaStation))
          .withModeID(modeId_preferred(canadaStation.getAntennaList()))
        ));
  }

  /**
   * Get the preferred modeID from a list of Canada Antenna.
   * <p>
   * Canada Antenna pattern type can be one of 'PRECISE', 'BRIEF',
   * 'THEORETICAL'. Prefer them in that order..
   * <p>
   * @param antennaList
   * @return
   */
  private static String modeId_preferred(List<ca.gc.ic.broadcast.entity.Antenna> antennaList) {
    /**
     * Canada Antenna pattern type can be one of 'PRECISE', 'BRIEF',
     * 'THEORETICAL'. Prefer them in that order.
     */
    String[] modes = new String[]{"PRECISE", "BRIEF", "THEORETICAL"};
    Map<String, String> modeMap = new HashMap<>();
    for (ca.gc.ic.broadcast.entity.Antenna canadaAntenna : antennaList) {
      canadaAntenna.getPattType();
      modeMap.put(canadaAntenna.getPattType(), canadaAntenna.getPattKey() + "-" + canadaAntenna.getPattType());
    }
    for (String mode : modes) {
      if (modeMap.get(mode) != null) {
        return modeMap.get(mode);
      }
    }
    return null;
  }

  /**
   * Construct a SSRF Link from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Link link(CanadaStation canadaStation) {
    return new Link()
      .withLinkID(canadaStation.getCanadaStationPK().getCallSign())
      .withMajorFunction(ListUFN.BROADCAST)
      .withIntermediateFunction(ListUFN.BROADCAST)
      .withStationConfig(stationConfig(canadaStation));
  }

  /**
   * Construct a SSRF StationConfig from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static StationConfig stationConfig(CanadaStation canadaStation) {
    StationConfig stationConfig = new StationConfig()
      .withStationID(canadaStation.getCanadaStationPK().getCallSign())
      .withConfigID(canadaStation.getCanadaStationPK().getBanner().getDescription())
      .withType(ListCAU.TRANSMIT_ONLY)
      .withEIRPMin(getPowerDBW(canadaStation));

    if (canadaStation instanceof CanadaStationTv) {
      stationConfig.withAntFeedpointHeight(((CanadaStationTv) canadaStation).getRadCenter().intValue());
    } else if (canadaStation instanceof CanadaStationFm) {
      stationConfig.withAntFeedpointHeight(((CanadaStationFm) canadaStation).getRadCenter().intValue());
    }

    return stationConfig;
  }

  /**
   * Construct a SSRF Station from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Station station(CanadaStation canadaStation) {
    Station station = new Station()
      .withStationID(canadaStation.getCanadaStationPK().getBanner().getDescription())
      .withCallSign(canadaStation.getCanadaStationPK().getCallSign())
      .withStationLoc(new StationLoc()
        .withLocSat(location(canadaStation)))
      .withPOCInformation(new POCInformation()
        .withType(ListCCI.USER)
        .withPoc(organisation(canadaStation)));
    /**
     * Set the antenna structure height if available.
     */
    if (canadaStation instanceof CanadaStationTv) {
      station.withAntStructureHeight(((CanadaStationTv) canadaStation).getRadCenter().intValue());
    } else if (canadaStation instanceof CanadaStationFm) {
      station.withAntStructureHeight(((CanadaStationFm) canadaStation).getRadCenter().intValue());
    }
    return station;
  }

  /**
   * Construct a SSRF Organisation (sic) from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Organisation organisation(CanadaStation canadaStation) {
    Comment comment = canadaStation.getComment();
    return new Organisation()
      .withEntryDateTime(canadaStation.getStCreat())
      .withName(comment.getName())
      .withAlternateName(canadaStation.getCanadaStationPK().getCallSign())
      .withType(ListCTO.CIVILIAN_COMMERCIAL)
      .withAddress(new Address()
        .withStreet(comment.getAddress() + " " + comment.getAddress2())
        .withCityArea(comment.getCity())
        .withCountry(ListCAO.CAN)
        .withStateCounty(comment.getProvince()));
  }

  /**
   * Construct a SSRF Location from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Location location(CanadaStation canadaStation) {
    return new Location()
      .withEntryDateTime(canadaStation.getStCreat())
      .withName(canadaStation.getCanadaStationPK().getCallSign())
      .withCountry(TERRITORY)
      .withStateCounty(canadaStation.getComment().getProvince())
      .withCityArea(canadaStation.getComment().getCity())
      .withPoint(new Point()
        .withLat(canadaStation.getLatitude())
        .withLon(canadaStation.getLongitude())
      );
  }

  /**
   * Construct a SSRF Transmitter from a CanadaStation record.
   * <p>
   * @param canadaStation the source record
   * @return the SSRF type
   */
  private static Transmitter transmitter(CanadaStation canadaStation) {
    return new Transmitter()
      .withEntryDateTime(canadaStation.getStCreat())
      .withGeneric(ListCBO.YES)
      .withOutputDeviceType(ListCOT.KLYSTRON)
      .withTxMode(new TxMode()
        .withModeID(canadaStation.getStationType().name())
        .withDescription(canadaStation.getStationType().getDescription())
        .withPower(new Power()
          .withPowerMin(getPowerDBW(canadaStation))
          .withPowerType(ListCPT.CARRIER)
          .withCalculated(ListCBO.NO)
        )
      );
  }

  /**
   * Internal helper method to get the power level in dBW.
   * <p>
   * @param canadaStation the source record
   * @return the power in dBW
   */
  private static Double getPowerDBW(CanadaStation canadaStation) {
    if (canadaStation instanceof CanadaStationTv) {
      CanadaStationTv tv = (CanadaStationTv) canadaStation;
      if (tv.getErpvpk() == 0 && tv.getErpvav() == 0) {
        /**
         * From FCC OET Guidance in 05/16/12 Canadian TV Stations Data
         * Implementation Information: Canadian records with no ERP VisualPeak
         * (erpvpk) or ERP VisualAverage (erpvav) should be assumed to transmit
         * at 1,000 WATT.
         */
        double ERP_DEFAULT = 1000.0d; // Watt
        return log10(ERP_DEFAULT);
      } else if (tv.getErpvpk() > 0) {
        return log10(tv.getErpvpk());
      } else {
        return log10(tv.getErpvav());
      }
    } else if (canadaStation instanceof CanadaStationFm) {
      CanadaStationFm fm = (CanadaStationFm) canadaStation;
      return log10(fm.getErpvav());
    } else if (canadaStation instanceof CanadaStationSdar) {
      CanadaStationSdar sdar = (CanadaStationSdar) canadaStation;
      return log10(sdar.getErpapk());
    } else if (canadaStation instanceof CanadaStationAm) {
      CanadaStationAm am = (CanadaStationAm) canadaStation;
      return log10(am.getPowerday());
    }
    return null;
  }

  /**
   * Internal method to return the base 10 logarithm of a value.
   * <p>
   * @param value the value
   * @return the base 10 logarithm
   */
  private static Double log10(Double value) {
    if (value != null && value > 0) {
      return Double.valueOf(10 * Math.log10(value));
    }
    return null;
  }
}

      
Session Error

Your session may have timed out or encountered an error. Please refesh the page to continue.