/** {@inheritDoc} */
 public Iterable<AVList> getLinks() {
   if (this.webViewWindowPtr != 0) {
     AVList[] links = WindowsWebViewJNI.getLinks(this.webViewWindowPtr);
     if (links != null) return Arrays.asList(links);
   }
   return Collections.emptyList();
 }
  protected Extent computeExtent(Globe globe, double verticalExaggeration) {
    List<Vec4> points = this.computeMinimalGeometry(globe, verticalExaggeration);
    if (points == null || points.isEmpty()) return null;

    // Add a point at the center of this polygon to the points used to compute its extent. The
    // center point captures
    // the curvature of the globe when the polygon's minimal geometry only contain any points near
    // the polygon's
    // edges.
    Vec4 centerPoint = Vec4.computeAveragePoint(points);
    LatLon centerLocation = globe.computePositionFromPoint(centerPoint);
    this.makeExtremePoints(globe, verticalExaggeration, Arrays.asList(centerLocation), points);

    return Box.computeBoundingBox(points);
  }
  /**
   * Create a collection of layer lists and their included layers described by an array of XML
   * layer-list description elements.
   *
   * <p>Any exceptions occurring during creation of the layer lists or their included layers are
   * logged and not re-thrown. The layers associated with the exceptions are not included in the
   * returned layer list.
   *
   * @param elements the XML elements describing the layer lists to create.
   * @param params any parameters to apply when creating the included layers.
   * @return an array containing the specified layer lists.
   */
  protected LayerList[] createLayerLists(Element[] elements, AVList params) {
    ArrayList<LayerList> layerLists = new ArrayList<LayerList>();

    for (Element element : elements) {
      try {
        String href = WWXML.getText(element, "@href");
        if (href != null && href.length() > 0) {
          Object o = this.createFromConfigSource(href, params);
          if (o == null) continue;

          if (o instanceof Layer) {
            LayerList ll = new LayerList();
            ll.add((Layer) o);
            o = ll;
          }

          if (o instanceof LayerList) {
            LayerList list = (LayerList) o;
            if (list != null && list.size() > 0) layerLists.add(list);
          } else if (o instanceof LayerList[]) {
            LayerList[] lists = (LayerList[]) o;
            if (lists != null && lists.length > 0) layerLists.addAll(Arrays.asList(lists));
          } else {
            String msg =
                Logging.getMessage("LayerFactory.UnexpectedTypeForLayer", o.getClass().getName());
            Logging.logger().log(java.util.logging.Level.WARNING, msg);
          }

          continue;
        }

        String title = WWXML.getText(element, "@title");
        Element[] children = WWXML.getElements(element, "./Layer", null);
        if (children != null && children.length > 0) {
          LayerList list = this.createLayerList(children, params);
          if (list != null && list.size() > 0) {
            layerLists.add(list);
            if (title != null && title.length() > 0) list.setValue(AVKey.DISPLAY_NAME, title);
          }
        }
      } catch (Exception e) {
        Logging.logger().log(java.util.logging.Level.WARNING, e.getMessage(), e);
        // keep going to create other layers
      }
    }

    return layerLists.toArray(new LayerList[layerLists.size()]);
  }
 public List<Tile> getTiles() {
   if (tileKeys.isEmpty()) {
     Tile[] tiles = buildTiles(this.placeNameService, this);
     // load tileKeys
     for (Tile t : tiles) {
       tileKeys.add(t.getFileCachePath());
       WorldWind.getMemoryCache(Tile.class.getName()).add(t.getFileCachePath(), t);
     }
     return Arrays.asList(tiles);
   } else {
     List<Tile> dataTiles = new ArrayList<Tile>();
     for (String s : tileKeys) {
       Tile t = (Tile) WorldWind.getMemoryCache(Tile.class.getName()).getObject(s);
       if (t != null) {
         dataTiles.add(t);
       }
     }
     return dataTiles;
   }
 }
  private void draw(DrawContext dc) {
    this.referencePoint = this.computeReferencePoint(dc);

    this.assembleTiles(dc); // Determine the tiles to draw.

    if (this.currentTiles.size() >= 1) {
      MercatorTextureTile[] sortedTiles = new MercatorTextureTile[this.currentTiles.size()];
      sortedTiles = this.currentTiles.toArray(sortedTiles);
      Arrays.sort(sortedTiles, levelComparer);

      GL gl = dc.getGL();

      if (this.isUseTransparentTextures() || this.getOpacity() < 1) {
        gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT | GL.GL_CURRENT_BIT);
        gl.glColor4d(1d, 1d, 1d, this.getOpacity());
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
      } else {
        gl.glPushAttrib(GL.GL_COLOR_BUFFER_BIT | GL.GL_POLYGON_BIT);
      }

      gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
      gl.glEnable(GL.GL_CULL_FACE);
      gl.glCullFace(GL.GL_BACK);

      dc.setPerFrameStatistic(
          PerformanceStatistic.IMAGE_TILE_COUNT, this.tileCountName, this.currentTiles.size());
      dc.getGeographicSurfaceTileRenderer().renderTiles(dc, this.currentTiles);

      gl.glPopAttrib();

      if (this.drawTileIDs) this.drawTileIDs(dc, this.currentTiles);

      if (this.drawBoundingVolumes) this.drawBoundingVolumes(dc, this.currentTiles);

      this.currentTiles.clear();
    }

    this.sendRequests();
    this.requestQ.clear();
  }
  protected void doMoveTo(Position oldRef, Position newRef) {
    if (oldRef == null) {
      String message = "nullValue.OldRefIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (newRef == null) {
      String message = "nullValue.NewRefIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    super.doMoveTo(oldRef, newRef);

    int count = this.locations.size();
    LatLon[] newLocations = new LatLon[count];
    for (int i = 0; i < count; i++) {
      LatLon ll = this.locations.get(i);
      double distance = LatLon.greatCircleDistance(oldRef, ll).radians;
      double azimuth = LatLon.greatCircleAzimuth(oldRef, ll).radians;
      newLocations[i] = LatLon.greatCircleEndPosition(newRef, azimuth, distance);
    }
    this.setLocations(Arrays.asList(newLocations));
  }
  protected List<Sector> computeSectors(Globe globe) {
    if (this.sector == null || this.sector.equals(Sector.EMPTY_SECTOR)) return null;

    return Arrays.asList(this.sector);
  }
  protected int computeCartesianPolygon(
      Globe globe,
      List<? extends LatLon> locations,
      List<Boolean> edgeFlags,
      Vec4[] points,
      Boolean[] edgeFlagArray,
      Matrix[] transform) {
    if (globe == null) {
      String message = Logging.getMessage("nullValue.GlobeIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (locations == null) {
      String message = "nullValue.LocationsIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (points == null) {
      String message = "nullValue.LocationsIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (points.length < (1 + locations.size())) {
      String message =
          Logging.getMessage(
              "generic.ArrayInvalidLength", "points.length < " + (1 + locations.size()));
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (transform == null) {
      String message = "nullValue.TransformIsNull";
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }
    if (transform.length < 1) {
      String message = Logging.getMessage("generic.ArrayInvalidLength", "transform.length < 1");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    // Allocate space to hold the list of locations and location vertices.
    int locationCount = locations.size();

    // Compute the cartesian points for each location.
    for (int i = 0; i < locationCount; i++) {
      LatLon ll = locations.get(i);
      points[i] = globe.computePointFromPosition(ll.getLatitude(), ll.getLongitude(), 0.0);

      if (edgeFlagArray != null) edgeFlagArray[i] = (edgeFlags != null) ? edgeFlags.get(i) : true;
    }

    // Compute the average of the cartesian points.
    Vec4 centerPoint = Vec4.computeAveragePoint(Arrays.asList(points));

    // Test whether the polygon is closed. If it is not closed, repeat the first vertex.
    if (!points[0].equals(points[locationCount - 1])) {
      points[locationCount] = points[0];
      if (edgeFlagArray != null) edgeFlagArray[locationCount] = edgeFlagArray[0];

      locationCount++;
    }

    // Compute a transform that will map the cartesian points to a local coordinate system centered
    // at the average
    // of the points and oriented with the globe surface.
    Position centerPos = globe.computePositionFromPoint(centerPoint);
    Matrix tx = globe.computeSurfaceOrientationAtPosition(centerPos);
    Matrix txInv = tx.getInverse();
    // Map the cartesian points to a local coordinate space.
    for (int i = 0; i < locationCount; i++) {
      points[i] = points[i].transformBy4(txInv);
    }

    transform[0] = tx;

    return locationCount;
  }
/**
 * Represents a single record of a shapefile.
 *
 * @author Patrick Murris
 * @version $Id$
 */
public abstract class ShapefileRecord {
  protected Shapefile shapeFile;
  protected int recordNumber;
  protected int contentLengthInBytes;
  protected String shapeType;
  protected DBaseRecord attributes;
  protected int numberOfParts;
  protected int numberOfPoints;
  protected int firstPartNumber;
  /** Indicates if the record's point coordinates should be normalized. Defaults to false. */
  protected boolean normalizePoints;

  protected static final int RECORD_HEADER_LENGTH = 8;
  protected static List<String> measureTypes =
      new ArrayList<String>(
          Arrays.asList(
              Shapefile.SHAPE_POINT_M, Shapefile.SHAPE_POINT_Z,
              Shapefile.SHAPE_MULTI_POINT_M, Shapefile.SHAPE_MULTI_POINT_Z,
              Shapefile.SHAPE_POLYLINE_M, Shapefile.SHAPE_POLYLINE_Z,
              Shapefile.SHAPE_POLYGON_M, Shapefile.SHAPE_POLYGON_Z));

  /**
   * Constructs a record instance from the given {@link java.nio.ByteBuffer}. The buffer's current
   * position must be the start of the record, and will be the start of the next record when the
   * constructor returns.
   *
   * @param shapeFile the parent {@link Shapefile}.
   * @param buffer the shapefile record {@link java.nio.ByteBuffer} to read from.
   * @throws IllegalArgumentException if any argument is null or otherwise invalid.
   * @throws gov.nasa.worldwind.exception.WWRuntimeException if the record's shape type does not
   *     match that of the shapefile.
   */
  public ShapefileRecord(Shapefile shapeFile, ByteBuffer buffer) {
    if (shapeFile == null) {
      String message = Logging.getMessage("nullValue.ShapefileIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

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

    // Save the buffer's current position.
    int pos = buffer.position();
    try {
      this.readFromBuffer(shapeFile, buffer);
    } finally {
      // Move to the end of the record.
      buffer.position(pos + this.contentLengthInBytes + RECORD_HEADER_LENGTH);
    }
  }

  /**
   * Returns the shapefile containing this record.
   *
   * @return the shapefile containing this record.
   */
  public Shapefile getShapeFile() {
    return this.shapeFile;
  }

  /**
   * Returns the zero-orgin ordinal position of the record in the shapefile.
   *
   * @return the record's ordinal position in the shapefile.
   */
  public int getRecordNumber() {
    return this.recordNumber;
  }

  /**
   * Returns the record's shape type.
   *
   * @return the record' shape type. See {@link Shapefile} for a list of the defined shape types.
   */
  public String getShapeType() {
    return this.shapeType;
  }

  /**
   * Returns the record's attributes.
   *
   * @return the record's attributes.
   */
  public DBaseRecord getAttributes() {
    return this.attributes;
  }

  /**
   * Specifies the shapefile's attributes.
   *
   * @param attributes the shapefile's attributes. May be null.
   */
  public void setAttributes(DBaseRecord attributes) {
    this.attributes = attributes;
  }

  /**
   * Returns the number of parts in the record.
   *
   * @return the number of parts in the record.
   */
  public int getNumberOfParts() {
    return this.numberOfParts;
  }

  /**
   * Returns the first part number in the record.
   *
   * @return the first part number in the record.
   */
  public int getFirstPartNumber() {
    return this.firstPartNumber;
  }

  /**
   * Returns the last part number in the record.
   *
   * @return the last part number in the record.
   */
  public int getLastPartNumber() {
    return this.firstPartNumber + this.numberOfParts - 1;
  }

  /**
   * Returns the number of points in the record.
   *
   * @return the number of points in the record.
   */
  public int getNumberOfPoints() {
    return this.numberOfPoints;
  }

  /**
   * Returns the number of points in a specified part of the record.
   *
   * @param partNumber the part number for which to return the number of points.
   * @return the number of points in the specified part.
   */
  public int getNumberOfPoints(int partNumber) {
    if (partNumber < 0 || partNumber >= this.getNumberOfParts()) {
      String message = Logging.getMessage("generic.indexOutOfRange", partNumber);
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    int shapefilePartNumber = this.getFirstPartNumber() + partNumber;
    return this.getShapeFile().getPointBuffer().subBufferSize(shapefilePartNumber);
  }

  /**
   * Returns the {@link gov.nasa.worldwind.util.VecBuffer} holding the X and Y points of a specified
   * part.
   *
   * @param partNumber the part for which to return the point buffer.
   * @return the buffer holding the part's points. The points are ordered X0,Y0,X1,Y1,...Xn-1,Yn-1,
   *     where "n" is the number of points in the part.
   */
  public VecBuffer getPointBuffer(int partNumber) {
    if (partNumber < 0 || partNumber >= this.getNumberOfParts()) {
      String message = Logging.getMessage("generic.indexOutOfRange", partNumber);
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    int shapefilePartNumber = this.getFirstPartNumber() + partNumber;
    return this.getShapeFile().getPointBuffer().subBuffer(shapefilePartNumber);
  }

  /**
   * Returns the {@link gov.nasa.worldwind.util.CompoundVecBuffer} holding all the X and Y points
   * for this record. The returned buffer contains one sub-buffer for each of this record's parts.
   * The coordinates for each part are referenced by invoking {@link
   * gov.nasa.worldwind.util.CompoundVecBuffer#subBuffer(int)}, where the index is one of this
   * record's part IDs, starting with 0 and ending with <code>{@link #getNumberOfParts()} - 1</code>
   * (inclusive).
   *
   * @return a CompoundVecBuffer that holds this record's coordinate data.
   */
  public CompoundVecBuffer getCompoundPointBuffer() {
    return this.getShapeFile()
        .getPointBuffer()
        .slice(this.getFirstPartNumber(), this.getLastPartNumber());
  }

  /**
   * Reads and parses subclass-specific contents of a shapefile record from a specified buffer. The
   * buffer's current position must be the start of the subclass' unique contents and will be the
   * start of the next record when the constructor returns.
   *
   * @param shapefile the containing {@link Shapefile}.
   * @param buffer the shapefile record {@link java.nio.ByteBuffer} to read from.
   */
  protected abstract void doReadFromBuffer(Shapefile shapefile, ByteBuffer buffer);

  /**
   * Reads and parses the contents of a shapefile record from a specified buffer. The buffer's
   * current position must be the start of the record and will be the start of the next record when
   * the constructor returns.
   *
   * @param shapefile the containing {@link Shapefile}.
   * @param buffer the shapefile record {@link java.nio.ByteBuffer} to read from.
   */
  protected void readFromBuffer(Shapefile shapefile, ByteBuffer buffer) {
    // Read record number and record length - big endian.
    buffer.order(ByteOrder.BIG_ENDIAN);
    this.recordNumber = buffer.getInt();
    this.contentLengthInBytes = buffer.getInt() * 2;

    // Read shape type - little endian
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    int type = buffer.getInt();
    String shapeType = shapefile.getShapeType(type);
    this.validateShapeType(shapefile, shapeType);

    this.shapeType = shapeType;
    this.shapeFile = shapefile;

    this.doReadFromBuffer(shapefile, buffer);
  }

  /**
   * Verifies that the record's shape type matches the expected one, typically that of the
   * shapefile. All non-null records in a Shapefile must be of the same type. Throws an exception if
   * the types do not match and the shape type is not <code>{@link Shapefile#SHAPE_NULL}</code>.
   * Records of type <code>SHAPE_NULL</code> are always valid, and may appear in any Shapefile.
   *
   * <p>For details, see the ESRI Shapefile specification at <a
   * href="http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf"/>, pages 4 and 5.
   *
   * @param shapefile the shapefile.
   * @param shapeType the record's shape type.
   * @throws WWRuntimeException if the shape types do not match.
   * @throws IllegalArgumentException if the specified shape type is null.
   */
  protected void validateShapeType(Shapefile shapefile, String shapeType) {
    if (shapeType == null) {
      String message = Logging.getMessage("nullValue.ShapeType");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    if (!shapeType.equals(shapefile.getShapeType()) && !shapeType.equals(Shapefile.SHAPE_NULL)) {
      String message = Logging.getMessage("SHP.UnsupportedShapeType", shapeType);
      Logging.logger().severe(message);
      throw new WWRuntimeException(message);
    }
  }

  /**
   * Indicates whether the record is a shape type capable of containing optional measure values.
   * Does not indicate whether the record actually contains measure values.
   *
   * @return true if the record may contain measure values.
   */
  protected boolean isMeasureType() {
    return Shapefile.isMeasureType(this.getShapeType());
  }

  /**
   * Indicates whether the record is a shape type containing Z values.
   *
   * @return true if the record is a type containing Z values.
   */
  protected boolean isZType() {
    return Shapefile.isZType(this.getShapeType());
  }

  /**
   * Returns whether the record's point coordinates should be normalized.
   *
   * @return <code>true</code> if the record's points should be normalized; <code>false</code>
   *     otherwise.
   */
  public boolean isNormalizePoints() {
    return this.normalizePoints;
  }

  /**
   * Specifies if the record's point coordinates should be normalized. Defaults to <code>false
   * </code>.
   *
   * @param normalizePoints <code>true</code> if the record's points should be normalized; <code>
   *     false</code> otherwise.
   */
  public void setNormalizePoints(boolean normalizePoints) {
    this.normalizePoints = normalizePoints;
  }

  public void exportAsXML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException {
    if (xmlWriter == null) {
      String message = Logging.getMessage("Export.UnsupportedOutputObject");
      Logging.logger().warning(message);
      throw new IllegalArgumentException(message);
    }

    xmlWriter.writeStartElement("Record");

    xmlWriter.writeAttribute("id", Integer.toString(this.getRecordNumber()));
    xmlWriter.writeAttribute(
        "shape", this.getShapeType().substring(this.getShapeType().lastIndexOf("Shape") + 5));
    xmlWriter.writeAttribute("parts", Integer.toString(this.getNumberOfParts()));
    xmlWriter.writeAttribute("points", Integer.toString(this.getNumberOfPoints()));
    xmlWriter.writeCharacters("\n");

    for (Map.Entry<String, Object> a : this.getAttributes().getEntries()) {
      xmlWriter.writeStartElement("Attribute");

      xmlWriter.writeAttribute("name", a.getKey() != null ? a.getKey().toString() : "");
      xmlWriter.writeAttribute("value", a.getValue() != null ? a.getValue().toString() : "");

      xmlWriter.writeEndElement(); // Attribute
      xmlWriter.writeCharacters("\n");
    }

    if (this.getNumberOfParts() > 0) {
      VecBuffer vb = this.getPointBuffer(0);
      for (LatLon ll : vb.getLocations()) {
        xmlWriter.writeStartElement("Point");
        xmlWriter.writeAttribute("x", Double.toString(ll.getLatitude().degrees));
        xmlWriter.writeAttribute("y", Double.toString(ll.getLongitude().degrees));
        xmlWriter.writeEndElement(); // Point
        xmlWriter.writeCharacters("\n");
      }
    }

    // TODO: export record-type specific fields

    xmlWriter.writeEndElement(); // Record
  }

  /**
   * Export the record as KML. This implementation does nothing; subclasses may override this method
   * to provide KML export.
   *
   * @param xmlWriter Writer to receive KML.
   * @throws IOException If an exception occurs while writing the KML
   * @throws XMLStreamException If an exception occurs while exporting the data.
   */
  public void exportAsKML(XMLStreamWriter xmlWriter) throws IOException, XMLStreamException {}

  public void printInfo(boolean printCoordinates) {
    System.out.printf(
        "%d, %s: %d parts, %d points",
        this.getRecordNumber(),
        this.getShapeType(),
        this.getNumberOfParts(),
        this.getNumberOfPoints());
    for (Map.Entry<String, Object> a : this.getAttributes().getEntries()) {
      if (a.getKey() != null) System.out.printf(", %s", a.getKey());
      if (a.getValue() != null) System.out.printf(", %s", a.getValue());
    }
    System.out.println();

    System.out.print("\tAttributes: ");
    for (Map.Entry<String, Object> entry : this.getAttributes().getEntries()) {
      System.out.printf("%s = %s, ", entry.getKey(), entry.getValue());
    }
    System.out.println();

    if (!printCoordinates) return;

    VecBuffer vb = this.getPointBuffer(0);
    for (LatLon ll : vb.getLocations()) {
      System.out.printf("\t%f, %f\n", ll.getLatitude().degrees, ll.getLongitude().degrees);
    }
  }
}