Package de.fhpotsdam.unfolding.mapdisplay

Source Code of de.fhpotsdam.unfolding.mapdisplay.Java2DMapDisplay

package de.fhpotsdam.unfolding.mapdisplay;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Vector;

import org.apache.log4j.Logger;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.core.PMatrix3D;
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.utils.ScreenPosition;

/**
* The internal MapDisplay to handle the map display in Processing 2D (and used as super class for 3D)
*/
@SuppressWarnings("unchecked")
public class Java2DMapDisplay extends AbstractMapDisplay implements PConstants {

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

  // Used for loadImage and float maths
  public PApplet papplet;

  /** The inner transformation matrix. Scales and rotates the map. */
  protected PMatrix3D innerMatrix = new PMatrix3D();

  /** The outer transformation matrix. Rotates the map pane. */
  protected PMatrix3D matrix = new PMatrix3D();

  // Background color
  protected Integer bgColor = null;

  // To notify client app when all tiles have been loaded
  private static final String TILESLOADED_METHOD_NAME = "tilesLoaded";
  private Method tilesLoadedMethod = null;

  /**
   * Creates a new MapDisplay with full canvas size, and given provider
   */
  public Java2DMapDisplay(PApplet papplet, AbstractMapProvider provider) {
    this(papplet, provider, 0, 0, papplet.width, papplet.height);
  }

  /**
   * Creates a new MapDisplay.
   */
  public Java2DMapDisplay(PApplet papplet, AbstractMapProvider provider, float offsetX, float offsetY, float width,
      float height) {
    super(provider, width, height);
    this.papplet = papplet;

    this.offsetX = offsetX;
    this.offsetY = offsetY;

    this.innerOffsetX = width / 2 - TILE_WIDTH / 2;
    this.innerOffsetY = height / 2 - TILE_HEIGHT / 2;

    registerTilesLoadedMethod();

    setInnerTransformationCenter(new PVector(width / 2 + offsetX, height / 2 + offsetY));

    calculateMatrix();

    calculateInnerMatrix();
  }

  private void registerTilesLoadedMethod() {
    try {
      Class<? extends PApplet> appletClass = papplet.getClass();
      tilesLoadedMethod = appletClass.getMethod(TILESLOADED_METHOD_NAME);
    } catch (SecurityException e) {
    } catch (NoSuchMethodException e) {
    }
  }

  /**
   * Gets called if all tiles have been loaded. Invokes the tilesLoaded method in the client application if existing.
   *
   * TODO Pass the ID of this map to the method to enable handling multiple maps.
   */
  @Override
  public void tilesLoaded() {
    if (tilesLoadedMethod != null) {
      try {
        tilesLoadedMethod.invoke(papplet);
      } catch (IllegalArgumentException e) {
      } catch (IllegalAccessException e) {
      } catch (InvocationTargetException e) {
      }
    }
  }

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

  /**
   * Updates the matrix to transform the map with.
   *
   * For the rotation the matrix has to be temporarily translated to the transformation center. Thus, it has to be
   * reset with the original position, afterwards. Original position is calculated by inverting the current matrix.
   * (As the matrix incorporates that position, it stores every transformation, even though the matrix is created
   * anew.)
   */
  public void calculateMatrix() {
    synchronized (this) {
      PMatrix3D invMatrix = new PMatrix3D();
      invMatrix.apply(matrix);
      invMatrix.invert();

      float originalCenterX = invMatrix.multX(transformationCenter.x, transformationCenter.y);
      float originalCenterY = invMatrix.multY(transformationCenter.x, transformationCenter.y);

      matrix = new PMatrix3D();
      matrix.translate(transformationCenter.x, transformationCenter.y);
      matrix.scale(scale);
      matrix.rotateZ(angle);
      matrix.translate(-originalCenterX, -originalCenterY);
    }
  }

  public void calculateInnerMatrix() {
    // Synchronize on this to not interfere with tile loading (see getVisibleKeys)
    synchronized (this) {
      PMatrix3D invMatrix = new PMatrix3D();
      invMatrix.apply(innerMatrix);
      invMatrix.invert();

      float originalCenterX = invMatrix.multX(innerTransformationCenter.x, innerTransformationCenter.y);
      float originalCenterY = invMatrix.multY(innerTransformationCenter.x, innerTransformationCenter.y);

      innerMatrix = new PMatrix3D();
      innerMatrix.translate(innerTransformationCenter.x, innerTransformationCenter.y);
      innerMatrix.scale(innerScale);
      innerMatrix.rotateZ(innerAngle);
      innerMatrix.translate(-originalCenterX, -originalCenterY);
    }
  }

