Package de.fhpotsdam.unfolding.mapdisplay

Source Code of de.fhpotsdam.unfolding.mapdisplay.AbstractMapDisplay$QueueSorter

package de.fhpotsdam.unfolding.mapdisplay;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import org.apache.log4j.Logger;

import processing.core.PGraphics;
import processing.core.PVector;
import de.fhpotsdam.unfolding.UnfoldingMap;
import de.fhpotsdam.unfolding.core.Coordinate;
import de.fhpotsdam.unfolding.geo.Location;
import de.fhpotsdam.unfolding.marker.Marker;
import de.fhpotsdam.unfolding.marker.MarkerManager;
import de.fhpotsdam.unfolding.providers.AbstractMapProvider;
import de.fhpotsdam.unfolding.tiles.TileLoader;
import de.fhpotsdam.unfolding.tiles.TileLoaderListener;
import de.fhpotsdam.unfolding.utils.ScreenPosition;

/**
* Handles tiles management and display, and map location and screen position conversions.
*
* Use {@link UnfoldingMap} in your application. This is the internal class, and should be used only if you know what
* you are doing.
*
*/
public abstract class AbstractMapDisplay implements TileLoaderListener {

  public static final int TILE_WIDTH = 256;
  public static final int TILE_HEIGHT = 256;

  // If less than this memory threshold is reached, oldest tile images will be deleted.
  private static final long MEMORY_THRESHOLD_BYTES = 300000;
  // Number of tile images to delete.
  private static final int MEMORY_THRESHOLD_IMAGES = 25;

  public static Logger log = Logger.getLogger(AbstractMapDisplay.class);

  // Dimension of this map display
  protected float width;
  protected float height;

  // Map values: Inner position and scale (used for tiles)
  public double innerOffsetX = -TILE_WIDTH / 2; // half the world width, at zoom 0
  public double innerOffsetY = -TILE_HEIGHT / 2; // half the world height, at zoom 0
  public float innerScale = 1;
  public float innerAngle;

  // MapDisplay values: Outer position, rotation, and scale
  /** Vertical offset of this MapDisplay (in screen coordinates). */
  public float offsetX;
  /** Horizontal offset of this MapDisplay (in screen coordinates). */
  public float offsetY;
  /** Rotation of this MapDisplay. */
  public float angle;
  /** Scale of this MapDisplay. Not used yet! */
  public float scale = 1.0f;
  /** Center of outer transformations in screen pixel. */
  protected PVector transformationCenter;

  // MapDisplay values: Inner stuff
  protected PVector innerTransformationCenter;

  // List of MarkerManager with one default MarkerManager
  protected List<MarkerManager<Marker>> markerManagerList;

  // Tiles
  public int max_pending = 4;
  public int max_images_to_keep = 256;
  // public int max_images_to_keep = 1024;
  public int grid_padding = 1; // set to 0 for debugging purposes

  /** Check whether all currently visible tiles have been loaded. */
  protected boolean allTilesLoaded = false;

  protected AbstractMapProvider provider;
  protected Hashtable<Coordinate, Runnable> pending = new Hashtable<Coordinate, Runnable>();
  protected Hashtable<Coordinate, Object> images = new Hashtable<Coordinate, Object>();
  protected Vector<Coordinate> queue = new Vector<Coordinate>();
  protected Vector<Object> recent_images = new Vector<Object>();

  protected ZoomComparator zoomComparator = new ZoomComparator();
  protected QueueSorter queueSorter = new QueueSorter();

  private MarkerManager<Marker> defaultMarkerManager = null;

  protected AbstractMapDisplay(AbstractMapProvider provider, float width, float height) {
    this.provider = provider;

    this.width = width;
    this.height = height;

    transformationCenter = new PVector(width / 2, height / 2);
    innerTransformationCenter = new PVector(width / 2, height / 2);

    innerScale = (float) Math.ceil(Math.min(height / (float) TILE_WIDTH, width / (float) TILE_HEIGHT));

    markerManagerList = new ArrayList<MarkerManager<Marker>>();
  }

  public void resize(float width, float height) {
    this.width = width;
    this.height = height;
  }

  public AbstractMapProvider getMapProvider() {
    return this.provider;
  }

  public void setMapProvider(AbstractMapProvider provider) {
    if (this.provider.getClass() != provider.getClass()) {
      this.provider = provider;
      images.clear();
      queue.clear();
      pending.clear();
    }
  }

  public abstract PGraphics getInnerPG();

  public abstract PGraphics getOuterPG();

  public PGraphics getMask() {
    return null;
  }

  public abstract void draw();

  /**
   * Sets background color of map.
   *
   * @param color
   *            Color for the background canvas. Can be semi-transparent. If null it is not used.
   */
  public abstract void setBackgroundColor(Integer color);

  // MarkerManagement -----------------------------------------------

