/**
   * Given a row from the ring primitive table, navigate the ring and edge primitive tables to
   * construct a new {@link VPFPrimitiveData.Ring}.
   *
   * @param row the ring primitive row.
   * @param edgeInfoArray the edge primitive data.
   * @return a new Ring.
   */
  protected VPFPrimitiveData.Ring buildRing(
      VPFRecord row, VPFPrimitiveData.PrimitiveInfo[] edgeInfoArray) {
    int faceId = ((Number) row.getValue("face_id")).intValue();
    int startEdgeId = ((Number) row.getValue("start_edge")).intValue();
    VPFWingedEdgeTraverser traverser = new VPFWingedEdgeTraverser();

    // Traverse the ring to collect the number of edges which define the ring.
    final int numEdges = traverser.traverseRing(faceId, startEdgeId, edgeInfoArray, null);
    final int[] idArray = new int[numEdges];
    final int[] orientationArray = new int[numEdges];

    // Traverse the ring again, but this time populate an entry for the primitiveID and orientation
    // data stuctures
    // for each edge.
    traverser.traverseRing(
        faceId,
        startEdgeId,
        edgeInfoArray,
        new VPFWingedEdgeTraverser.EdgeTraversalListener() {
          public void nextEdge(int index, int primitiveId, boolean reverseCoordinates) {
            idArray[index] = primitiveId;
            orientationArray[index] = reverseCoordinates ? -1 : 1;
          }
        });

    return new VPFPrimitiveData.Ring(numEdges, idArray, orientationArray);
  }
  /**
   * Returns the extent ("xmin", "ymin", "xmax", "ymax") for the specified row as a {@link
   * VPFBoundingBox}.
   *
   * @param record the record to extract the bound attributes from.
   * @return extent of the specified row.
   */
  public static VPFBoundingBox getExtent(VPFRecord record) {
    if (record == null) {
      String message = Logging.getMessage("nullValue.RecordIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    return new VPFBoundingBox(
        ((Number) record.getValue("xmin")).doubleValue(),
        ((Number) record.getValue("ymin")).doubleValue(),
        ((Number) record.getValue("xmax")).doubleValue(),
        ((Number) record.getValue("ymax")).doubleValue());
  }
  protected static boolean isEdgeOnTileBoundary(VPFRecord record) {
    VPFTripletId id = null;

    Object o = record.getValue("left_face");
    if (o instanceof VPFTripletId) id = (VPFTripletId) o;

    if (id == null) {
      o = record.getValue("right_face");
      if (o instanceof VPFTripletId) id = (VPFTripletId) o;
    }

    return id != null && id.getExtId() > 0;
  }
  public static void checkAndSetValue(
      VPFRecord record, String paramName, String paramKey, AVList params) {
    if (record == null) {
      String message = Logging.getMessage("nullValue.RecordIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (paramName == null) {
      String message = Logging.getMessage("nullValue.ParameterNameIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (paramKey == null) {
      String message = Logging.getMessage("nullValue.ParameterKeyIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (params == null) {
      String message = Logging.getMessage("nullValue.ParamsIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (record.hasValue(paramName)) {
      Object o = record.getValue(paramName);
      if (o != null) params.setValue(paramKey, o);
    }
  }
  protected void buildEdgePrimitives(
      VPFCoverage coverage, VPFTile tile, VPFPrimitiveData primitiveData) {
    VPFBufferedRecordData edgeTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.EDGE_PRIMITIVE_TABLE);
    if (edgeTable == null || edgeTable.getNumRecords() == 0) return;

    VPFBufferedRecordData mbrTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.EDGE_BOUNDING_RECTANGLE_TABLE);
    if (mbrTable == null) return;

    int numEdges = edgeTable.getNumRecords();
    VPFPrimitiveData.EdgeInfo[] edgeInfo = new VPFPrimitiveData.EdgeInfo[numEdges];
    VecBufferSequence coords =
        (VecBufferSequence) edgeTable.getRecordData("coordinates").getBackingData();

    for (VPFRecord row : edgeTable) {
      int id = row.getId();
      VPFRecord mbrRow = mbrTable.getRecord(id);

      edgeInfo[VPFBufferedRecordData.indexFromId(id)] =
          new VPFPrimitiveData.EdgeInfo(
              getNumber(row.getValue("edge_type")),
              getId(row.getValue("start_node")),
              getNumber(row.getValue("end_node")),
              getId(row.getValue("left_face")),
              getId(row.getValue("right_face")),
              getId(row.getValue("left_edge")),
              getId(row.getValue("right_edge")),
              isEdgeOnTileBoundary(row),
              VPFUtils.getExtent(mbrRow));
    }

    primitiveData.setPrimitiveInfo(VPFConstants.EDGE_PRIMITIVE_TABLE, edgeInfo);
    primitiveData.setPrimitiveCoords(VPFConstants.EDGE_PRIMITIVE_TABLE, coords);
  }
  protected void buildFacePrimitives(
      VPFCoverage coverage, VPFTile tile, VPFPrimitiveData primitiveData) {
    VPFBufferedRecordData faceTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.FACE_PRIMITIVE_TABLE);
    if (faceTable == null) return;

    VPFBufferedRecordData mbrTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.FACE_BOUNDING_RECTANGLE_TABLE);
    if (mbrTable == null) return;

    VPFBufferedRecordData ringTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.RING_TABLE);
    if (ringTable == null) return;

    VPFPrimitiveData.PrimitiveInfo[] edgeInfo =
        primitiveData.getPrimitiveInfo(VPFConstants.EDGE_PRIMITIVE_TABLE);

    int numFaces = faceTable.getNumRecords();
    VPFPrimitiveData.FaceInfo[] faceInfo = new VPFPrimitiveData.FaceInfo[numFaces];

    for (VPFRecord faceRow : faceTable) {
      int faceId = faceRow.getId();
      VPFRecord mbrRow = mbrTable.getRecord(faceId);

      // Face ID 1 is reserved for the "universe face", which does not have any associated geometry.
      if (faceId == 1) continue;

      // The first ring primitive associated with the face primitive defines the outer ring. The
      // face primitive must
      // at least contain coordinates for an outer ring.

      int ringId = ((Number) faceRow.getValue("ring_ptr")).intValue();
      VPFRecord ringRow = ringTable.getRecord(ringId);
      VPFPrimitiveData.Ring outerRing = this.buildRing(ringRow, edgeInfo);

      // The ring table maintains an order relationship for its rows. The first record of a new face
      // id will always
      // be defined as the outer ring. Any repeating records with an identical face value will
      // define inner rings.

      ArrayList<VPFPrimitiveData.Ring> innerRingList = new ArrayList<VPFPrimitiveData.Ring>();

      for (ringId = ringId + 1; ringId <= ringTable.getNumRecords(); ringId++) {
        ringRow = ringTable.getRecord(ringId);

        // Break on the first ring primitive row which isn't associated with the face. Because the
        // ring rows
        // maintain an ordering with respect to face id, there will be no other ring rows
        // corresponding to this
        // face.
        if (faceId != getId(ringRow.getValue("face_id"))) break;

        VPFPrimitiveData.Ring innerRing = this.buildRing(ringRow, edgeInfo);
        if (innerRing != null) innerRingList.add(innerRing);
      }

      VPFPrimitiveData.Ring[] innerRings = new VPFPrimitiveData.Ring[innerRingList.size()];
      innerRingList.toArray(innerRings);

      faceInfo[VPFBufferedRecordData.indexFromId(faceId)] =
          new VPFPrimitiveData.FaceInfo(outerRing, innerRings, VPFUtils.getExtent(mbrRow));
    }

    primitiveData.setPrimitiveInfo(VPFConstants.FACE_PRIMITIVE_TABLE, faceInfo);
  }