/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Author: Damian Waradzyn
*/
package com.mapmidlet.tile.ui;
import henson.midp.Float11;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;
import com.mapmidlet.CloudGps;
import com.mapmidlet.gps.GpsState;
import com.mapmidlet.gps.GpsState.Satellite;
import com.mapmidlet.misc.IOTool;
import com.mapmidlet.options.Options;
import com.mapmidlet.projection.*;
import com.mapmidlet.routing.*;
import com.mapmidlet.routing.Route.RouteDirection;
import com.mapmidlet.tile.provider.*;
/**
* This canvas shows the map and some controls on the screen. This class is also
* responsible for updating various variables (tile management, reading and
* interpolating GPS position and so on) and repainting constantly. Besides that
* it handles interaction with user - keys and touchscreen.
*
* @author Damian Waradzyn
*/
public class TileCanvas extends GameCanvas implements Runnable {
private final double MAX_SCROLL_SPEED = 5.0;
private final double SCROLL_SPEED_DELTA = MAX_SCROLL_SPEED / 10.0;
private AbstractTileFactory tileFactory;
private Tile[][][] tiles = null;
private int currentTilesIdx = 1;
public int latitude;
public int longitude;
private int zoom;
public double x, y;
double dx = 0.0, dy = 0.0;
public int horizontalTilesCount;
public int verticalTilesCount;
private boolean followGps;
private boolean followMarker;
public int tileSize;
private ScreenMarker draggedMarker;
private boolean dragging = false;
boolean doubleBuffered;
private Image buffer = null;
private boolean running = false;
private final Options options = Options.getInstance();
public boolean doZoomIn, doZoomOut, doScreenSizeChanged, doChangeFollowMode, doCycleRouteEnds, firePressed;
private GpsState gpsState = new GpsState();
private ScreenCoordinate gpsCurrent;
public TileCanvas() {
super(false);
doubleBuffered = isDoubleBuffered();
if (!doubleBuffered) {
buffer = Image.createImage(getMapWidth(), getMapHeight());
}
}
long lastRouteCalcMilis = -1;
public void run() {
while (true) {
try {
if (running) {
if (doScreenSizeChanged) {
createTiles();
doScreenSizeChanged = false;
}
if (doZoomIn) {
zoomIn();
doZoomIn = false;
}
if (doZoomOut) {
zoomOut();
doZoomOut = false;
}
if (options.routeEndIndex < 0) {
followMarker = false;
}
processMarkerDragging();
updateCoordinates();
if (firePressed) {
if (options.routeEndIndex >= 0) {
ScreenMarker marker = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
marker.worldCoordinate = ProjectionTool.toWorldCoordinate(marker.tileCoordinate, tileSize);
marker.raised = false;
followMarker = false;
options.routeEndIndex = -1;
}
firePressed = false;
}
if (doCycleRouteEnds) {
doCycleRouteEnds = false;
cycleRouteEnds();
}
gpsState = new GpsState(CloudGps.gpsState);
// if (gpsState.state < GpsState.CONNECTED_FIX) {
// doChangeFollowMode = false;
// }
Navigation.navigate(gpsState);
gpsState.performCalculations();
if (doChangeFollowMode) {
changeFollowGps();
doChangeFollowMode = false;
}
if (gpsState.state >= GpsState.CONNECTED_FIX) {
calculateGpsCurrent();
}
if (followMarker && options.automaticRouteCalc
&& System.currentTimeMillis() - lastRouteCalcMilis > 10000) {
ScreenMarker marker = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
if (marker.tileCoordinate != null) {
marker.worldCoordinate = ProjectionTool.toWorldCoordinate(marker.tileCoordinate, tileSize);
CloudGps.calculateRoute(false);
lastRouteCalcMilis = System.currentTimeMillis();
}
}
repaint();
Thread.sleep(20);
} else {
Thread.yield();
}
} catch (Throwable e) {
CloudGps.setError(e);
}
}
}
private void cycleRouteEnds() {
ScreenMarker marker;
if (options.routeEndIndex >= 0) {
marker = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
marker.raiseChangeMilis = System.currentTimeMillis();
marker.worldCoordinate = ProjectionTool.toWorldCoordinate(marker.tileCoordinate, tileSize);
marker.raised = false;
}
options.routeEndIndex++;
if (options.routeEndIndex > 1) {
options.routeEndIndex = -1;
followMarker = false;
} else {
marker = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
marker.visible = true;
marker.raised = true;
marker.raiseChangeMilis = System.currentTimeMillis();
followMarker = true;
if (Double.isNaN(marker.worldCoordinate.latitude)) {
marker.worldCoordinate = getCurrentCenter();
}
}
}
protected void keyPressed(int keyCode) {
// System.out.println(getKeyName(keyCode));
if (keyCode == KEY_STAR) {
doZoomOut = true;
} else if (keyCode == KEY_POUND) {
doZoomIn = true;
} else if (keyCode == KEY_NUM0) {
doCycleRouteEnds = true;
} else if (keyCode == KEY_NUM2) {
doChangeFollowMode = true;
} else if (keyCode == KEY_NUM1) {
decreaseReplaySpeed();
} else if (keyCode == KEY_NUM3) {
increaseReplaySpeed();
} else if (keyCode == FIRE || getKeyName(keyCode).equals("SELECT")) {
firePressed = true;
}
}
protected void keyRepeated(int keyCode) {
keyPressed(keyCode);
}
private void calculateGpsCurrent() {
gpsCurrent = toScreenCoordinate(gpsState.tileCoordinate, x, y);
if (gpsState.prevTileCoordinate != null) {
ScreenCoordinate gpsPrevious = toScreenCoordinate(gpsState.prevTileCoordinate, x, y);
if (gpsState.coordinateUpdateMilis - gpsState.prevCoordinateUpdateMilis != 0) {
double factor = (System.currentTimeMillis() - gpsState.coordinateUpdateMilis)
/ (double) (gpsState.coordinateUpdateMilis - gpsState.prevCoordinateUpdateMilis);
if (factor > 1.0) {
factor = 1.0;
}
gpsCurrent.x = (int) (gpsPrevious.x + factor * (gpsCurrent.x - gpsPrevious.x));
gpsCurrent.y = (int) (gpsPrevious.y + factor * (gpsCurrent.y - gpsPrevious.y));
}
}
}
private void updateTiles(int horizontalShift, int verticalShift) {
// Manual image deallocation - helps some KVMs with memory management.
deallocateTiles(horizontalShift, verticalShift);
for (int i = 0; i < tiles.length; i++) {
for (int j = 0; j < tiles[i].length; j++) {
int newPosX = i - horizontalShift;
int newPosY = j - verticalShift;
if (newPosX >= 0 && newPosX < horizontalTilesCount && newPosY >= 0 && newPosY < verticalTilesCount) {
tiles[i][j][1 - currentTilesIdx] = tiles[newPosX][newPosY][currentTilesIdx];
} else {
Tile newTile = tileFactory.createTile(tiles[i][j][currentTilesIdx], horizontalShift, verticalShift);
tiles[i][j][1 - currentTilesIdx] = newTile;
}
}
}
currentTilesIdx = 1 - currentTilesIdx;
}
private void deallocateTiles(int horizontalShift, int verticalShift) {
for (int i = getLowerDeallocateBound(horizontalTilesCount, horizontalShift); i < getUpperDeallocateBound(
horizontalTilesCount, horizontalShift); i++) {
for (int j = 0; j < verticalTilesCount; j++) {
tiles[i][j][currentTilesIdx].deallocate();
}
}
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = getLowerDeallocateBound(verticalTilesCount, verticalShift); j < getUpperDeallocateBound(
verticalTilesCount, verticalShift); j++) {
tiles[i][j][currentTilesIdx].deallocate();
}
}
}
private int getLowerDeallocateBound(int size, int shift) {
return shift < 0 ? 0 : (size - shift) < 0 ? 0 : (size - shift);
}
private int getUpperDeallocateBound(int size, int shift) {
return shift < 0 ? (-shift < size ? -shift : size) : size;
}
private void updateCoordinates() {
dx *= 0.95;
dy *= 0.95;
if (pressedTime > 0 && System.currentTimeMillis() - pressedTime > 250) {
dx *= 0.25;
dy *= 0.25;
}
if (getKeyStates() != 0) {
followGps = false;
if ((getKeyStates() & LEFT_PRESSED) != 0) {
dx += SCROLL_SPEED_DELTA;
}
if ((getKeyStates() & RIGHT_PRESSED) != 0) {
dx -= SCROLL_SPEED_DELTA;
}
if ((getKeyStates() & UP_PRESSED) != 0) {
dy += SCROLL_SPEED_DELTA;
}
if ((getKeyStates() & DOWN_PRESSED) != 0) {
dy -= SCROLL_SPEED_DELTA;
}
if (dx > MAX_SCROLL_SPEED) {
dx = MAX_SCROLL_SPEED;
}
if (dx < -MAX_SCROLL_SPEED) {
dx = -MAX_SCROLL_SPEED;
}
if (dy > MAX_SCROLL_SPEED) {
dy = MAX_SCROLL_SPEED;
}
if (dy < -MAX_SCROLL_SPEED) {
dy = -MAX_SCROLL_SPEED;
}
}
if (followGps || followMarker) {
computeFollowDeltas();
}
if (followMarker) {
ScreenMarker routeEnd = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
if (routeEnd.screenCoordinate != null && routeEnd.screenCoordinate.x == getMapWidth() / 2
&& routeEnd.screenCoordinate.y == getMapHeight() / 2) {
routeEnd.tileCoordinate.x -= dx;
routeEnd.tileCoordinate.y -= dy;
}
}
if (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01) {
x += dx;
y += dy;
} else {
dx = 0.0;
dy = 0.0;
}
// Counter force disallowing moving off the map.
/*
* int mapSize = tileSize << zoom; if ((latitude) * tileSize + x < 0) {
* double cfx = ((-(latitude + 1)) * tileSize + x) / 4.0; if (cfx > 0.1)
* { x -= cfx; System.out.println("cfx = " + cfx); } } else if
* ((latitude) * tileSize + x > mapSize) { x += 5; }
*
* if ((longitude) * tileSize + y < 0) { double cfy = ((-(longitude +
* 1)) * tileSize + y) / 4.0; if (cfy > 0.1) { y -= cfy;
* System.out.println("cfy = " + cfy); } }
*/
for (int i = 0; i < tiles.length; i++) {
for (int j = 0; j < tiles[i].length; j++) {
if (tiles[i][j][currentTilesIdx] != null && tiles[i][j][currentTilesIdx].state == Tile.STATE_EMPTY
&& isVisible(i, j)) {
tiles[i][j][currentTilesIdx].load();
}
}
}
int horizontalShift = roundDown(x / tileSize);
int verticalShift = roundDown(y / tileSize);
if (horizontalShift != 0 || verticalShift != 0) {
x -= horizontalShift * tileSize;
y -= verticalShift * tileSize;
latitude -= horizontalShift;
longitude -= verticalShift;
updateTiles(horizontalShift, verticalShift);
}
}
private int roundDown(double x) {
return x < 0 ? (int) (x - 1.0) : (int) x;
}
private void createTiles() {
int verticalTilesCount = this.verticalTilesCount;
int horizontalTilesCount = this.horizontalTilesCount;
this.verticalTilesCount = 0;
this.horizontalTilesCount = 0;
if (tiles != null) {
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = 0; j < verticalTilesCount; j++) {
tiles[i][j][currentTilesIdx].deallocate();
}
}
tiles = null;
}
horizontalTilesCount = getTilesCount(getMapWidth(), tileSize);
verticalTilesCount = getTilesCount(getMapHeight(), tileSize);
// System.gc();
tiles = new Tile[horizontalTilesCount][verticalTilesCount][2];
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = 0; j < verticalTilesCount; j++) {
tiles[i][j][0] = tiles[i][j][1] = tileFactory.createTile(zoom, latitude + i, longitude + j);
// lazy load
if (isVisible(i, j)) {
tiles[i][j][0].load();
}
}
}
this.verticalTilesCount = verticalTilesCount;
this.horizontalTilesCount = horizontalTilesCount;
}
private boolean isVisible(int i, int j) {
int tilePositionX = getTilePosition(x, i, tileSize);
int tilePositionY = getTilePosition(y, j, tileSize);
return !(tilePositionX >= getMapWidth() || tilePositionY >= getMapHeight() || tilePositionX <= -tileSize || tilePositionY <= -tileSize);
}
public int getMapHeight() {
return getHeight() - 40;
}
public int getMapWidth() {
return getWidth();
}
private int getTilesCount(int screenSize, int tileSize) {
if (screenSize <= tileSize) {
return 2;
}
return (int) ((double) (screenSize) / (double) tileSize) + 2;
}
public void paint(Graphics screen) {
try {
Graphics g;
Tile[][][] tiles = this.tiles;
if (!doubleBuffered) {
g = buffer.getGraphics();
} else {
g = screen;
}
g.setFont(options.skin.mainFont);
double x = this.x;
double y = this.y;
int panelHeight = options.skin.panelBackground.getHeight();
int tmpIdx = currentTilesIdx;
Tile[][] tiles2 = new Tile[tiles.length][tiles[0].length];
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = 0; j < verticalTilesCount; j++) {
tiles2[i][j] = tiles[i][j][tmpIdx];
}
}
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = 0; j < verticalTilesCount; j++) {
Tile tile = tiles2[i][j];
if (tile != null) {
g.translate(getTilePosition(x, i, tileSize) - g.getTranslateX(),
getTilePosition(y, j, tileSize) - g.getTranslateY() + panelHeight);
tile.paint(g, getMapWidth(), getMapHeight());
if (options.debugMode) {
g.setColor(20, 20, 250);
g.drawString(tile.getLatitude() + "," + tile.getLongitude(), 0, 0, Graphics.LEFT
| Graphics.TOP);
g.drawRect(0, 0, tileSize, tileSize);
}
} else {
Tile.paintEmpty(g, tileSize, tileSize);
}
}
}
g.translate(-g.getTranslateX(), -g.getTranslateY() + panelHeight);
drawRoute(g, x, y);
drawMarkers(g, x, y);
drawNav(g);
if (gpsState.state >= GpsState.CONNECTED_FIX) {
String posImgPrefix = (options.navContext.directionIdx < 0) ? "position" : "snapped_pos";
Image posImage = options.skin
.getImage(posImgPrefix + (System.currentTimeMillis() % 800) / 200 + ".png");
g.drawImage(posImage, gpsCurrent.x, gpsCurrent.y, Graphics.HCENTER | Graphics.VCENTER);
}
drawScale(g);
g.setColor(0, 0, 0);
g.drawLine(getMapWidth() / 2, getMapHeight() / 2 - 15, getMapWidth() / 2, getMapHeight() / 2 + 15);
g.drawLine(getMapWidth() / 2 - 15, getMapHeight() / 2, getMapWidth() / 2 + 15, getMapHeight() / 2);
if (options.debugMode) {
if (gpsState.state >= GpsState.CONNECTED_FIX) {
g.setColor(0, 0, 0);
ScreenCoordinate tmp = toScreenCoordinate(ProjectionTool.toTileCoordinate(gpsState.worldCoordinate,
zoom, tileSize), x, y);
g.drawRect(tmp.x, tmp.y, 1, 1);
}
}
g.translate(-g.getTranslateX(), -g.getTranslateY());
g.drawImage(options.skin.panelBackground, 0, 0, Graphics.LEFT | Graphics.TOP);
drawStatusString(g, 1, 1, "Downloaded " + (options.downloaded / 1024) + " kB", Graphics.LEFT | Graphics.TOP);
if (options.gpsEnabled || options.replayMode) {
StringBuffer sb = new StringBuffer(96);
int state = gpsState.state;
if (state == GpsState.CONNECTING) {
sb.append("GPS: Connecting...");
} else if (state == GpsState.CONNECTED_NO_FIX) {
sb.append("GPS: No fix");
} else if (state == GpsState.CONNECTED_FIX) {
if (gpsState.activeSats != null) {
sb.append("GPS: " + gpsState.activeSats.size() + " active sats");
}
} else {
sb.append("GPS: Connection error");
}
drawStatusString(g, 1, 15, sb.toString(), Graphics.LEFT | Graphics.TOP);
int speedX = options.skin.mainFont.stringWidth("Downloaded 99999 kB") + 30;
if (gpsState.state >= GpsState.CONNECTED_FIX && gpsState.groundSpeed >= 0) {
drawStatusString(g, speedX, 1, getRoundedNumber(gpsState.groundSpeed), Graphics.RIGHT
| Graphics.TOP);
drawStatusString(g, speedX, 15, "km/h", Graphics.RIGHT | Graphics.TOP);
}
int altX = speedX + options.skin.mainFont.stringWidth("AMSL") + 10;
if (gpsState.state >= GpsState.CONNECTED_FIX) {
drawStatusString(g, altX, 1, gpsState.altitude + " m", Graphics.HCENTER | Graphics.TOP);
drawStatusString(g, altX, 15, "AMSL", Graphics.HCENTER | Graphics.TOP);
}
int satsInView = gpsState.satellitesInView;
if (satsInView > 0) {
int barWidth = 12;
int barPanelX = getWidth() - (satsInView * barWidth);
if (barPanelX < altX + 30) {
satsInView = (getWidth() - (altX + 30)) / 12;
barPanelX = getWidth() - (satsInView * barWidth);
}
Vector satellites = gpsState.satellites;
Hashtable activeSats = gpsState.activeSats;
for (int i = 0; i < satsInView; i++) {
int barX = barPanelX + i * barWidth;
g.setColor(0, 0, 0);
g.drawRect(barX, 1, barWidth - 3, 25);
if (satellites != null && i < satellites.size()) {
Satellite sat = (Satellite) satellites.elementAt(i);
if (activeSats != null && activeSats.contains(sat.prn)) {
g.setColor(200, 200, 200);
} else {
g.setColor(40, 40, 40);
}
g.drawString(sat.prn, barX, 26, Graphics.LEFT | Graphics.TOP);
int barHeight = sat.snr / 2;
if (barHeight > 25) {
barHeight = 25;
}
g.fillRect(barX + 1, 26 - barHeight, barWidth - 4, barHeight);
}
}
}
if (options.replayMode) {
g.drawImage(options.skin.getImage("decrease_speed.png"), getMapWidth() / 2 - 80, 50, Graphics.TOP
| Graphics.LEFT);
g.drawImage(options.skin.getImage("increase_speed.png"), getMapWidth() / 2 + 32, 50, Graphics.TOP
| Graphics.LEFT);
g.setColor(40, 40, 40);
g.fillRect(getMapWidth() / 2 - 32, 50, 64, 48);
g.setColor(250, 250, 250);
g.drawString(options.replaySpeed + "x", getMapWidth() / 2, 55, Graphics.HCENTER | Graphics.TOP);
g.drawString(getRoundedNumber(gpsState.replyPosition * 100.0 / (double) gpsState.replySize) + "%",
getMapWidth() / 2, 75, Graphics.HCENTER | Graphics.TOP);
}
}
if (hasPointerEvents()) {
g.drawImage(options.skin.getImage("zoom_out.png"), 10, getHeight() - 58, Graphics.TOP | Graphics.LEFT);
g.drawImage(options.skin.getImage("zoom_in.png"), getMapWidth() - 58, getHeight() - 58, Graphics.TOP
| Graphics.LEFT);
if (gpsState.state >= GpsState.CONNECTED_FIX) {
g.drawImage(options.skin.getImage(followGps ? "my_pos_enabled.png" : "my_pos_disabled.png"),
getMapWidth() - 116, getHeight() - 58, Graphics.TOP | Graphics.LEFT);
}
}
if (!doubleBuffered) {
screen.drawImage(buffer, 0, 0, Graphics.LEFT | Graphics.TOP);
flushGraphics();
}
} catch (Throwable e) {
CloudGps.setError(e);
}
}
private void drawStatusString(Graphics g, int x, int y, String string, int anchor) {
g.setColor(50, 50, 50);
g.drawString(string, x, y, anchor);
g.setColor(250, 250, 250);
g.drawString(string, x + 1, y + 1, anchor);
}
private void drawNav(Graphics g) {
String img = null;
if (options.routingStatus == Options.ROUTING_ERROR) {
img = "nav_calc_error.png";
} else if (options.routingStatus == Options.ROUTING_CALCULATING) {
img = "nav_calc.png";
} else if (gpsState.state >= GpsState.CONNECTED_FIX) {
if (options.route != null) {
if (options.navContext.directionIdx == -1) {
img = "nav_offroad.png";
} else {
RouteDirection direction = (RouteDirection) options.route.routeDirections
.elementAt(options.navContext.directionIdx);
img = "turn" + (direction.turn == null ? "C" : direction.turn) + ".png";
g.drawString("[" + options.navContext.directionIdx + "] " + direction.text, 10, 70, Graphics.TOP
| Graphics.LEFT);
int distance = (int) ProjectionTool.getDistance((WorldCoordinate) options.route.waypoints
.elementAt(direction.offset), gpsState.worldCoordinate) / 10;
distance *= 10;
String str;
if (distance > 1000) {
str = getRoundedNumber(distance / 1000) + " km";
} else if (distance == 0) {
str = "<10 m";
} else {
str = distance + " m";
}
g.drawString(str, 10, 55, Graphics.TOP | Graphics.LEFT);
}
}
}
if (img != null) {
g.drawImage(options.skin.getImage(img), 0, 0, Graphics.TOP | Graphics.LEFT);
}
}
private void drawRoute(Graphics g, double x, double y) {
OptimizedRoute optimizedRoute = options.navContext.optimizedRoute;
if (optimizedRoute != null) {
Vector segments = optimizedRoute.visibleSegments;
Vector dirs = optimizedRoute.visibleSegmentDirections;
if (segments != null && dirs != null) {
ScreenCoordinate s1, s2;
Integer prevdir = null;
g.setColor(0, 0, 255);
for (int i = 0; i < segments.size(); i += 2) {
s1 = toScreenCoordinate((TileCoordinate) segments.elementAt(i), x, y);
s2 = toScreenCoordinate((TileCoordinate) segments.elementAt(i + 1), x, y);
Integer dir = (Integer) dirs.elementAt(i / 2);
if (options.debugMode) {
if (prevdir != null && !dir.equals(prevdir)) {
g.setColor(255 - g.getRedComponent(), 0, 255 - g.getBlueComponent());
}
int dirx = (s1.x + s2.x) / 2, diry = (s1.y + s2.y) / 2;
if (Math.abs(s1.x - s2.x) > Math.abs(s1.y - s2.y)) {
diry += 6;
} else {
dirx += 6;
}
g.drawString("" + dir, dirx, diry, Graphics.TOP | Graphics.HCENTER);
prevdir = dir;
}
// g.drawLine(s1.x, s1.y, s2.x, s2.y);
boldLine(g, s1.x, s1.y, s2.x, s2.y);
}
}
}
}
private void boldLine(Graphics g, int x1, int y1, int x2, int y2) {
if (Math.abs(x1 - x2) > Math.abs(y1 - y2)) {
g.drawLine(x1, y1 - 1, x2, y2 - 1);
g.drawLine(x1, y1, x2, y2);
g.drawLine(x1, y1 + 1, x2, y2 + 1);
} else {
g.drawLine(x1 - 1, y1, x2 - 1, y2);
g.drawLine(x1, y1, x2, y2);
g.drawLine(x1 + 1, y1, x2 + 1, y2);
}
}
private void drawMarkers(Graphics g, double x, double y) {
if (options.markers != null && !options.markers.isEmpty()) {
Image shadow = options.skin.getImage("shadow.png");
Image marker = options.skin.getImage("search_result.png");
// Calculate visibility for markers and draw shadows first.
for (int i = 0; i < options.markers.size(); i++) {
ScreenMarker res = (ScreenMarker) options.markers.elementAt(i);
if (res.visible) {
if (res.tileCoordinate == null || res.tileCoordinate.zoom != zoom) {
if (res.tileCoordinate != null) {
res.worldCoordinate = ProjectionTool.toWorldCoordinate(res.tileCoordinate, tileSize);
}
res.tileCoordinate = ProjectionTool.toTileCoordinate(res.worldCoordinate, zoom, tileSize);
}
res.screenCoordinate = toScreenCoordinate(res.tileCoordinate, x, y);
if (res.screenCoordinate.x > -marker.getWidth() / 2
&& res.screenCoordinate.x < getMapWidth() + marker.getWidth() / 2
&& res.screenCoordinate.y > 0
&& res.screenCoordinate.y < getMapHeight() + marker.getHeight()) {
g.drawImage(shadow, res.screenCoordinate.x, res.screenCoordinate.y, Graphics.BOTTOM
| Graphics.LEFT);
} else {
// Marker is not visible.
res.screenCoordinate = null;
}
}
}
// Update raise level and draw all markers.
for (int i = 0; i < options.markers.size(); i++) {
ScreenMarker res = (ScreenMarker) options.markers.elementAt(i);
if (res.visible) {
if (res.raised && res.raiseLevel < 20) {
res.raiseLevel++;
} else if (!res.raised && res.raiseLevel > 0) {
res.raiseLevel--;
}
marker = options.skin.getImage(res.iconName);
if (res.screenCoordinate != null) {
g.drawImage(marker, res.screenCoordinate.x, res.screenCoordinate.y - res.raiseLevel,
Graphics.HCENTER | Graphics.BOTTOM);
}
}
}
}
}
private void drawScale(Graphics g) {
double gr = ProjectionTool.getGroundResolution(getCurrentCenter(), tileSize, zoom);
double log10 = Float11.log10(gr);
double norm = gr / Float11.pow(10, Math.floor(log10) + 1);
double scaleWidth;
String scaleText;
if (norm < 0.15) {
scaleWidth = 10;
} else if (norm < 0.3) {
scaleWidth = 20;
} else if (norm < 0.7) {
scaleWidth = 50;
} else {
scaleWidth = 100;
}
int scale = ((int) scaleWidth * (int) Float11.pow(10, Math.floor(log10) + 1));
if (scale >= 1000) {
scaleText = (scale / 1000) + " km";
} else {
scaleText = scale + " m";
}
scaleWidth = scaleWidth / norm;
g.setColor(50, 50, 200);
int x1 = getMapWidth() / 2 - (int) scaleWidth / 2;
int y = getMapHeight() - 15;
int x2 = x1 + (int) scaleWidth;
g.drawLine(x1, y, x2, y);
g.drawLine(x1, y, x1, y - 5);
g.drawLine(x2, y, x2, y - 5);
g.drawLine(getMapWidth() / 2, y, getMapWidth() / 2, y - 2);
g.drawString(scaleText, x1 + (int) (scaleWidth / 2), y - 4, Graphics.HCENTER | Graphics.BASELINE);
}
private String getRoundedNumber(double number) {
int i = (int) Math.floor(number);
int frac = (int) ((number - i) * 100.0);
return i + "." + (frac < 10 ? "0" : "") + frac;
}
private int getTilePosition(double screenTranslation, int tileNum, int tileSize) {
return (int) screenTranslation + (tileNum - 1) * tileSize;
}
public ScreenCoordinate toScreenCoordinate(TileCoordinate c, double x, double y) {
ScreenCoordinate result = new ScreenCoordinate();
result.x = (int) ((c.latitude - latitude - 1) * tileSize + x + c.x);
result.y = (int) ((c.longitude - longitude - 1) * tileSize + y + c.y);
return result;
}
int lastX = -1, lastY = -1;
private long pressedTime, markerPressedTime;
protected void pointerPressed(int x, int y) {
int width = getWidth();
int height = getHeight();
if (x >= 10 && x <= 58 && y >= height - 58 && y <= height - 10) {
doZoomOut = true;
} else if (x >= width - 58 && x <= width - 10 && y >= height - 58 && y <= height - 10) {
doZoomIn = true;
} else if (gpsState.state >= GpsState.CONNECTED_FIX && x >= width - 116 && x <= width - 68 && y >= height - 58
&& y <= height - 10) {
doChangeFollowMode = true;
} else if (Options.getInstance().replayMode && x >= getMapWidth() / 2 - 64 && x <= getMapWidth() / 2 - 16
&& y >= 50 && y <= 98) {
decreaseReplaySpeed();
} else if (Options.getInstance().replayMode && x >= getMapWidth() / 2 + 16 && x <= getMapWidth() / 2 + 64
&& y >= 50 && y <= 98) {
increaseReplaySpeed();
}
else {
followGps = false;
lastX = x;
lastY = y;
pressedTime = System.currentTimeMillis();
}
}
private void processMarkerDragging() {
dragging = false;
if (lastX >= 0 && draggedMarker != null && draggedMarker.raiseLevel >= 20) {
draggedMarker.tileCoordinate.x += lastX - draggedMarker.screenCoordinate.x;
draggedMarker.tileCoordinate.y += lastY - draggedMarker.screenCoordinate.y;
draggedMarker.worldCoordinate = ProjectionTool.toWorldCoordinate(draggedMarker.tileCoordinate, tileSize);
dragging = true;
return;
} else if (options.markers != null) {
for (int i = 0; i < options.markers.size(); i++) {
ScreenMarker marker = (ScreenMarker) options.markers.elementAt(i);
if (marker.visible && marker.screenCoordinate != null
&& Math.abs((lastX - marker.screenCoordinate.x)) < 15
&& Math.abs((lastY - marker.screenCoordinate.y - 18)) < 20) {
dragging = true;
if (draggedMarker != marker) {
if (draggedMarker != null) {
changeDraggedIcon(draggedMarker, false);
draggedMarker.raised = false;
}
markerPressedTime = System.currentTimeMillis();
draggedMarker = marker;
changeDraggedIcon(draggedMarker, true);
} else {
if (System.currentTimeMillis() - markerPressedTime > 300) {
draggedMarker.raised = true;
}
}
break;
}
}
}
if (!dragging && draggedMarker != null) {
changeDraggedIcon(draggedMarker, false);
draggedMarker.raised = false;
draggedMarker = null;
}
}
private void changeDraggedIcon(ScreenMarker marker, boolean addRemove) {
if (addRemove) {// add dragged suffix
if (!marker.iconName.endsWith("_dragged.png")) {
marker.iconName = marker.iconName.substring(0, marker.iconName.length() - 4) + "_dragged.png";
}
} else { // remove suffix
if (marker.iconName.endsWith("_dragged.png")) {
marker.iconName = marker.iconName.substring(0, marker.iconName.length() - 12) + ".png";
}
}
}
private void changeFollowGps() {
followGps = !followGps;
if (followGps) {
followMarker = false;
options.routeEndIndex = -1;
// todo raisemilis
computeFollowDeltas();
}
}
private void computeFollowDeltas() {
if (followGps && gpsState.state >= GpsState.CONNECTED_FIX) {
double factor = 2 + Math.sqrt(options.replaySpeed) / 5.0;
dx = (getMapWidth() / 2 - gpsCurrent.x) * factor / 25.0;
dy = (getMapHeight() / 2 - gpsCurrent.y) * factor / 25.0;
}
if (followMarker) {
ScreenMarker marker = (ScreenMarker) options.routeEnds.elementAt(options.routeEndIndex);
ScreenCoordinate follow = marker.screenCoordinate;
if (follow == null) {
if (marker.tileCoordinate == null) {
marker.tileCoordinate = ProjectionTool.toTileCoordinate(marker.worldCoordinate, zoom, tileSize);
}
follow = toScreenCoordinate(marker.tileCoordinate, x, y);
}
if (follow.x != getMapWidth() / 2 || follow.y != getMapHeight() / 2) {
dx = (getMapWidth() / 2 - follow.x) / 5.0;
dy = (getMapHeight() / 2 - follow.y) / 5.0;
}
}
}
protected void pointerDragged(int x, int y) {
if (lastX >= 0 && lastY >= 0) {
if (!dragging) {
dx += (x - lastX) / 4.0;
dy += (y - lastY) / 4.0;
}
lastX = x;
lastY = y;
pressedTime = System.currentTimeMillis();
}
}
protected void pointerReleased(int x, int y) {
lastX = -1;
lastY = -1;
pressedTime = -1;
}
public void reload() {
for (int i = 0; i < horizontalTilesCount; i++) {
for (int j = 0; j < verticalTilesCount; j++) {
if (isVisible(i, j)) {
tiles[i][j][currentTilesIdx].load();
}
}
}
}
protected void sizeChanged(int w, int h) {
doScreenSizeChanged = true;
}
public void setTileFactory(AbstractTileFactory tileFactory) {
setRunning(false);
this.tileFactory = tileFactory;
this.tileSize = tileFactory.getTileSize();
if (zoom > tileFactory.getMaxZoom()) {
setZoomAndCenter(tileFactory.getMaxZoom(), getCurrentCenter());
}
IOTool.ensureDirExist(tileFactory.getDirectoryName());
createTiles();
setRunning(true);
}
public void setRunning(boolean running) {
this.running = running;
}
public void zoomIn() {
if (zoom < tileFactory.getMaxZoom()) {
int newZoom = zoom + 1;
setZoomAndCenter(newZoom, getCurrentCenter());
}
}
public void zoomOut() {
if (zoom > 0) {
int newZoom = zoom - 1;
setZoomAndCenter(newZoom, getCurrentCenter());
}
}
public WorldCoordinate getCurrentCenter() {
TileCoordinate center = new TileCoordinate();
center.zoom = zoom;
center.latitude = (int) latitude;
center.longitude = (int) longitude;
center.x = (tileSize - x) + getMapWidth() / 2;
center.y = (tileSize - y) + getMapHeight() / 2;
adjustShifts(center);
return ProjectionTool.toWorldCoordinate(center, tileSize);
}
private void adjustShifts(TileCoordinate c) {
int horizontalShift = roundDown(c.x / (double) tileSize);
int verticalShift = roundDown(c.y / (double) tileSize);
c.x -= horizontalShift * tileSize;
c.y -= verticalShift * tileSize;
c.latitude += horizontalShift;
c.longitude += verticalShift;
}
public void setZoomAndCenter(int newZoom, WorldCoordinate center) {
if (newZoom < 0 || newZoom > tileFactory.getMaxZoom()) {
throw new RuntimeException("Illegal zoom value in setZoomAndCenter(): " + newZoom);
}
boolean oldFollowMode = followGps;
zoom = newZoom;
options.zoom = zoom;
TileCoordinate tileCoordinate = ProjectionTool.toTileCoordinate(center, newZoom, tileSize);
tileCoordinate.x -= getMapWidth() / 2;
tileCoordinate.y -= getMapHeight() / 2;
adjustShifts(tileCoordinate);
latitude = tileCoordinate.latitude;
longitude = tileCoordinate.longitude;
x = tileSize - tileCoordinate.x;
y = tileSize - tileCoordinate.y;
dx = 0;
dy = 0;
createTiles();
followGps = oldFollowMode;
}
private void decreaseReplaySpeed() {
if (Options.getInstance().replaySpeed > 1) {
Options.getInstance().replaySpeed /= 2;
}
}
private void increaseReplaySpeed() {
if (Options.getInstance().replaySpeed < 256) {
Options.getInstance().replaySpeed *= 2;
}
}
public void showSearchResults() {
if (options.markers != null && !options.markers.isEmpty()) {
ScreenMarker res = (ScreenMarker) options.markers.elementAt(0);
double minx = res.worldCoordinate.longitude;
double maxx = res.worldCoordinate.longitude;
double miny = res.worldCoordinate.latitude;
double maxy = res.worldCoordinate.latitude;
if (options.markers.size() > 1) {
for (int i = 1; i < options.markers.size(); i++) {
res = (ScreenMarker) options.markers.elementAt(i);
if (res.worldCoordinate.longitude < minx) {
minx = res.worldCoordinate.longitude;
}
if (res.worldCoordinate.longitude > maxx) {
maxx = res.worldCoordinate.longitude;
}
if (res.worldCoordinate.latitude < miny) {
miny = res.worldCoordinate.latitude;
}
if (res.worldCoordinate.latitude > maxy) {
maxy = res.worldCoordinate.latitude;
}
}
} else {
setZoomAndCenter(zoom, res.worldCoordinate);
return;
}
// Naive way to find optimal zoom showing all search results.
int newZoom;
WorldCoordinate upperLeft = new WorldCoordinate();
upperLeft.latitude = miny;
upperLeft.longitude = minx;
WorldCoordinate lowerLeft = new WorldCoordinate();
lowerLeft.latitude = maxy;
lowerLeft.longitude = minx;
WorldCoordinate lowerRight = new WorldCoordinate();
lowerRight.latitude = maxy;
lowerRight.longitude = maxx;
for (newZoom = 1; newZoom < tileFactory.getMaxZoom(); newZoom++) {
TileCoordinate upperLeftTile = ProjectionTool.toTileCoordinate(upperLeft, newZoom, tileSize);
TileCoordinate lowerLeftTile = ProjectionTool.toTileCoordinate(lowerLeft, newZoom, tileSize);
TileCoordinate lowerRightTile = ProjectionTool.toTileCoordinate(lowerRight, newZoom, tileSize);
double scrMinx = lowerLeftTile.latitude * tileSize + lowerLeftTile.x;
double scrMaxx = lowerRightTile.latitude * tileSize + lowerRightTile.x;
double scrMiny = upperLeftTile.longitude * tileSize + upperLeftTile.y;
double scrMaxy = lowerLeftTile.longitude * tileSize + lowerLeftTile.y;
if (Math.abs(scrMinx - scrMaxx) > getMapWidth() * 0.9) {
break;
}
if (Math.abs(scrMiny - scrMaxy) > getMapHeight() * 0.9) {
break;
}
}
WorldCoordinate center = new WorldCoordinate();
center.longitude = (maxx + minx) / 2.0;
center.latitude = (maxy + miny) / 2.0;
setZoomAndCenter(newZoom - 1, center);
}
}
}