  /**
   * You need to set the map of the given MarkerManager before using.
   */
  public void addMarkerManager(MarkerManager<Marker> markerManager) {
        // Replace default MarkerManager, if only default exists and has no entries
        if (markerManagerList.size() == 1) {
            MarkerManager<?> mm = markerManagerList.get(0);
            if (mm.getMarkers().size() == 0 && mm.equals(this.defaultMarkerManager)) {
                markerManagerList.remove(0);
                this.defaultMarkerManager = null;
            }
        }

        markerManagerList.add(markerManager);
    }

  public MarkerManager<Marker> getLastMarkerManager() {
    return markerManagerList.get(markerManagerList.size() - 1);
  }

  public MarkerManager<Marker> getDefaultMarkerManager() {
    return getMarkerManager(0);
  }

  @Deprecated
  public MarkerManager<Marker> getMarkerManager() {
    return getDefaultMarkerManager();
  }

  public MarkerManager<Marker> getMarkerManager(int index) {
    if (markerManagerList.size() > index) {
      return markerManagerList.get(index);
    } else {
      return null;
    }
  }

  /**
   * Adds a marker to the default marker manager.
   *
   * If you have more than one marker manager, use {@link MarkerManager#addMarker(Marker)} instead.
   *
   * @param marker
   *            The marker to add.
   */
  public void addMarker(Marker marker) {
    getDefaultMarkerManager().addMarker(marker);
  }

  /**
   * Adds multiple markers to the default marker manager.
   *
   * If you have more than one marker manager, use {@link MarkerManager#addMarkers(List)} instead.
   *
   * @param markers
   *            The markers to add.
   */
  public void addMarkers(List<Marker> markers) {
    getDefaultMarkerManager().addMarkers(markers);
  }

  // TRANSFORMATION --------------------------------------------------

  public float getWidth() {
    return width;
  }

  public float getHeight() {
    return height;
  }

  /**
   * Updates the matrix to transform the map with with the current transformation center.
   */
  public abstract void calculateMatrix();

  public abstract void calculateInnerMatrix();

  /**
   * Calculates offset and rotation for screen canvas position, to be used with the internal transformation matrix.
   *
   * @param x
   *            Cartesian x coordinate.
   * @param y
   *            Cartesian y coordinate.
   * @param inverse
   *            Indicates back and forward matrix calculation. Inverse is used for point2location, non-inverse for
   *            location2point.
   * @return An 1d-2elements-array with x and y.
   */
  protected abstract float[] getTransformedPosition(float x, float y, boolean inverse);

  public abstract float[] getObjectFromInnerObjectPosition(float x, float y);

  public abstract float[] getInnerObjectFromObjectPosition(float x, float y);

  public abstract float[] getScreenFromInnerObjectPosition(float x, float y);

  @Deprecated
  public abstract float[] getInnerObjectFromScreenPosition(float x, float y);

  public abstract float[] getInnerObject(ScreenPosition screenPosition);

  public abstract float[] getScreenFromObjectPosition(float x, float y);

  public abstract float[] getObjectFromScreenPosition(float x, float y);

  public abstract Location getLocationFromInnerObjectPosition(float x, float y);

  @Deprecated
  public abstract Location getLocationFromScreenPosition(float x, float y);

  public abstract Location getLocation(float x, float y);

  public abstract Location getLocation(ScreenPosition screenPosition);

  public abstract Location getLocationFromObjectPosition(float x, float y);

  public abstract float[] getInnerObjectFromLocation(Location location);

  @Deprecated
  public abstract float[] getScreenPositionFromLocation(Location location);

  public abstract ScreenPosition getScreenPosition(Location location);

  public abstract float[] getObjectFromLocation(Location location);

  public PVector getTransformationCenter() {
    return transformationCenter;
  }

  public PVector getInnerTransformationCenter() {
    return innerTransformationCenter;
  }

  /**
   * Set outer transformation center.
   *
   * @param transformationCenter
   *            Point in screen coordinates.
   */
  public void setTransformationCenter(PVector transformationCenter) {
    // NB Offset subtraction due to special handling (i.e. not included in matrix)
    this.transformationCenter.x = transformationCenter.x - offsetX;
    this.transformationCenter.y = transformationCenter.y - offsetY;
  }

  /**
   * Set inner transformation center.
   *
   * @param innerTransformationCenter
   *            Point in screen coordinates.
   */
  public void setInnerTransformationCenter(PVector innerTransformationCenter) {
    float[] xy = getObjectFromScreenPosition(innerTransformationCenter.x, innerTransformationCenter.y);

    this.innerTransformationCenter.x = xy[0] - (float) innerOffsetX;
    this.innerTransformationCenter.y = xy[1] - (float) innerOffsetY;
  }

  // TILES --------------------------------------------------------

  public void processQueue() {
    while (pending.size() < max_pending && queue.size() > 0) {
      Coordinate coord = (Coordinate) queue.remove(0);
      TileLoader tileLoader = createTileLoader(coord);
      pending.put(coord, tileLoader);
      new Thread(tileLoader).start();
    }
  }

