protected void buildTextPrimitives(
      VPFCoverage coverage, VPFTile tile, VPFPrimitiveData primitiveData) {
    VPFBufferedRecordData textTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.TEXT_PRIMITIVE_TABLE);
    if (textTable == null || textTable.getNumRecords() == 0) return;

    int numText = textTable.getNumRecords();
    VPFPrimitiveData.BasicPrimitiveInfo[] textInfo =
        new VPFPrimitiveData.BasicPrimitiveInfo[numText];
    VecBufferSequence coords =
        (VecBufferSequence) textTable.getRecordData("shape_line").getBackingData();
    CompoundStringBuilder strings =
        (CompoundStringBuilder) textTable.getRecordData("string").getBackingData();

    for (VPFRecord row : textTable) {
      int id = row.getId();

      textInfo[VPFBufferedRecordData.indexFromId(id)] =
          new VPFPrimitiveData.BasicPrimitiveInfo(
              VPFBoundingBox.fromVecBuffer(coords.subBuffer(id)));
    }

    primitiveData.setPrimitiveInfo(VPFConstants.TEXT_PRIMITIVE_TABLE, textInfo);
    primitiveData.setPrimitiveCoords(VPFConstants.TEXT_PRIMITIVE_TABLE, coords);
    primitiveData.setPrimitiveStrings(VPFConstants.TEXT_PRIMITIVE_TABLE, strings);
  }
  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 buildNodePrimitives(
      VPFCoverage coverage, VPFTile tile, VPFPrimitiveData primitiveData) {
    VPFBufferedRecordData nodeTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.NODE_PRIMITIVE_TABLE);
    if (nodeTable != null && nodeTable.getNumRecords() > 0)
      this.buildNodePrimitives(nodeTable, VPFConstants.NODE_PRIMITIVE_TABLE, primitiveData);

    VPFBufferedRecordData entityNodeTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.ENTITY_NODE_PRIMITIVE_TABLE);
    if (entityNodeTable != null && entityNodeTable.getNumRecords() > 0)
      this.buildNodePrimitives(
          entityNodeTable, VPFConstants.ENTITY_NODE_PRIMITIVE_TABLE, primitiveData);

    VPFBufferedRecordData connectedNodeTable =
        this.createPrimitiveTable(coverage, tile, VPFConstants.CONNECTED_NODE_PRIMITIVE_TABLE);
    if (connectedNodeTable != null && connectedNodeTable.getNumRecords() > 0)
      this.buildNodePrimitives(
          connectedNodeTable, VPFConstants.CONNECTED_NODE_PRIMITIVE_TABLE, primitiveData);
  }
  protected boolean buildNodePrimitives(
      VPFBufferedRecordData table, String name, VPFPrimitiveData primitiveData) {
    int numNodes = table.getNumRecords();
    VPFPrimitiveData.BasicPrimitiveInfo[] nodeInfo =
        new VPFPrimitiveData.BasicPrimitiveInfo[numNodes];
    VecBufferSequence coords =
        (VecBufferSequence) table.getRecordData("coordinate").getBackingData();

    for (VPFRecord row : table) {
      int id = row.getId();

      nodeInfo[VPFBufferedRecordData.indexFromId(id)] =
          new VPFPrimitiveData.BasicPrimitiveInfo(
              VPFBoundingBox.fromVecBuffer(coords.subBuffer(id)));
    }

    primitiveData.setPrimitiveInfo(name, nodeInfo);
    primitiveData.setPrimitiveCoords(name, coords);
    return true;
  }
  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);
  }