  @Override
  public float[] getObjectFromScreenPosition(float x, float y) {
    return getTransformedPosition(x, y, true);
  }

  @Override
  public float[] getScreenFromObjectPosition(float x, float y) {
    return getTransformedPosition(x, y, false);
  }

  @Override
  protected float[] getTransformedPosition(float x, float y, boolean inverse) {
    if (inverse) {
      x -= offsetX;
      y -= offsetY;
    }

    float[] xyz = new float[3];
    PMatrix3D m = new PMatrix3D();
    m.apply(matrix);
    if (inverse) {
      m.invert();
    }
    m.mult(new float[] { x, y, 0 }, xyz);

    if (!inverse) {
      xyz[0] += offsetX;
      xyz[1] += offsetY;
    }

    return xyz;
  }

  public float[] getObjectFromInnerObjectPosition(float x, float y) {
    return getInnerTransformedPosition(x, y, false);
  }

  public float[] getInnerObjectFromObjectPosition(float x, float y) {
    return getInnerTransformedPosition(x, y, true);
  }

  public float[] getScreenFromInnerObjectPosition(float x, float y) {
    float objectXY[] = getObjectFromInnerObjectPosition(x, y);
    float screenXY[] = getScreenFromObjectPosition(objectXY[0], objectXY[1]);
    return screenXY;
  }

  @Deprecated
  public float[] getInnerObjectFromScreenPosition(float x, float y) {
    float objectXY[] = getObjectFromScreenPosition(x, y);
    float innerObjectXY[] = getInnerObjectFromObjectPosition(objectXY[0], objectXY[1]);
    return innerObjectXY;
  }

  public float[] getInnerObject(ScreenPosition screenPosition) {
    float objectXY[] = getObjectFromScreenPosition(screenPosition.x, screenPosition.y);
    float innerObjectXY[] = getInnerObjectFromObjectPosition(objectXY[0], objectXY[1]);
    return innerObjectXY;
  }

  public float[] getObjectFromLocation(Location location) {
    float[] ixy = getInnerObjectFromLocation(location);
    float[] xy = getObjectFromInnerObjectPosition(ixy[0], ixy[1]);
    return xy;
  }

  private float[] getInnerTransformedPosition(float x, float y, boolean inverse) {
    if (inverse) {
      x -= innerOffsetX;
      y -= innerOffsetY;
    }

    float[] xyz = new float[3];
    PMatrix3D m = new PMatrix3D();
    m.apply(innerMatrix);
    if (inverse) {
      m.invert();
    }
    m.mult(new float[] { x, y, 0 }, xyz);

    if (!inverse) {
      xyz[0] += innerOffsetX;
      xyz[1] += innerOffsetY;
    }

    return xyz;
  }

  // Location (instead of innerObj) methods ---------------
  public Location getLocationFromInnerObjectPosition(float x, float y) {
    Coordinate coord = getCoordinateFromInnerPosition(x, y);
    return provider.coordinateLocation(coord);
  }

  private Coordinate getCoordinateFromInnerPosition(float x, float y) {
    PMatrix3D m = new PMatrix3D();
    float tl[] = new float[3];
    m.mult(new float[] { 0, 0, 0 }, tl);
    float br[] = new float[3];
    m.mult(new float[] { TILE_WIDTH, TILE_HEIGHT, 0 }, br);

    float col = (x - tl[0]) / (br[0] - tl[0]);
    float row = (y - tl[1]) / (br[1] - tl[1]);
    Coordinate coord = new Coordinate(row, col, 0);
    return coord;
  }

  @Deprecated
  @Override
  public Location getLocationFromScreenPosition(float x, float y) {
    return getLocation(x, y);
  }

  @Override
  public Location getLocation(float x, float y) {
    synchronized (this) {
      float innerObjectXY[] = getInnerObjectFromScreenPosition(x, y);
      return getLocationFromInnerObjectPosition(innerObjectXY[0], innerObjectXY[1]);
    }
  }

  @Override
  public Location getLocation(ScreenPosition screenPosition) {
    synchronized (this) {
      float innerObjectXY[] = getInnerObjectFromScreenPosition(screenPosition.x, screenPosition.y);
      return getLocationFromInnerObjectPosition(innerObjectXY[0], innerObjectXY[1]);
    }
  }

  @Override
  public Location getLocationFromObjectPosition(float x, float y) {
    synchronized (this) {
      float innerObjectXY[] = getInnerObjectFromObjectPosition(x, y);
      return getLocationFromInnerObjectPosition(innerObjectXY[0], innerObjectXY[1]);
    }
  }

