public WorldWindowImpl() {
    this.sceneController =
        (SceneController) WorldWind.createConfigurationComponent(AVKey.SCENE_CONTROLLER_CLASS_NAME);

    // Set up to initiate a repaint whenever a file is retrieved and added to the local file store.
    WorldWind.getDataFileStore().addPropertyChangeListener(this);
  }
  /**
   * @param placeNameServiceSet the set of PlaceNameService objects that PlaceNameLayer will render.
   * @throws IllegalArgumentException if {@link
   *     gov.nasa.worldwind.layers.placename.PlaceNameServiceSet} is null
   */
  public PlaceNameLayer(PlaceNameServiceSet placeNameServiceSet) {
    if (placeNameServiceSet == null) {
      String message = Logging.getMessage("nullValue.PlaceNameServiceSetIsNull");
      Logging.logger().fine(message);
      throw new IllegalArgumentException(message);
    }

    //
    this.placeNameServiceSet = placeNameServiceSet.deepCopy();
    for (int i = 0; i < this.placeNameServiceSet.getServiceCount(); i++) {
      // todo do this for long as well and pick min
      int calc1 =
          (int)
              (PlaceNameService.TILING_SECTOR.getDeltaLatDegrees()
                  / this.placeNameServiceSet
                      .getService(i)
                      .getTileDelta()
                      .getLatitude()
                      .getDegrees());
      int numLevels = (int) Math.log(calc1);
      navTiles.add(
          new NavigationTile(
              this.placeNameServiceSet.getService(i),
              PlaceNameService.TILING_SECTOR,
              numLevels,
              "top"));
    }

    if (!WorldWind.getMemoryCacheSet().containsCache(Tile.class.getName())) {
      long size = Configuration.getLongValue(AVKey.PLACENAME_LAYER_CACHE_SIZE, 2000000L);
      MemoryCache cache = new BasicMemoryCache((long) (0.85 * size), size);
      cache.setName("Placename Tiles");
      WorldWind.getMemoryCacheSet().addCache(Tile.class.getName(), cache);
    }
  }
 private void sendRequests() {
   Runnable task = this.requestQ.poll();
   while (task != null) {
     if (!WorldWind.getTaskService().isFull()) {
       WorldWind.getTaskService().addTask(task);
     }
     task = this.requestQ.poll();
   }
 }
  /**
   * Thread's off a task to determine whether the resource is local or remote and then retrieves it
   * either from disk cache or a remote server.
   *
   * @param dc the current draw context.
   */
  protected void requestResource(DrawContext dc) {
    if (WorldWind.getTaskService().isFull()) return;

    KMLLink link = this.model.getLink();
    if (link == null) return;

    String address = link.getAddress(dc);
    if (address != null) address = address.trim();

    if (WWUtil.isEmpty(address)) return;

    WorldWind.getTaskService().addTask(new RequestTask(this, address));
  }
  /**
   * Causes resources used by the World Window to be freed. The World Window cannot be used once
   * this method is called. An OpenGL context for the window must be current.
   */
  public void shutdown() {
    WorldWind.getDataFileStore().removePropertyChangeListener(this);

    if (this.inputHandler != null) {
      this.inputHandler.dispose();
      this.inputHandler = new NoOpInputHandler();
    }

    // Clear the texture cache
    if (this.getGpuResourceCache() != null) this.getGpuResourceCache().clear();

    // Dispose all the layers //  TODO: Need per-window dispose for layers
    if (this.getModel() != null && this.getModel().getLayers() != null) {
      for (Layer layer : this.getModel().getLayers()) {
        try {
          layer.dispose();
        } catch (Exception e) {
          Logging.logger()
              .log(
                  java.util.logging.Level.SEVERE,
                  Logging.getMessage("WorldWindowGLCanvas.ExceptionWhileShuttingDownWorldWindow"),
                  e);
        }
      }
    }

    SceneController sc = this.getSceneController();
    if (sc != null) sc.dispose();
  }
  protected boolean loadTile(Tile tile, java.net.URL url) {
    if (WWIO.isFileOutOfDate(url, this.placeNameServiceSet.getExpiryTime())) {
      // The file has expired. Delete it then request download of newer.
      this.getDataFileStore().removeFile(url);
      String message = Logging.getMessage("generic.DataFileExpired", url);
      Logging.logger().fine(message);
      return false;
    }

    PlaceNameChunk tileData;
    synchronized (this.fileLock) {
      tileData = readTileData(tile, url);
    }

    if (tileData == null) {
      // Assume that something's wrong with the file and delete it.
      this.getDataFileStore().removeFile(url);
      tile.getPlaceNameService()
          .markResourceAbsent(tile.getPlaceNameService().getTileNumber(tile.row, tile.column));
      String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
      Logging.logger().fine(message);
      return false;
    }

    tile.setDataChunk(tileData);
    WorldWind.getMemoryCache(Tile.class.getName()).add(tile.getFileCachePath(), tile);
    return true;
  }
  /**
   * Resolves a reference to a local element identified by address and identifier, where {@code
   * linkBase} identifies a document, including the current document, and {@code linkRef} is the id
   * of the desired element.
   *
   * <p>If {@code linkBase} refers to a local COLLADA file and {@code linkRef} is non-null, the
   * return value is the element identified by {@code linkRef}. If {@code linkRef} is null, the
   * return value is a parsed {@link ColladaRoot} for the COLLADA file identified by {@code
   * linkBase}. Otherwise, {@code linkBase} is returned.
   *
   * @param linkBase the address of the document containing the requested element.
   * @param linkRef the element's identifier.
   * @return the requested element, or null if the element is not found.
   * @throws IllegalArgumentException if the address is null.
   */
  protected Object resolveLocalReference(String linkBase, String linkRef) {
    if (linkBase == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      File file = new File(linkBase);

      if (!file.exists()) return null;

      // Determine whether the file is a COLLADA document. If not, just return the file path.
      if (!WWIO.isContentType(file, ColladaConstants.COLLADA_MIME_TYPE))
        return file.toURI().toString();

      // Attempt to open and parse the COLLADA file.
      ColladaRoot refRoot = ColladaRoot.createAndParse(file);
      // An exception is thrown if parsing fails, so no need to check for null.

      // Add the parsed file to the session cache so it doesn't have to be parsed again.
      WorldWind.getSessionCache().put(linkBase, refRoot);

      // Now check the newly opened COLLADA file for the referenced item, if a reference was
      // specified.
      if (linkRef != null) return refRoot.getItemByID(linkRef);
      else return refRoot;
    } catch (Exception e) {
      String message =
          Logging.getMessage("generic.UnableToResolveReference", linkBase + "/" + linkRef);
      Logging.logger().warning(message);
      return null;
    }
  }
    public ByteBuffer run(Retriever retriever) {
      if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL)) return null;

      HTTPRetriever htr = (HTTPRetriever) retriever;
      if (htr.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
        // Mark tile as missing to avoid excessive attempts
        MercatorTiledImageLayer.this.levels.markResourceAbsent(tile);
        return null;
      }

      if (htr.getResponseCode() != HttpURLConnection.HTTP_OK) return null;

      URLRetriever r = (URLRetriever) retriever;
      ByteBuffer buffer = r.getBuffer();

      String suffix = WWIO.makeSuffixForMimeType(htr.getContentType());
      if (suffix == null) {
        return null; // TODO: log error
      }

      String path = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
      path += suffix;

      final File outFile = WorldWind.getDataFileStore().newFile(path);
      if (outFile == null) return null;

      try {
        WWIO.saveBuffer(buffer, outFile);
        return buffer;
      } catch (IOException e) {
        e.printStackTrace(); // TODO: log error
        return null;
      }
    }
  /**
   * Resolves a reference to a remote element identified by address and identifier, where {@code
   * linkBase} identifies a remote document, and {@code linkRef} is the id of the desired element.
   * This method retrieves resources asynchronously using the {@link
   * gov.nasa.worldwind.cache.FileStore}.
   *
   * <p>The return value is null if the file is not yet available in the FileStore. If {@code
   * linkBase} refers to a COLLADA file and {@code linkRef} is non-null, the return value is the
   * element identified by {@code linkRef}. If {@code linkBase} refers to a COLLADA file and {@code
   * linkRef} is null, the return value is a parsed {@link ColladaRoot} for the COLLADA file
   * identified by {@code linkBase}. Otherwise the return value is a {@link URL} to the file in the
   * file cache.
   *
   * @param linkBase the address of the document containing the requested element.
   * @param linkRef the element's identifier.
   * @return URL to the requested file, parsed ColladaRoot, or COLLADA element. Returns null if the
   *     document is not yet available in the FileStore.
   * @throws IllegalArgumentException if the {@code linkBase} is null.
   */
  public Object resolveRemoteReference(String linkBase, String linkRef) {
    if (linkBase == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      // See if it's in the cache. If not, requestFile will start another thread to retrieve it and
      // return null.
      URL url = WorldWind.getDataFileStore().requestFile(linkBase);
      if (url == null) return null;

      // It's in the cache. If it's a COLLADA file try to parse it so we can search for the
      // specified reference.
      // If it's not COLLADA, just return the url for the cached file.
      String contentType = WorldWind.getDataFileStore().getContentType(linkBase);
      if (contentType == null) {
        String suffix = WWIO.getSuffix(linkBase.split(";")[0]); // strip of trailing garbage
        if (!WWUtil.isEmpty(suffix)) contentType = WWIO.makeMimeTypeForSuffix(suffix);
      }

      if (!this.canParseContentType(contentType)) return url;

      // If the file is a COLLADA document, attempt to open it. We can't open it as a File with
      // createAndParse
      // because the ColladaRoot that will be created needs to have the remote address in order to
      // resolve any
      // relative references within it.
      ColladaRoot refRoot = this.parseCachedColladaFile(url, linkBase);

      // Add the parsed file to the session cache so it doesn't have to be parsed again.
      WorldWind.getSessionCache().put(linkBase, refRoot);

      // Now check the newly opened COLLADA file for the referenced item, if a reference was
      // specified.
      if (linkRef != null) return refRoot.getItemByID(linkRef);
      else return refRoot;
    } catch (Exception e) {
      String message =
          Logging.getMessage("generic.UnableToResolveReference", linkBase + "/" + linkRef);
      Logging.logger().warning(message);
      return null;
    }
  }
 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;
   }
 }
  /**
   * If this instance's image source is a <code>BufferedImage</code>, creates and returns the
   * texture, otherwise creates a task in a separate thread to retrieve it from its local or remote
   * location.
   *
   * @param dc the current draw context.
   * @return the new texture, or null if the texture is not yet available.
   */
  protected Texture requestTexture(DrawContext dc) {
    if (this.isBufferedImageSource()) return this.makeBufferedImageTexture(dc);

    if (this.getTextureData() != null && this.getTexture(dc) == null)
      return this.makeTextureFromTextureData(dc);

    if (WorldWind.getTaskService().isFull()) return null;

    Runnable task = this.createRequestTask();
    if (WorldWind.getTaskService().contains(task)) return null;

    // Use either the current layer or the layer list as the listener to notify when the request
    // completes. The
    // latter is used when the image source is requested during ordered rendering and the current
    // layer is null.
    this.listener = dc.getCurrentLayer() != null ? dc.getCurrentLayer() : dc.getLayers();

    WorldWind.getTaskService().addTask(task);

    return null;
  }
  /*
   * Load cache locations
   */
  private void loadData() {
    // load combo with WW caches
    // List<File> caches =  WorldWind.getDataFileCache().getCacheLocations();
    List<? extends File> caches = WorldWind.getDataFileStore().getLocations();

    for (File file : caches) {
      combo.add(file.toString());
    }

    // load table w/ 1st location files
    // loadTable(caches.get(0));
  }
  protected void downloadTile(final Tile tile, DownloadPostProcessor postProcessor) {
    if (!this.isNetworkRetrievalEnabled()) return;

    if (!WorldWind.getRetrievalService().isAvailable()) return;

    java.net.URL url;
    try {
      url = tile.getRequestURL();
      if (WorldWind.getNetworkStatus().isHostUnavailable(url)) return;
    } catch (java.net.MalformedURLException e) {
      Logging.logger()
          .log(
              java.util.logging.Level.SEVERE,
              Logging.getMessage("layers.PlaceNameLayer.ExceptionCreatingUrl", tile),
              e);
      return;
    }

    Retriever retriever;

    if ("http".equalsIgnoreCase(url.getProtocol()) || "https".equalsIgnoreCase(url.getProtocol())) {
      if (postProcessor == null) postProcessor = new DownloadPostProcessor(this, tile);
      retriever = new HTTPRetriever(url, postProcessor);
    } else {
      Logging.logger()
          .severe(
              Logging.getMessage("layers.PlaceNameLayer.UnknownRetrievalProtocol", url.toString()));
      return;
    }

    // Apply any overridden timeouts.
    Integer cto = AVListImpl.getIntegerValue(this, AVKey.URL_CONNECT_TIMEOUT);
    if (cto != null && cto > 0) retriever.setConnectTimeout(cto);
    Integer cro = AVListImpl.getIntegerValue(this, AVKey.URL_READ_TIMEOUT);
    if (cro != null && cro > 0) retriever.setReadTimeout(cro);
    Integer srl = AVListImpl.getIntegerValue(this, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT);
    if (srl != null && srl > 0) retriever.setStaleRequestLimit(srl);

    WorldWind.getRetrievalService().runRetriever(retriever, tile.getPriority());
  }
    public WWPanel(Dimension size) {
      this.wwd = new WorldWindowGLCanvas();
      this.wwd.setSize(size);

      this.wwd.setModel((Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME));

      this.setLayout(new BorderLayout(5, 5));
      this.add(this.wwd, BorderLayout.CENTER);

      StatusBar statusBar = new StatusBar();
      statusBar.setEventSource(wwd);
      this.add(statusBar, BorderLayout.SOUTH);
    }
  @Override
  protected boolean handleElevations(
      Globe globe, TextureTile tile, Sector sector, BufferWrapper[] elevations) {
    int width = tile.getLevel().getTileWidth();
    int height = tile.getLevel().getTileHeight();

    double[] minmax = getMinMax(elevations[4], elevationModel.getMissingDataSignal());
    byte[][] bytes =
        elevationsToTexture(width, height, globe, sector, elevations, minmax[0], minmax[1]);

    File file = WorldWind.getDataFileStore().newFile(tile.getPath());
    return saveTexture(file, bytes, width, height, minmax[0], minmax[1]);
  }
  private BufferedImage requestImage(MercatorTextureTile tile, String mimeType)
      throws URISyntaxException {
    String pathBase = tile.getPath().substring(0, tile.getPath().lastIndexOf("."));
    String suffix = WWIO.makeSuffixForMimeType(mimeType);
    String path = pathBase + suffix;
    URL url = WorldWind.getDataFileStore().findFile(path, false);

    if (url == null) // image is not local
    return null;

    if (WWIO.isFileOutOfDate(url, tile.getLevel().getExpiryTime())) {
      // The file has expired. Delete it.
      WorldWind.getDataFileStore().removeFile(url);
      String message = Logging.getMessage("generic.DataFileExpired", url);
      Logging.logger().fine(message);
    } else {
      try {
        File imageFile = new File(url.toURI());
        BufferedImage image = ImageIO.read(imageFile);
        if (image == null) {
          String message = Logging.getMessage("generic.ImageReadFailed", imageFile);
          throw new RuntimeException(message);
        }

        this.levels.unmarkResourceAbsent(tile);
        return image;
      } catch (IOException e) {
        // Assume that something's wrong with the file and delete it.
        gov.nasa.worldwind.WorldWind.getDataFileStore().removeFile(url);
        this.levels.markResourceAbsent(tile);
        String message = Logging.getMessage("generic.DeletedCorruptDataFile", url);
        Logging.logger().info(message);
      }
    }

    return null;
  }
  /**
   * Resolves a reference to a local or remote file or element. If the link refers to an element in
   * the current document, this method returns that element. If the link refers to a remote
   * document, this method will initiate asynchronous retrieval of the document, and return a URL of
   * the downloaded document in the file cache, if it is available locally. If the link identifies a
   * COLLADA document, the document will be returned as a parsed ColladaRoot.
   *
   * @param link the address of the document or element to resolve. This may be a full URL, a URL
   *     fragment that identifies an element in the current document ("#myElement"), or a URL and a
   *     fragment identifier ("http://server.com/model.dae#myElement").
   * @return the requested element, or null if the element is not found.
   * @throws IllegalArgumentException if the address is null.
   */
  public Object resolveReference(String link) {
    if (link == null) {
      String message = Logging.getMessage("nullValue.DocumentSourceIsNull");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    try {
      String[] linkParts = link.split("#");
      String linkBase = linkParts[0];
      String linkRef = linkParts.length > 1 ? linkParts[1] : null;

      // See if it's a reference to an internal element.
      if (WWUtil.isEmpty(linkBase) && !WWUtil.isEmpty(linkRef)) return this.getItemByID(linkRef);

      // Interpret the path relative to the current document.
      String path = this.getSupportFilePath(linkBase);
      if (path == null) path = linkBase;

      // See if it's an already found and parsed COLLADA file.
      Object o = WorldWind.getSessionCache().get(path);
      if (o != null && o instanceof ColladaRoot)
        return linkRef != null ? ((ColladaRoot) o).getItemByID(linkRef) : o;

      URL url = WWIO.makeURL(path);
      if (url == null) {
        // See if the reference can be resolved to a local file.
        o = this.resolveLocalReference(path, linkRef);
      }

      // If we didn't find a local file, treat it as a remote reference.
      if (o == null) o = this.resolveRemoteReference(path, linkRef);

      if (o != null) return o;

      // If the reference was not resolved as a remote reference, look for a local element
      // identified by the
      // reference string. This handles the case of malformed internal references that omit the #
      // sign at the
      // beginning of the reference.
      return this.getItemByID(link);
    } catch (Exception e) {
      String message = Logging.getMessage("generic.UnableToResolveReference", link);
      Logging.logger().warning(message);
    }

    return null;
  }
  /**
   * Create a layer described by an XML layer description.
   *
   * @param domElement the XML element describing the layer to create.
   * @param params any parameters to apply when creating the layer.
   * @return a new layer
   * @throws WWUnrecognizedException if the layer type or service type given in the describing
   *     element is unrecognized.
   * @see #createTiledImageLayer(org.w3c.dom.Element, gov.nasa.worldwind.avlist.AVList).
   */
  protected Layer createFromLayerDocument(Element domElement, AVList params) {
    String className = WWXML.getText(domElement, "@className");
    if (className != null && className.length() > 0) {
      Layer layer = (Layer) WorldWind.createComponent(className);
      String actuate = WWXML.getText(domElement, "@actuate");
      layer.setEnabled(WWUtil.isEmpty(actuate) || actuate.equals("onLoad"));
      WWXML.invokePropertySetters(layer, domElement);
      return layer;
    }

    AVList props = WWXML.copyProperties(domElement, null);
    if (props != null) { // Copy params and add any properties for this layer to the copy
      if (params != null) props.setValues(params);
      params = props;
    }

    Layer layer;
    String href = WWXML.getText(domElement, "@href");
    if (href != null && href.length() > 0) {
      Object o = this.createFromConfigSource(href, params);
      if (o == null) return null;

      if (!(o instanceof Layer)) {
        String msg =
            Logging.getMessage("LayerFactory.UnexpectedTypeForLayer", o.getClass().getName());
        throw new WWRuntimeException(msg);
      }

      layer = (Layer) o;
    } else {
      String layerType = WWXML.getText(domElement, "@layerType");
      if (layerType != null && layerType.equals("TiledImageLayer")) {
        layer = this.createTiledImageLayer(domElement, params);
      } else {
        String msg = Logging.getMessage("generic.UnrecognizedLayerType", layerType);
        throw new WWUnrecognizedException(msg);
      }
    }

    if (layer != null) {
      String actuate = WWXML.getText(domElement, "@actuate");
      layer.setEnabled(actuate != null && actuate.equals("onLoad"));
      WWXML.invokePropertySetters(layer, domElement);
    }

    return layer;
  }
  /** Setup the WorldWindow. */
  protected void setupWWD() {
    // Create the default model as described in the current worldwind
    // properties.
    Model worldModel = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
    getWWD().setModel(worldModel);

    // Deal with rendering exceptions - graphics not good enough
    getWWD()
        .addRenderingExceptionListener(
            new RenderingExceptionListener() {
              public void exceptionThrown(Throwable t) {
                if (t instanceof WWAbsentRequirementException) {
                  String message = "Computer does not meet minimum graphics requirements.\n";
                  message += "Please install up-to-date graphics driver and try again.\n";
                  message += "Reason: " + t.getMessage() + "\n";
                  message += "This program will end when you press OK.";

                  JOptionPane.showMessageDialog(
                      MapPanel.this, message, "Unable to Start Program", JOptionPane.ERROR_MESSAGE);
                  System.exit(-1);
                }
              }
            });

    //		getWWD().addRenderingListener(new RenderingListener() {
    //
    //			@Override
    //			public void stageChanged(RenderingEvent arg0) {
    //				System.out.println("MapPanel.renderingListener.stateChanged(): "+ arg0);
    //
    //			}
    //
    //		});

    // Setup a select listener for the worldmap click-and-go feature
    getWWD().addSelectListener(new ClickAndGoSelectListener(getWWD(), WorldMapLayer.class));

    // Put the world window in the center
    add(getWWD(), BorderLayout.CENTER);
  }
  /**
   * @param classNameKey the key identifying the component
   *
   * @return the new component
   *
   * @throws IllegalStateException    if no name could be found which corresponds to <code>classNameKey</code>
   * @throws IllegalArgumentException if <code>classNameKey<code> is null
   * @throws WWRuntimeException       if the component could not be created
   */
  public static Object createConfigurationComponent(String classNameKey)
      throws IllegalStateException, IllegalArgumentException {
    if (classNameKey == null || classNameKey.length() == 0) {
      Logging.logger().severe("nullValue.ClassNameKeyNullZero");
      throw new IllegalArgumentException(Logging.getMessage("nullValue.ClassNameKeyNullZero"));
    }

    String name = Configuration.getStringValue(classNameKey);
    if (name == null) {
      Logging.logger()
          .log(Level.SEVERE, "WorldWind.NoClassNameInConfigurationForKey", classNameKey);
      throw new WWRuntimeException(
          Logging.getMessage("WorldWind.NoClassNameInConfigurationForKey", classNameKey));
    }

    try {
      return WorldWind.createComponent(name.trim());
    } catch (Throwable e) {
      Logging.logger().log(Level.SEVERE, "WorldWind.UnableToCreateClassForConfigurationKey", name);
      throw new IllegalStateException(
          Logging.getMessage("WorldWind.UnableToCreateClassForConfigurationKey", name), e);
    }
  }
    public TestlocWorldFrame(String wdPath) {
      mapDirPath = wdPath;
      initFilelist();

      Model roundModel = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
      globeMapPanel = new MapPanel(roundModel, true);
      roundModel.getLayers().getLayerByName("Bing Imagery").setEnabled(true);
      LayerPanel layerPanel = new LayerPanel(globeMapPanel.wwd);
      globeDisplayAdapter =
          new DisplayAdapter(globeMapPanel.wwd, pointBuilderListener, MouseEvent.BUTTON1, 5, true);

      mapRenderLayer = new RenderableLayer();
      mapRenderLayer.setName("Surface Images");
      mapRenderLayer.setPickEnabled(false);
      LayerList layers = new LayerList();
      layers.add(mapRenderLayer);

      Model flatModel = new BasicModel(new EarthFlat(), layers);
      ((EarthFlat) flatModel.getGlobe()).setProjection(FlatGlobe.PROJECTION_LAT_LON);
      mapMapPanel = new MapPanel(flatModel, false);
      mapDisplayAdapter =
          new DisplayAdapter(
              mapMapPanel.wwd, markerBuilderListener, MouseEvent.BUTTON3, 50000, false);

      try {
        globeMapPanel.add(new GazetteerPanel(globeMapPanel.wwd, null), BorderLayout.NORTH);
      } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
        e.printStackTrace();
        System.exit(1);
      }

      mapSelectionList = new JList<String>(trackNames);
      mapSelectionList.addListSelectionListener(listListener);

      JLabel lblStepsize = new JLabel("Stepsize:");
      textFieldStepsize = new JTextField();
      textFieldStepsize.setText("5");

      btnStep = new JButton("Step");
      btnStep.addMouseListener(stepListener);

      btnStepToMarker = new JButton("to Marker");
      btnStepToMarker.addMouseListener(notImplementedListener);

      slider = new JSlider();
      slider.setValue(0);
      slider.addMouseListener(sliderListener);

      JLabel lblDstFallofExp = new JLabel("Distance Fallof:");
      textFieldDistFallofExp = new JTextField();
      textFieldDistFallofExp.setText(String.valueOf(7));

      JLabel lblTriDissimilarity = new JLabel("Tri Dissimilarity:");
      textFieldTriDissimilarity = new JTextField();
      textFieldTriDissimilarity.setText(String.valueOf(0.50));

      JLabel lblMinTriAngle = new JLabel("Min Tri Angle:");
      textFieldMinTriAngle = new JTextField();
      textFieldMinTriAngle.setText(String.valueOf(4.2));

      JLabel lblBadTriPen = new JLabel("Bad Tri Penalty:");
      textFieldBadTriPen = new JTextField();
      textFieldBadTriPen.setText(String.valueOf(0.10));

      btnDisplayTriangles = new JButton("Display Triangles");
      btnDisplayTriangles.addMouseListener(notImplementedListener);

      btnRefresh = new JButton("Refresh");
      btnRefresh.addMouseListener(parameterRefreshListener);

      tglbtnMouseMode = new JToggleButton("Mode: Move");
      tglbtnMouseMode.addMouseListener(toggleAction);

      JButton btnNewFile = new JButton("New File");
      btnNewFile.addMouseListener(newFileListener);

      JButton btnSaveFile = new JButton("Save File");
      btnSaveFile.addMouseListener(saveFileListener);

      // Window Builder generated stuff
      JPanel settings = new JPanel();
      GroupLayout groupLayout = new GroupLayout(settings);
      groupLayout.setHorizontalGroup(
          groupLayout
              .createParallelGroup(Alignment.TRAILING)
              .addGroup(
                  groupLayout
                      .createSequentialGroup()
                      .addContainerGap()
                      .addGroup(
                          groupLayout
                              .createParallelGroup(Alignment.LEADING)
                              .addGroup(
                                  groupLayout
                                      .createSequentialGroup()
                                      .addComponent(lblStepsize)
                                      .addGroup(
                                          groupLayout
                                              .createParallelGroup(Alignment.LEADING)
                                              .addGroup(
                                                  groupLayout
                                                      .createSequentialGroup()
                                                      .addGap(421)
                                                      .addComponent(lblTriDissimilarity)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(
                                                          textFieldTriDissimilarity,
                                                          GroupLayout.PREFERRED_SIZE,
                                                          36,
                                                          GroupLayout.PREFERRED_SIZE)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(lblBadTriPen)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(
                                                          textFieldBadTriPen,
                                                          GroupLayout.PREFERRED_SIZE,
                                                          34,
                                                          GroupLayout.PREFERRED_SIZE)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(btnRefresh)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(btnNewFile)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(btnSaveFile))
                                              .addGroup(
                                                  groupLayout
                                                      .createSequentialGroup()
                                                      .addGap(12)
                                                      .addComponent(
                                                          textFieldStepsize,
                                                          GroupLayout.PREFERRED_SIZE,
                                                          66,
                                                          GroupLayout.PREFERRED_SIZE)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(lblDstFallofExp)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(
                                                          textFieldDistFallofExp,
                                                          GroupLayout.PREFERRED_SIZE,
                                                          35,
                                                          GroupLayout.PREFERRED_SIZE)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(lblMinTriAngle)
                                                      .addPreferredGap(ComponentPlacement.RELATED)
                                                      .addComponent(
                                                          textFieldMinTriAngle,
                                                          GroupLayout.PREFERRED_SIZE,
                                                          42,
                                                          GroupLayout.PREFERRED_SIZE))))
                              .addGroup(
                                  groupLayout
                                      .createSequentialGroup()
                                      .addComponent(
                                          btnStep,
                                          GroupLayout.PREFERRED_SIZE,
                                          67,
                                          GroupLayout.PREFERRED_SIZE)
                                      .addPreferredGap(ComponentPlacement.RELATED)
                                      .addComponent(btnStepToMarker)
                                      .addPreferredGap(ComponentPlacement.RELATED)
                                      .addComponent(
                                          tglbtnMouseMode,
                                          GroupLayout.PREFERRED_SIZE,
                                          128,
                                          GroupLayout.PREFERRED_SIZE)
                                      .addPreferredGap(ComponentPlacement.RELATED)
                                      .addComponent(
                                          btnDisplayTriangles,
                                          GroupLayout.PREFERRED_SIZE,
                                          161,
                                          GroupLayout.PREFERRED_SIZE)
                                      .addPreferredGap(ComponentPlacement.RELATED)
                                      .addComponent(
                                          slider, GroupLayout.DEFAULT_SIZE, 1062, Short.MAX_VALUE)))
                      .addContainerGap()));
      groupLayout.setVerticalGroup(
          groupLayout
              .createParallelGroup(Alignment.LEADING)
              .addGroup(
                  groupLayout
                      .createSequentialGroup()
                      .addContainerGap()
                      .addGroup(
                          groupLayout
                              .createParallelGroup(Alignment.BASELINE)
                              .addComponent(lblStepsize)
                              .addComponent(
                                  textFieldStepsize,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE)
                              .addComponent(lblDstFallofExp)
                              .addComponent(
                                  textFieldDistFallofExp,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE)
                              .addComponent(lblMinTriAngle)
                              .addComponent(
                                  textFieldMinTriAngle,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE)
                              .addComponent(lblTriDissimilarity)
                              .addComponent(
                                  textFieldTriDissimilarity,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE)
                              .addComponent(lblBadTriPen)
                              .addComponent(
                                  textFieldBadTriPen,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE)
                              .addComponent(btnRefresh)
                              .addComponent(btnNewFile)
                              .addComponent(btnSaveFile))
                      .addPreferredGap(ComponentPlacement.RELATED)
                      .addGroup(
                          groupLayout
                              .createParallelGroup(Alignment.TRAILING)
                              .addGroup(
                                  groupLayout
                                      .createParallelGroup(Alignment.BASELINE)
                                      .addComponent(btnStep)
                                      .addComponent(btnStepToMarker)
                                      .addComponent(tglbtnMouseMode)
                                      .addComponent(btnDisplayTriangles))
                              .addComponent(
                                  slider,
                                  GroupLayout.PREFERRED_SIZE,
                                  GroupLayout.DEFAULT_SIZE,
                                  GroupLayout.PREFERRED_SIZE))));
      settings.setLayout(groupLayout);

      JSplitPane globeSplit = new JSplitPane();
      globeSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
      globeSplit.setLeftComponent(layerPanel);
      globeSplit.setRightComponent(globeMapPanel);
      globeSplit.setOneTouchExpandable(true);
      globeSplit.setContinuousLayout(true);
      globeSplit.setDividerLocation(0);

      JSplitPane imgSplit = new JSplitPane();
      imgSplit.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
      imgSplit.setLeftComponent(mapMapPanel);
      imgSplit.setRightComponent(mapSelectionList);
      imgSplit.setOneTouchExpandable(true);
      imgSplit.setContinuousLayout(true);

      JSplitPane layer2Split = new JSplitPane();
      layer2Split.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
      layer2Split.setLeftComponent(globeSplit);
      layer2Split.setRightComponent(imgSplit);
      layer2Split.setOneTouchExpandable(true);
      layer2Split.setContinuousLayout(true);
      layer2Split.setResizeWeight(0.5);

      this.add(settings, BorderLayout.NORTH);
      this.add(layer2Split, BorderLayout.CENTER);
      this.pack();

      this.setExtendedState(this.getExtendedState() | JFrame.MAXIMIZED_BOTH);

      enableSettingsMode(false);
    }
    protected void importImagery() {
      try {
        // Read the data and save it in a temp file.
        File sourceFile = ExampleUtil.saveResourceToTempFile(IMAGE_PATH, ".tif");

        // Create a raster reader to read this type of file. The reader is created from the
        // currently
        // configured factory. The factory class is specified in the Configuration, and a different
        // one can be
        // specified there.
        DataRasterReaderFactory readerFactory =
            (DataRasterReaderFactory)
                WorldWind.createConfigurationComponent(AVKey.DATA_RASTER_READER_FACTORY_CLASS_NAME);
        DataRasterReader reader = readerFactory.findReaderFor(sourceFile, null);

        // Before reading the raster, verify that the file contains imagery.
        AVList metadata = reader.readMetadata(sourceFile, null);
        if (metadata == null || !AVKey.IMAGE.equals(metadata.getStringValue(AVKey.PIXEL_FORMAT)))
          throw new Exception("Not an image file.");

        // Read the file into the raster. read() returns potentially several rasters if there are
        // multiple
        // files, but in this case there is only one so just use the first element of the returned
        // array.
        DataRaster[] rasters = reader.read(sourceFile, null);
        if (rasters == null || rasters.length == 0)
          throw new Exception("Can't read the image file.");

        DataRaster raster = rasters[0];

        // Determine the sector covered by the image. This information is in the GeoTIFF file or
        // auxiliary
        // files associated with the image file.
        final Sector sector = (Sector) raster.getValue(AVKey.SECTOR);
        if (sector == null) throw new Exception("No location specified with image.");

        // Request a sub-raster that contains the whole image. This step is necessary because only
        // sub-rasters
        // are reprojected (if necessary); primary rasters are not.
        int width = raster.getWidth();
        int height = raster.getHeight();

        // getSubRaster() returns a sub-raster of the size specified by width and height for the
        // area indicated
        // by a sector. The width, height and sector need not be the full width, height and sector
        // of the data,
        // but we use the full values of those here because we know the full size isn't huge. If it
        // were huge
        // it would be best to get only sub-regions as needed or install it as a tiled image layer
        // rather than
        // merely import it.
        DataRaster subRaster = raster.getSubRaster(width, height, sector, null);

        // Tne primary raster can be disposed now that we have a sub-raster. Disposal won't affect
        // the
        // sub-raster.
        raster.dispose();

        // Verify that the sub-raster can create a BufferedImage, then create one.
        if (!(subRaster instanceof BufferedImageRaster))
          throw new Exception("Cannot get BufferedImage.");
        BufferedImage image = ((BufferedImageRaster) subRaster).getBufferedImage();

        // The sub-raster can now be disposed. Disposal won't affect the BufferedImage.
        subRaster.dispose();

        // Create a SurfaceImage to display the image over the specified sector.
        final SurfaceImage si1 = new SurfaceImage(image, sector);

        // On the event-dispatch thread, add the imported data as an SurfaceImageLayer.
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                // Add the SurfaceImage to a layer.
                SurfaceImageLayer layer = new SurfaceImageLayer();
                layer.setName("Imported Surface Image");
                layer.setPickEnabled(false);
                layer.addRenderable(si1);

                // Add the layer to the model and update the application's layer panel.
                insertBeforeCompass(AppFrame.this.getWwd(), layer);
                AppFrame.this.getLayerPanel().update(AppFrame.this.getWwd());

                // Set the view to look at the imported image.
                ExampleUtil.goTo(getWwd(), sector);
              }
            });
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  public boolean initialize(MapSource mapSource) throws IOException, WMSServiceException {
    if (null == mapSource) {
      String msg = Logging.getMessage("nullValue.MapSourceIsNull");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    this.mapSource = mapSource;

    this.params = mapSource.getParameters();
    if (null == params) {
      String msg = Logging.getMessage("nullValue.AVListIsNull");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    if (!params.hasKey(AVKey.FILE_NAME)) {
      String msg = Logging.getMessage("nullValue.ParamsIsNull");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    this.sourceFile = new File(params.getStringValue(AVKey.FILE_NAME));
    if (!this.sourceFile.exists()) {
      String msg = Logging.getMessage("generic.FileNotFound", this.sourceFile.getAbsolutePath());
      Logging.logger().severe(msg);
      throw new FileNotFoundException(msg);
    }

    AVList fileParams = this.params.copy();

    try {
      this.readerFactory =
          (DataRasterReaderFactory)
              WorldWind.createConfigurationComponent(AVKey.DATA_RASTER_READER_FACTORY_CLASS_NAME);
    } catch (Exception e) {
      this.readerFactory = new BasicDataRasterReaderFactory();
    }
    DataRasterReader reader =
        this.readerFactory.findReaderFor(this.sourceFile, fileParams, readers);
    if (reader == null) {
      String msg = Logging.getMessage("nullValue.ReaderIsNull", this.sourceFile);
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    reader.readMetadata(this.sourceFile, fileParams);

    this.params.setValues(fileParams);

    if (!this.params.hasKey(AVKey.SECTOR)) {
      String msg = Logging.getMessage("nullValue.SectorIsNull");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }
    this.BBOX = (Sector) this.params.getValue(AVKey.SECTOR);

    if (0d == this.BBOX.getDeltaLatDegrees() || 0d == this.BBOX.getDeltaLonDegrees()) {
      String msg = Logging.getMessage("generic.SectorSizeInvalid");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    int height = 0;
    if (!this.params.hasKey(AVKey.HEIGHT)) {
      String msg = Logging.getMessage("generic.InvalidHeight", 0);
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    } else {
      Object o = this.params.getValue(AVKey.HEIGHT);
      double d = Double.parseDouble("" + o);
      height = (int) d;
    }

    if (!this.params.hasKey(AVKey.WIDTH)) {
      String msg = Logging.getMessage("generic.InvalidWidth", 0);
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    this.isElevation =
        (this.params.hasKey(AVKey.PIXEL_FORMAT)
            && AVKey.ELEVATION.equals(this.params.getValue(AVKey.PIXEL_FORMAT)));

    if (this.params.hasKey(AVKey.MISSING_DATA_SIGNAL)) {
      try {
        Object o = this.params.getValue(AVKey.MISSING_DATA_SIGNAL);
        double d = Double.parseDouble("" + o);
        this.nodataSignal = (short) d;
      } catch (Exception e) {
        this.nodataSignal = (this.isElevation) ? Short.MIN_VALUE : 0;
      }
    } else {
      this.nodataSignal = (this.isElevation) ? Short.MIN_VALUE : 0;
    }

    if (this.params.hasKey(AVKey.MISSING_DATA_REPLACEMENT)) {
      try {
        Object o = this.params.getValue(AVKey.MISSING_DATA_REPLACEMENT);
        double d = Double.parseDouble("" + o);
        this.nodataReplacement = (short) d;
      } catch (Exception e) {
        Logging.logger().finest(e.getMessage());
        this.nodataReplacement = (this.isElevation) ? Short.MIN_VALUE : 0;
      }
    } else {
      this.nodataReplacement = (this.isElevation) ? Short.MIN_VALUE : 0;
    }

    if (this.isElevation) {
      if (this.params.hasKey(AVKey.ELEVATION_UNIT)) {
        try {
          String unit = this.params.getStringValue(AVKey.ELEVATION_UNIT);
          this.convertFeetToMeters = "feet".equalsIgnoreCase(unit);
        } catch (Exception e) {
          Logging.logger().finest(e.getMessage());
        }
      }
    }

    // if PIXEL_HEIGHT is specified, we are not overriding it
    // because UTM images will have different pixel size
    if (!this.params.hasKey(AVKey.PIXEL_HEIGHT)) {
      this.pixelHeight = this.BBOX.getDeltaLatDegrees() / (double) height;
    } else {
      try {
        Object o = this.params.getValue(AVKey.PIXEL_HEIGHT);
        this.pixelHeight = Double.parseDouble("" + o);
      } catch (Exception e) {
        Logging.logger().finest(e.getMessage());
      }
    }

    this.rasters = reader.read(this.sourceFile, this.params);

    if (null == this.rasters || 0 == this.rasters.length) {
      String msg = Logging.getMessage("nullValue.RasterIsNull");
      Logging.logger().severe(msg);
      throw new WMSServiceException(msg);
    }

    return true;
  }
  @Override
  protected boolean loadTexture(TextureTile tile, URL textureURL) {
    if (!(tile instanceof MinMaxTextureTile)) {
      Logging.logger().severe("Tile is not instance of " + MinMaxTextureTile.class);
      getLevels().markResourceAbsent(tile);
      return false;
    }

    synchronized (fileLock) {
      InputStream is = null;
      try {
        is = textureURL.openStream();
        DataInputStream dis = new DataInputStream(is);

        int width = dis.readInt();
        int height = dis.readInt();
        int bands = dis.readInt();
        double minElevation = dis.readDouble();
        double maxElevation = dis.readDouble();
        byte[][] bytes = new byte[bands][];
        for (int i = 0; i < bands; i++) {
          bytes[i] = new byte[width * height];
          is.read(bytes[i]);
        }

        DataBuffer db = new DataBufferByte(bytes, width * height);
        SampleModel sm = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, bands);
        Raster raster = Raster.createRaster(sm, db, null);
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
        image.setData(raster);

        TextureData textureData = TextureIO.newTextureData(image, isUseMipMaps());
        if (textureData == null) {
          throw new Exception("Could not create texture data for " + textureURL);
        }

        ((MinMaxTextureTile) tile).setMinElevation(minElevation);
        ((MinMaxTextureTile) tile).setMaxElevation(maxElevation);
        tile.setTextureData(textureData);

        // if (tile.getLevelNumber() != 0 || !this.isRetainLevelZeroTiles())
        addTileToCache(tile);

        getLevels().unmarkResourceAbsent(tile);
        firePropertyChange(AVKey.LAYER, null, this);
        return true;
      } catch (Exception e) {
        // Assume that something's wrong with the file and delete it.
        gov.nasa.worldwind.WorldWind.getDataFileStore().removeFile(textureURL);
        getLevels().markResourceAbsent(tile);
        String message = Logging.getMessage("generic.DeletedCorruptDataFile", textureURL);
        Logging.logger().info(message + ":" + e.getLocalizedMessage());
        return false;
      } finally {
        if (is != null) {
          try {
            is.close();
          } catch (IOException e) {
          }
        }
      }
    }
  }
 /**
  * Reinitialize World Wind to its initial ready state. Shut down and restart all World Wind
  * services and clear all World Wind memory caches. Cache memory will be released at the next JVM
  * garbage collection.
  *
  * <p>Call this method to reduce World Wind's current resource usage to its initial, empty state.
  * This is typically required by applets when the user leaves the applet page.
  *
  * <p>The state of any open {@link WorldWindow} objects is indeterminate subsequent to invocation
  * of this method. The core WorldWindow objects attempt to shut themselves down cleanly during the
  * call, but their resulting window state is undefined.
  *
  * <p>World Wind can continue to be used after calling this method.
  */
 public static synchronized void shutDown() {
   instance.wwo.firePropertyChange(SHUTDOWN_EVENT, null, -1);
   instance.dispose();
   instance = new WorldWind();
 }
 protected boolean isTileInMemoryWithData() {
   Tile t =
       (Tile) WorldWind.getMemoryCache(Tile.class.getName()).getObject(this.getFileCachePath());
   return !(t == null || t.getDataChunk() == null);
 }
/**
 * Represents a texture derived from a lazily loaded image source such as an image file or a {@link
 * java.awt.image.BufferedImage}.
 *
 * <p>The interface contains a method, {@link #isTextureInitializationFailed()} to determine whether
 * the instance failed to convert an image source to a texture. If such a failure occurs, the method
 * returns true and no further attempts are made to create the texture.
 *
 * <p>This class performs lazy retrieval and loading of an image source, attempting to retrieve and
 * load the image source only when the {@link #bind(DrawContext)} or {@link
 * #applyInternalTransform(DrawContext)} methods are called. If the image source is a {@link
 * BufferedImage} the associated {@link Texture} object is created and available immediately when
 * <code>bind</code> or <code>applyInternalTransform</code> are called. If the image source is a
 * local file or remote stream (URL), retrieval and loading is performed on a separate thread from
 * the EDT.
 *
 * @author tag
 * @version $Id$
 */
public class LazilyLoadedTexture extends AVListImpl implements WWTexture {
  /** The original image source specified at construction. */
  protected Object imageSource;
  /** The mip-map flag specified at construction. */
  protected boolean useMipMaps;
  /** The current anisotropy flag. */
  protected boolean useAnisotropy = true;

  /** The texture width, if the width is known. Otherwise it's -1. */
  protected Integer width;
  /** The texture height, if the height is known. Otherwise it's -1. */
  protected Integer height;
  /** The texture's texture coordinates, as determined by JOGL when the texture is created. */
  protected TextureCoords texCoords;
  /**
   * The texture data created as the image source is read. It's removed - set to null - once the
   * textures is fully created. This intermediate texture data is necessary because the image source
   * is read in a non-EDT thread. This field is <code>volatile</code> in order to synchronize atomic
   * access among threads. This field is not used if the image source is <code>BufferedImage</code>.
   */
  protected volatile TextureData textureData; // if non-null, then must be converted to a Texture
  /** Indicates that texture initialization failed. This texture should not be used if true. */
  protected boolean textureInitializationFailed = false;
  /** Indicates whether the image read from the image source has mip-map data. */
  protected boolean hasMipmapData = false;
  /**
   * Identifies the {@link gov.nasa.worldwind.cache.FileStore} of the supporting file cache for this
   * model.
   */
  protected FileStore fileStore = WorldWind.getDataFileStore();
  /**
   * Provides a semaphore to synchronize access to the texture file if duplicate request tasks are
   * active.
   */
  protected final Object fileLock = new Object();

  /**
   * The object to notify when an image is eventually loaded in memory. The current layer at the
   * time the image source is requested is assigned to this field.
   */
  protected PropertyChangeListener listener;

  /**
   * Constructs a texture object for a specified image source. Requests that mip-maps be used.
   *
   * @param imageSource the source of the image, either a file path {@link String} or a {@link
   *     java.awt.image.BufferedImage}.
   * @throws IllegalArgumentException if the <code>imageSource</code> is null.
   */
  public LazilyLoadedTexture(Object imageSource) {
    this(imageSource, true);
  }

  /**
   * Constructs a texture object for a specified image source.
   *
   * @param imageSource the source of the image, either a file path {@link String} or a {@link
   *     java.awt.image.BufferedImage}.
   * @param useMipMaps Indicates whether to generate and use mip-maps for the image.
   * @throws IllegalArgumentException if the <code>imageSource</code> is null.
   */
  public LazilyLoadedTexture(Object imageSource, boolean useMipMaps) {
    initialize(imageSource, useMipMaps, null);
  }

  /**
   * Initializes this object's fields during construction.
   *
   * @param imageSource the image source.
   * @param useMipMaps the mip-map flag.
   * @param listener the change listener.
   * @throws IllegalArgumentException if the image source is null.
   */
  protected void initialize(
      Object imageSource, boolean useMipMaps, PropertyChangeListener listener) {
    if (imageSource == null) {
      String message = Logging.getMessage("nullValue.ImageSource");
      Logging.logger().severe(message);
      throw new IllegalArgumentException(message);
    }

    this.imageSource = imageSource;
    this.useMipMaps = useMipMaps;

    if (listener != null) this.addPropertyChangeListener(listener);
  }

  public Object getImageSource() {
    return this.imageSource;
  }

  /**
   * Indicates whether the image source is a <code>BufferedImage</code>
   *
   * @return true if the image source is a <code>BufferedImage</code>, otherwise false.
   */
  protected boolean isBufferedImageSource() {
    return this.getImageSource() instanceof BufferedImage;
  }

  /**
   * Indicates the texture's width, which is the same as the image source's width. The width is
   * known only after the image source has been retrieved from disk or network and read as <code>
   * TextureData</code>. It's value is -1 until then.
   *
   * @return the texture's width if the texture has been retrieved, otherwise -1.
   */
  public int getWidth() {
    return this.width != null ? this.width : -1;
  }

  /**
   * Indicates the texture's height, which is the same as the image source's height. The height is
   * known only after the image source has been retrieved from disk or network and read as <code>
   * TextureData</code>. It's value is -1 until then.
   *
   * @return the texture's height if the texture has been retrieved, otherwise -1.
   */
  public int getHeight() {
    return this.height != null ? this.height : -1;
  }

  /**
   * {@inheritDoc}
   *
   * <p>This method behaves identically to {@link #getWidth()}. The <code>DrawContext</code>
   * argument is not used.
   *
   * @param dc this parameter is not used by this class.
   * @return the texture's width if the texture has been retrieved, otherwise -1.
   */
  public int getWidth(DrawContext dc) {
    return this.getWidth();
  }

  /**
   * {@inheritDoc}
   *
   * <p>This method behaves identically to {@link #getHeight()}. The <code>DrawContext</code>
   * argument is not used.
   *
   * @param dc this parameter is not used by this class.
   * @return the texture's height if the texture has been retrieved, otherwise -1.
   */
  public int getHeight(DrawContext dc) {
    return this.getHeight();
  }

  /**
   * Indicates whether the texture should use mip-maps. If they are not available in the source
   * image they are created.
   *
   * @return true if mip-maps are used, false if not.
   */
  public boolean isUseMipMaps() {
    return this.useMipMaps;
  }

  public TextureCoords getTexCoords() {
    return this.texCoords;
  }

  public boolean isTextureCurrent(DrawContext dc) {
    return this.getTexture(dc) != null;
  }

  /**
   * Indicates whether texture anisotropy is applied to the texture when rendered.
   *
   * @return useAnisotropy true if anisotropy is to be applied, otherwise false.
   */
  public boolean isUseAnisotropy() {
    return this.useAnisotropy;
  }

  /**
   * Specifies whether texture anisotropy is applied to the texture when rendered.
   *
   * @param useAnisotropy true if anisotropy is to be applied, otherwise false.
   */
  public void setUseAnisotropy(boolean useAnisotropy) {
    this.useAnisotropy = useAnisotropy;
  }

  /**
   * Indicates whether an attempt was made to retrieve and read the texture but it failed. If this
   * flag is true, this texture should not be used.
   *
   * @return true if texture retrieval or creation failed, otherwise true, even if the image source
   *     has not yet been retrieved.
   */
  public boolean isTextureInitializationFailed() {
    return this.textureInitializationFailed;
  }

  /**
   * Returns the {@link Texture} associated with this instance.
   *
   * @param dc the current draw context.
   * @return this instance's texture, or null if the texture does not currently exist.
   */
  protected Texture getTexture(DrawContext dc) {
    if (this.getImageSource() == null) return null;

    Texture texture = dc.getTextureCache().getTexture(this.getImageSource());

    if (this.width == null && texture != null) {
      this.width = texture.getWidth();
      this.height = texture.getHeight();
      this.texCoords = texture.getImageTexCoords();
    }

    return texture;
  }

  /**
   * Returns this texture's texture data if it has been retrieved but a <code>Texture</code> has not
   * yet been created for it.
   *
   * <p>If this object's texture data field is non-null, a new texture is created from the texture
   * data when the tile is next bound or otherwise initialized. This object's texture data field is
   * then set to null.
   *
   * @return the texture data, which may be null indicating that the image source has not been read
   *     or that a texture has been created.
   */
  protected TextureData getTextureData() {
    return this.textureData;
  }

  /**
   * Specifies texture data for the tile. If texture data is non-null, a new texture is created from
   * the texture data when the tile is next bound.
   *
   * <p>When a texture is created from the texture data, the texture data field is set to null to
   * indicate that the data has been converted to a texture and its resources may be released.
   *
   * @param textureData the texture data, which may be null.
   */
  protected void setTextureData(TextureData textureData) {
    this.textureData = textureData;
    if (textureData != null && textureData.getMipmapData() != null) this.hasMipmapData = true;
  }

  /**
   * Binds this instance's {@link Texture} to the <code>GLContext</code> if the texture has been
   * created, otherwise initiates image source retrieval and texture creation in a separate thread.
   *
   * @param dc the current draw context.
   * @return true if the texture was bound, otherwise false.
   */
  public boolean bind(DrawContext dc) {
    if (this.isTextureInitializationFailed()) return false;

    if (dc == null) {
      String message = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    Texture texture = this.getTexture(dc);
    if (texture == null) texture = this.requestTexture(dc);

    if (texture != null) {
      texture.bind();
      return true;
    } else {
      return false;
    }
  }

  public void applyInternalTransform(DrawContext dc) {
    if (dc == null) {
      String message = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    Texture texture = this.getTexture(dc);
    if (texture == null) texture = this.requestTexture(dc);

    if (texture == null) return;

    if (texture.getMustFlipVertically()) {
      GL gl = GLContext.getCurrent().getGL();
      gl.glMatrixMode(GL.GL_TEXTURE);
      gl.glLoadIdentity();
      gl.glScaled(1, -1, 1);
      gl.glTranslated(0, -1, 0);
    }
  }

  /**
   * If this instance's image source is a <code>BufferedImage</code>, creates and returns the
   * texture, otherwise creates a task in a separate thread to retrieve it from its local or remote
   * location.
   *
   * @param dc the current draw context.
   * @return the new texture, or null if the texture is not yet available.
   */
  protected Texture requestTexture(DrawContext dc) {
    if (this.isBufferedImageSource()) return this.makeBufferedImageTexture(dc);

    if (this.getTextureData() != null && this.getTexture(dc) == null)
      return this.makeTextureFromTextureData(dc);

    if (WorldWind.getTaskService().isFull()) return null;

    Runnable task = this.createRequestTask();
    if (WorldWind.getTaskService().contains(task)) return null;

    // Use either the current layer or the layer list as the listener to notify when the request
    // completes. The
    // latter is used when the image source is requested during ordered rendering and the current
    // layer is null.
    this.listener = dc.getCurrentLayer() != null ? dc.getCurrentLayer() : dc.getLayers();

    WorldWind.getTaskService().addTask(task);

    return null;
  }

  /**
   * Returns an object that implements the Runnable interface, and who's <code>run</code> method
   * retrieves and loads this texture's image source.
   *
   * @return a new request task that retrieves and loads this texture's image source.
   */
  protected Runnable createRequestTask() {
    return new RequestTask(this);
  }

  /**
   * Creates this instance's {@link Texture} if the image source is a <code>BufferedImage<code>.
   *
   * @param dc the current draw context.
   *
   * @return the newly created texture, or null if the texture was not created.
   *
   * @throws IllegalStateException if the image source is null or not a <code>BufferedImage</code>.
   */
  protected Texture makeBufferedImageTexture(DrawContext dc) {
    if (this.getImageSource() == null || !(this.getImageSource() instanceof BufferedImage)) {
      String message = Logging.getMessage("generic.NotABufferedImage");
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    try {
      TextureData td =
          TextureIO.newTextureData((BufferedImage) this.getImageSource(), this.isUseMipMaps());
      if (td == null) return null;

      this.setTextureData(td);

      return this.makeTextureFromTextureData(dc);
    } catch (Exception e) {
      String msg = Logging.getMessage("generic.IOExceptionDuringTextureInitialization");
      Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
      this.textureInitializationFailed = true;
      return null;
    }
  }

  /**
   * Creates a {@link Texture} from this instance's {@link TextureData} if the <code>TextureData
   * </code> exists.
   *
   * @param dc the current draw context.
   * @return the newly created texture, or null if this instance has no current <code>TextureData
   *     </code> or if texture creation failed.
   */
  protected Texture makeTextureFromTextureData(DrawContext dc) {
    if (dc == null) {
      String message = Logging.getMessage("nullValue.DrawContextIsNull");
      Logging.logger().severe(message);
      throw new IllegalStateException(message);
    }

    if (this.getTextureData()
        == null) // texture not in cache yet texture data is null, can't initialize
    {
      String msg = Logging.getMessage("nullValue.TextureDataIsNull");
      Logging.logger().severe(msg);
      throw new IllegalStateException(msg);
    }

    try {
      Texture texture = TextureIO.newTexture(this.getTextureData());
      if (texture == null) {
        this.textureInitializationFailed = true;
        return null;
      }

      this.width = texture.getWidth();
      this.height = texture.getHeight();
      this.texCoords = texture.getImageTexCoords();

      this.setTextureParameters(dc, texture);

      // Cache the texture and release the texture data.
      dc.getTextureCache().put(this.getImageSource(), texture);
      this.setTextureData(null);

      return texture;
    } catch (Exception e) {
      String name =
          this.isBufferedImageSource() ? "BufferedImage" : this.getImageSource().toString();
      String msg = Logging.getMessage("generic.ExceptionAttemptingToCreateTexture", name);
      Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
      return null;
    }
  }

  /**
   * Sets a specified texture's OpenGL <code>Texture</code> parameters.
   *
   * @param dc the current draw context.
   * @param texture the texture whose parameters to set.
   */
  protected void setTextureParameters(DrawContext dc, Texture texture) {
    // Enable the appropriate mip-mapping texture filters if the caller has specified that
    // mip-mapping should be
    // enabled, and the texture itself supports mip-mapping.
    boolean useMipMapFilter =
        this.useMipMaps
            && (this.getTextureData().getMipmapData() != null
                || texture.isUsingAutoMipmapGeneration());

    GL gl = dc.getGL();
    gl.glTexParameteri(
        GL.GL_TEXTURE_2D,
        GL.GL_TEXTURE_MIN_FILTER,
        useMipMapFilter ? GL.GL_LINEAR_MIPMAP_LINEAR : GL.GL_LINEAR);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);

    if (this.isUseAnisotropy() && useMipMapFilter) {
      double maxAnisotropy = dc.getGLRuntimeCapabilities().getMaxTextureAnisotropy();
      if (dc.getGLRuntimeCapabilities().isUseAnisotropicTextureFilter() && maxAnisotropy >= 2.0) {
        gl.glTexParameterf(
            GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, (float) maxAnisotropy);
      }
    }
  }

  protected void notifyTextureLoaded() {
    if (this.listener != null) {
      this.listener.propertyChange(new PropertyChangeEvent(this, AVKey.TEXTURE, null, this));
      this.listener = null; // forget the listener to avoid dangling references
    }
  }

  /**
   * Attempts to find this texture's image file locally, and if that fails attempts to find it
   * remotely.
   */
  protected static class RequestTask implements Runnable {
    /** The BasicWWTexture associated with this request. */
    protected final LazilyLoadedTexture wwTexture;

    /**
     * Construct a request task for a specified BasicWWTexture.
     *
     * @param wwTexture the texture object for which to construct the request task.
     */
    protected RequestTask(LazilyLoadedTexture wwTexture) {
      if (wwTexture == null) {
        String message = Logging.getMessage("nullValue.TextureIsNull");
        Logging.logger().severe(message);
        throw new IllegalArgumentException(message);
      }

      this.wwTexture = wwTexture;
    }

    public void run() {
      if (Thread.currentThread().isInterrupted())
        return; // the task was cancelled because it's a duplicate or for some other reason

      URL fileUrl =
          this.wwTexture.fileStore.requestFile(this.wwTexture.getImageSource().toString());

      if (fileUrl != null) {
        if (this.wwTexture.loadTextureData(fileUrl)) {
          this.wwTexture.notifyTextureLoaded();
        }
      }
    }

    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      final RequestTask that = (RequestTask) o;

      return !(this.wwTexture != null
          ? !this.wwTexture.equals(that.wwTexture)
          : that.wwTexture != null);
    }

    public int hashCode() {
      return (this.wwTexture != null ? this.wwTexture.hashCode() : 0);
    }

    public String toString() {
      return this.wwTexture.getImageSource().toString();
    }
  }

  /**
   * Loads the image from disk into memory. If successful, texture data is created and available via
   * {@link #getTextureData()}.
   *
   * @param fileUrl the URL of the image file.
   * @return true if the image was successfully loaded, otherwise false.
   */
  protected boolean loadTextureData(URL fileUrl) {
    TextureData td;

    synchronized (this.fileLock) {
      td = readImage(fileUrl);

      if (td != null) this.setTextureData(td);
    }

    return this.getTextureData() != null;
  }

  /**
   * Reads and returns a {@link TextureData} for an image from a specified file URL.
   *
   * @param fileUrl the URL of the image file to read.
   * @return a <code>TextureData</code> instance for the image.
   */
  protected TextureData readImage(URL fileUrl) {
    try {
      return TextureIO.newTextureData(fileUrl, this.isUseMipMaps(), null);
    } catch (Exception e) {
      String msg =
          Logging.getMessage(
              "layers.TextureLayer.ExceptionAttemptingToReadTextureFile", this.getImageSource());
      Logging.logger().log(java.util.logging.Level.SEVERE, msg, e);
      this.textureInitializationFailed = true;
      return null;
    }
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    LazilyLoadedTexture that = (LazilyLoadedTexture) o;

    //noinspection RedundantIfStatement
    if (imageSource != null ? !imageSource.equals(that.imageSource) : that.imageSource != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    return imageSource != null ? imageSource.hashCode() : 0;
  }
}