  protected abstract TileLoader createTileLoader(Coordinate coord);

  public void grabTile(Coordinate coord) {
    if (!pending.containsKey(coord) && !queue.contains(coord) && !images.containsKey(coord))
      queue.add(coord);
  }

  // TODO images & pending thread safe?
  // TODO Handle null images (if TileLoader/MapProvider returns null tile)
  public void tileLoaded(Coordinate coord, Object image) {
    if (pending.containsKey(coord) && coord != null) {
      images.put(coord, image);
      pending.remove(coord);
    } else {
      queue.add(coord);
      pending.remove(coord);
    }

    if (pending.size() == 0 && queue.size() == 0) {
      allTilesLoaded = true;
      tilesLoaded();
    } else {
      allTilesLoaded = false;
    }
  }

  /**
   * Check whether all currently visible tiles have been loaded.
   *
   * @return True if all tiles have been loaded, false otherwise.
   */
  public boolean allTilesLoaded() {
    return allTilesLoaded;
  }

  /**
   * Will be called if all tiles have been loaded. Subclasses can implement this method to notify a method in the
   * client app.
   */
  public abstract void tilesLoaded();

  // LOAD SORTING
  public class QueueSorter implements Comparator<Coordinate> {
    Coordinate center;

    public void setCenter(Coordinate center) {
      this.center = center;
    }

    public int compare(Coordinate c1, Coordinate c2) {
      if (c1.zoom == center.zoom) {
        if (c2.zoom == center.zoom) {
          // only compare squared distances, saves cpu
          float d1 = (float) Math.pow(c1.column - center.column, 2)
              + (float) Math.pow(c1.row - center.row, 2);
          float d2 = (float) Math.pow(c2.column - center.column, 2)
              + (float) Math.pow(c2.row - center.row, 2);
          return d1 < d2 ? -1 : d1 > d2 ? 1 : 0;
        } else {
          return -1;
        }
      } else if (c2.zoom == center.zoom) {
        return 1;
      } else {
        float d1 = Math.abs(c1.zoom - center.zoom);
        float d2 = Math.abs(c2.zoom - center.zoom);
        return d1 < d2 ? -1 : d1 > d2 ? 1 : 0;
      }
    }
  }

  public class ZoomComparator implements Comparator<Coordinate> {
    public int compare(Coordinate c1, Coordinate c2) {
      return c1.zoom < c2.zoom ? -1 : c1.zoom > c2.zoom ? 1 : 0;
    }
  }

  /**
   * Cleans oldest images if too many images exist, or if memory is too full.
   *
   * Tiles are added to the recency-based list to allow removing oldest ones from images-array.
   *
   * REVISIT Check java.lang.ref.SoftReference for better solution.
   */
  protected void cleanupImageBuffer() {
    if (recent_images.size() > max_images_to_keep) {
      log.info("Cleaning image buffer due to MAX_IMAGE reached.");
      recent_images.subList(0, recent_images.size() - max_images_to_keep).clear();
      images.values().retainAll(recent_images);
    } else if (Runtime.getRuntime().freeMemory() < MEMORY_THRESHOLD_BYTES) {
      log.info("Cleaning image buffer due to MEMORY_THRESHOLD reached.");
      int imagesToDelete = recent_images.size() > MEMORY_THRESHOLD_IMAGES ? MEMORY_THRESHOLD_IMAGES
          : recent_images.size();
      recent_images.subList(0, imagesToDelete).clear();
      images.values().retainAll(recent_images);
    }
  }

  protected void cleanupImageBuffer(boolean force) {
    if (force) {
      images.clear();
    } else {
      cleanupImageBuffer();
    }
  }

  /**
   * Set the map provider, dynamically. The currently selected area, as well as all events etc will stay.
   *
   * Note that the image buffer will be cleaned, i.e. all tiles need to be loaded anew.
   *
   * @param provider
   *            The provider to use.
   */
  public void setProvider(AbstractMapProvider provider) {
    this.provider = provider;
    cleanupImageBuffer(true);
  }

  protected void createDefaultMarkerManager(UnfoldingMap map) {
    if (this.defaultMarkerManager == null) {
      this.defaultMarkerManager = new MarkerManager<Marker>();
      this.defaultMarkerManager.setMap(map);
      markerManagerList.add(defaultMarkerManager);
    }
  }

  public List<MarkerManager<Marker>> getMarkerManagerList() {
    return markerManagerList;
  }

  public void removeMarkerManager(MarkerManager<Marker> markerManager) {
    markerManagerList.remove(markerManager);
  }

  public void removeMarkerManager(int i) {
    markerManagerList.remove(i);
  }

}
TOP

Related Classes of de.fhpotsdam.unfolding.mapdisplay.AbstractMapDisplay$QueueSorter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.