  public float[] getInnerObjectFromLocation(Location location) {
    Coordinate coord = provider.locationCoordinate(location).zoomTo(0);
    return new float[] { coord.column * TILE_WIDTH, coord.row * TILE_HEIGHT, 0 };

    // PMatrix3D m = new PMatrix3D();
    // Coordinate coord = provider.locationCoordinate(location).zoomTo(0);
    // float[] out = new float[3];
    // m.mult(new float[] { coord.column * TILE_WIDTH, coord.row * TILE_HEIGHT, 0 }, out);
    // log.debug("getInnerObjectFromLocation. " + (coord.column * TILE_WIDTH) + ", " + out[0]);
    // return out;
  }

  @Override
  public ScreenPosition getScreenPosition(Location location) {
    synchronized (this) {
      float innerObjectXY[] = getInnerObjectFromLocation(location);
      return new ScreenPosition(getScreenFromInnerObjectPosition(innerObjectXY[0], innerObjectXY[1]));
    }
  }

  @Deprecated
  public float[] getScreenPositionFromLocation(Location location) {
    synchronized (this) {
      float innerObjectXY[] = getInnerObjectFromLocation(location);
      return getScreenFromInnerObjectPosition(innerObjectXY[0], innerObjectXY[1]);
    }
  }

  // DRAWING --------------------------------------------------

  public PGraphics getInnerPG() {
    // NB Always inner graphics, this one not used. Implemented in sub classes.
    return papplet.g;
  }

  public PGraphics getOuterPG() {
    return papplet.g;
  }

  /**
   * Is called last in {@link #draw()}. May be implemented in sub-classes to handle drawing on outerPG.
   */
  protected void postDraw() {

    // Draws all markers
    for (MarkerManager<Marker> mm : markerManagerList) {
      mm.draw();
    }

  }

  /**
   * Draws the on the PGraphics canvas.
   */
  public void draw() {
    PGraphics pg = getInnerPG();
    pg.beginDraw();

    // Clears canvas (set to transparency, to not smudge previous map on panning)
    pg.clear();
    if (bgColor != null) {
      // Set background color if given (can also be transparent)
      pg.background(bgColor);
    }

    // translate and scale, from the middle
    pg.pushMatrix();
    pg.translate((float) innerOffsetX, (float) innerOffsetY);
    if (pg.is3D()) {
      pg.applyMatrix(innerMatrix);
    } else {
      pg.applyMatrix(innerMatrix.m00, innerMatrix.m01, innerMatrix.m03, innerMatrix.m10, innerMatrix.m11,
          innerMatrix.m13);
    }

    Vector visibleKeys = getVisibleKeys(pg);

    if (visibleKeys.size() > 0) {
      Coordinate previous = (Coordinate) visibleKeys.get(0);
      pg.pushMatrix();
      pg.scale(1.0f / PApplet.pow(2, previous.zoom));

      for (int i = 0; i < visibleKeys.size(); i++) {
        Coordinate coord = (Coordinate) visibleKeys.get(i);
        if (coord.zoom != previous.zoom) {
          pg.popMatrix();
          pg.pushMatrix();
          pg.scale(1.0f / PApplet.pow(2, coord.zoom));
        }

        if (images.containsKey(coord)) {
          PImage tile = (PImage) images.get(coord);

          float x = coord.column * TILE_WIDTH;
          float y = coord.row * TILE_HEIGHT;

          // REVISIT For transparency, do not paint image (why no transparent imgs?)
          pg.image(tile, x, y, TILE_WIDTH, TILE_HEIGHT);

          if (recent_images.contains(tile)) {
            recent_images.remove(tile);
          }
          recent_images.add(tile);
        }
      }
      pg.popMatrix();
    }

    pg.popMatrix();
    pg.endDraw();

    postDraw();

    cleanupImageBuffer();
  }

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

  // Based on code by ModestMaps, Tom Carden
  protected Vector getVisibleKeys(PGraphics pg) {

    // Stores IDs of tiles already displayed
    Vector visibleKeys = new Vector();

    // Grid to load tiles for.
    int minCol, maxCol, minRow, maxRow;

    int zoomLevel = UnfoldingMap.getZoomLevelFromScale(innerScale);

    // Synchronize on this to not interfere with tile loading (see getVisibleKeys)
    // NB External threads can change innerMatrix, innerScale, and other properties while this method is
    // running, which resulted in incorrect values, and in incorrect tile loading.
    synchronized (this) {

      // Gets outer object corner positions in inner object coordinate system
      // to check which tiles to load: Always uses bounding box.
      float[] innerTL = getInnerObjectFromObjectPosition(0, 0);
      float[] innerTR = getInnerObjectFromObjectPosition(getWidth(), 0);
      float[] innerBR = getInnerObjectFromObjectPosition(getWidth(), getHeight());
      float[] innerBL = getInnerObjectFromObjectPosition(0, getHeight());

      Coordinate coordTL = getCoordinateFromInnerPosition(innerTL[0], innerTL[1]).zoomTo(zoomLevel);
      Coordinate coordTR = getCoordinateFromInnerPosition(innerTR[0], innerTR[1]).zoomTo(zoomLevel);
      Coordinate coordBR = getCoordinateFromInnerPosition(innerBR[0], innerBR[1]).zoomTo(zoomLevel);
      Coordinate coordBL = getCoordinateFromInnerPosition(innerBL[0], innerBL[1]).zoomTo(zoomLevel);

      minCol = (int) PApplet.min(new float[] { coordTL.column, coordTR.column, coordBR.column, coordBL.column });
      maxCol = (int) PApplet.max(new float[] { coordTL.column, coordTR.column, coordBR.column, coordBL.column });
      minRow = (int) PApplet.min(new float[] { coordTL.row, coordTR.row, coordBR.row, coordBL.row });
      maxRow = (int) PApplet.max(new float[] { coordTL.row, coordTR.row, coordBR.row, coordBL.row });
    }

    // Add tile padding (to pre-load, and because we might be zooming out between zoom levels)
    minCol -= grid_padding;
    minRow -= grid_padding;
    maxCol += grid_padding;
    maxRow += grid_padding;

    // log.debug("getVisibleKeys: " + minCol + "," + maxCol + "; " + minRow + "," + maxRow);
    // PApplet.println("getVisibleKeys: " + minCol + "," + maxCol + "; " + minRow + "," + maxRow);

    // we don't wrap around the world yet, so:
    int numberTiles = (int) UnfoldingMap.getScaleFromZoom(zoomLevel);
    minCol = PApplet.constrain(minCol, 0, numberTiles);
    maxCol = PApplet.constrain(maxCol, 0, numberTiles);
    minRow = PApplet.constrain(minRow, 0, numberTiles);
    maxRow = PApplet.constrain(maxRow, 0, numberTiles);

    // grab coords for visible tiles
    for (int col = minCol; col <= maxCol; col++) {
      for (int row = minRow; row <= maxRow; row++) {

        // source coordinate wraps around the world:
        Coordinate coord = provider.sourceCoordinate(new Coordinate(row, col, zoomLevel));
        // make sure we still have ints:
        coord.roundValues();

        // keep this for later:
        visibleKeys.add(coord);

        if (!images.containsKey(coord)) {
          // fetch it if we don't have it
          grabTile(coord);

          // see if we have a parent coord for this tile?
          boolean gotParent = false;
          for (int i = (int) coord.zoom; i > 0; i--) {
            Coordinate zoomed = coord.zoomTo(i).container();
            zoomed.roundValues();
            if (images.containsKey(zoomed)) {
              visibleKeys.add(zoomed);
              gotParent = true;
              break;
            }
          }

          // or if we have any of the children
          if (!gotParent) {
            Coordinate zoomed = coord.zoomBy(1).container();
            Coordinate[] kids = { zoomed, zoomed.right(), zoomed.down(), zoomed.right().down() };
            for (int i = 0; i < kids.length; i++) {
              zoomed = kids[i];
              // make sure we still have ints:
              zoomed.row = PApplet.round(zoomed.row);
              zoomed.column = PApplet.round(zoomed.column);
              zoomed.zoom = PApplet.round(zoomed.zoom);
              if (images.containsKey(zoomed)) {
                visibleKeys.add(zoomed);
              }
            }
          }

        }

      } // rows
    } // columns

    // sort by zoom so we draw small zoom levels (big tiles) first:
    Collections.sort(visibleKeys, zoomComparator);

    // stop fetching things we can't see:
    // (visibleKeys also has the parents and children, if needed, but that
    // shouldn't matter)
    queue.retainAll(visibleKeys);

    // sort what's left by distance from center:
    queueSorter.setCenter(new Coordinate((minRow + maxRow) / 2.0f, (minCol + maxCol) / 2.0f, zoomLevel));
    Collections.sort(queue, queueSorter);

    // load up to 4 more things:
    processQueue();

    return visibleKeys;
  }

  // TILE LOADING ---------------------------------------

  protected TileLoader createTileLoader(Coordinate coord) {
    return new TileLoader(papplet, provider, this, coord);

    // TODO Make showDebug etc public.
    // TileLoader tl = new TileLoader(papplet, provider, this, coord);
    // tl.showDebugBorder();
    // tl.showTileCoordinates();
    // return tl;
  }

}
TOP

Related Classes of de.fhpotsdam.unfolding.mapdisplay.Java2DMapDisplay

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.