www.pudn.com > GMapViewer-src.zip > GMapCanvas.java
package org.sreid.j2me.gmapviewer;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import org.sreid.j2me.util.*;
class GMapCanvas extends Canvas implements CommandListener {
private static final String MAIN_MENU = "Back";
private static final String ZOOM_IN = "Zoom in";
private static final String ZOOM_OUT = "Zoom out";
private static final String CREATE_MAP_PIN = "New map pin";
private static final String MAP_PIN_MENU = "Map pin menu";
private static final String SEARCH_MENU = "Search menu";
private final GMapViewer app;
// Font for map pin text
private final Font font = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL);
// Absolute position.
private int x, y;
// Zoom level. 0-14. Smaller is more zoomed in.
private int zoom;
private long lastMoveTime = 0;
int requestID = 0; // used by MapTileManager.getMapTile
GMapCanvas(GMapViewer app) {
this.app = app;
setCommandListener(this);
addCommand(new Command(MAIN_MENU, Command.BACK, 1));
addCommand(new Command(ZOOM_IN, Command.SCREEN, 1));
addCommand(new Command(ZOOM_OUT, Command.SCREEN, 1));
addCommand(new Command(CREATE_MAP_PIN, Command.SCREEN, 2));
addCommand(new Command(MAP_PIN_MENU, Command.SCREEN, 2));
addCommand(new Command(SEARCH_MENU, Command.SCREEN, 2));
}
void setXY(int x, int y) {
this.x = x;
this.y = y;
app.mpm.sortPinsNearestFirst();
repaint();
}
int getX() { return x; }
int getY() { return y; }
int getZ() { return zoom; }
// Move view position (relative, 0 = no change)
private void moveView(int movx, int movy) {
long t = System.currentTimeMillis();
boolean fast = (t < (lastMoveTime + app.prefs.getInt("moveSpeedTimeout", 800)));
int speed = (fast ? app.prefs.getInt("moveSpeed2", 20) : app.prefs.getInt("moveSpeed1", 8));
movx *= speed;
movy *= speed;
setXY(x + (movx << zoom), y + (movy << zoom));
lastMoveTime = t;
}
private void zoomIn() {
if (zoom > 0) zoom--;
repaint();
}
private void zoomOut() {
if (zoom < 14) zoom++;
repaint();
}
public void paint(Graphics g) {
// Convert x,y to screen coordinates
final int w = getWidth();
final int h = getHeight();
final int sx = (x >> zoom) - (w / 2); // screen x position
final int sy = (y >> zoom) - (h / 2); // screen y position
final int xpos = sx >> 7;
final int xoff = sx & 0x7f;
final int ypos = sy >> 7;
final int yoff = sy & 0x7f;
//if (app.debug) app.debug("Position: " + x + " " + y + " -> " + xpos + " " + xoff + " " + ypos + " " + yoff + " " + zoom);
// Number of tiles wide/high. Round up, and add 1 to handle overlap.
final int tw = ((w + MapTile.WIDTH - 1) / MapTile.WIDTH) + 1;
final int th = ((h + MapTile.HEIGHT - 1) / MapTile.HEIGHT) + 1;
// Make sure image cache size is large enough that we don't
// have to re-decode tiles on every refresh.
app.cache.imageCacheSize = tw * th + 1;
// Identify visible tiles
XYZ[] visible = new XYZ[tw * th];
int nVisible = 0;
for (int ty = 0; ty < th; ty++) {
for (int tx = 0; tx < tw; tx++) {
XYZ xyz = new XYZ(xpos + tx, ypos + ty, zoom);
if (computeTileVisiblePixels(xyz, sx, sy, w, h) > 0) {
visible[nVisible++] = xyz;
}
}
}
// Sort tiles by how much space in the on-screen area it affects,
// so that we will request the most visible tiles first.
Sort.sort(visible, 0, nVisible, new Comparator() {
public int compare(Object o1, Object o2) {
int v1, v2;
// First, compare by visible pixels
v1 = computeTileVisiblePixels((XYZ)o1, sx, sy, w, h);
v2 = computeTileVisiblePixels((XYZ)o2, sx, sy, w, h);
if (v1 != v2) return v2 - v1;
// If visible pixels the same (probably fully visible), then compare by distance from center
v1 = computeTileDistance((XYZ)o1, sx, sy, w, h);
v2 = computeTileDistance((XYZ)o2, sx, sy, w, h);
return v1 - v2;
}
});
// Request/draw the tiles
requestID++;
for (int i = 0; i < nVisible; i++) {
XYZ xyz = visible[i];
MapTile mt = app.mtm.getMapTile(xyz);
int tx = (xyz.x * MapTile.WIDTH) - ((xpos * MapTile.WIDTH) + xoff);
int ty = (xyz.y * MapTile.HEIGHT) - ((ypos * MapTile.HEIGHT) + yoff);
Image image = mt.image; // take a reference now, in case background thread nulls out the field
if (image != null) {
// Got the image. Draw it.
g.drawImage(image, tx, ty, Graphics.TOP|Graphics.LEFT);
}
else if (mt.bytes == null && mt.rmsID == -1) {
if (mt.downloading) {
// yellow to indicate downloading
drawEmptyTile(g, 0xffff00, tx, ty);
}
else {
// orange to indicate waiting for download
drawEmptyTile(g, 0xff8000, tx, ty);
}
}
else { // Image is null, but does not need to be downloaded. That means we're waiting for decode.
if (mt.decoding) {
// green to indicate decoding
drawEmptyTile(g, 0x00ff00, tx, ty);
}
else {
// blue to indicate waiting for decode
drawEmptyTile(g, 0x0000ff, tx, ty);
}
}
}
// Draw the map pins over top of the tiles.
g.setFont(font);
final int fudge = 100;
final int PW = 16, PH = 16; // pin size
final long mapPinTextRadius = app.prefs.getInt("mapPinTextRadius", 600);
final long mapPinTextRadiusSquared = mapPinTextRadius * mapPinTextRadius;
for (int i = app.mpm.getMapPinCount() - 1; i >= 0; i--) { // reverse order, so closer map pins will overwrite more distant ones
MapPin pin = app.mpm.getMapPin(i);
if (pin == null) continue;
int px = (pin.x >> zoom) - sx;
int py = (pin.y >> zoom) - sy;
if (px >= (-fudge) && px <= (w + fudge) && py >= (-fudge) && py <= (h + fudge)) {
// The pin is on-screen, or close enough. Draw it.
g.setColor(pin.isPermanent ? 0xff0000 : 0x0000ff);
g.fillArc(px - PW, py - PH, PW*2, PH*2, 90, 15);
g.drawLine(px, py - PH, px, py); // draw a line too, so it always goes right to the point.
// Draw text? (pythagorean theorem)
int hdist = Math.abs(x - pin.x);
int vdist = Math.abs(y - pin.y);
if ( (hdist * hdist) + (vdist * vdist) < mapPinTextRadiusSquared) {
g.setColor(0xffffff);
g.fillRect(px + 1, py - PH, font.stringWidth(pin.text), font.getHeight());
g.setColor(0x000000);
g.drawString(pin.text, px + 1, py - PH, Graphics.TOP|Graphics.LEFT);
}
}
}
// Draw crosshairs
g.setColor(0x808080);
final int cs = 3; // crosshair size
g.drawLine(w/2 - cs, h/2, w/2 + cs, h/2);
g.drawLine(w/2, h/2 - cs, w/2, h/2 + cs);
if (app.prefs.getInt("callgc", 0) >= 3) System.gc();
}
// Returns the number of pixels currently visible from the specified tile.
// Return value ranges from 0 (completely off-screen) to
// MapTile.WIDTH*MapTile.HEIGHT (completely on-screen).
private int computeTileVisiblePixels(XYZ tile, int sx, int sy, int w, int h) {
// convert to pixels
int tx = tile.x * MapTile.WIDTH;
int ty = tile.y * MapTile.HEIGHT;
// figure visible width, visible height
int vw = Math.max(0, (
Math.min(tx + MapTile.WIDTH, sx + w) -
Math.max(tx, sx) ));
int vh = Math.max(0, (
Math.min(ty + MapTile.HEIGHT, sy + h) -
Math.max(ty, sy) ));
// area
return vw * vh;
}
// Returns the distance (squared actually) of the center of the specified
// tile from the center of the screen.
private int computeTileDistance(XYZ tile, int sx, int sy, int w, int h) {
// convert to pixels
int tx = tile.x * MapTile.WIDTH;
int ty = tile.y * MapTile.HEIGHT;
// Compute horizontal and vertical distances
int hdist = Math.abs( (sx + (w / 2)) - (tx + (MapTile.WIDTH / 2)) );
int vdist = Math.abs( (sy + (h / 2)) - (ty + (MapTile.HEIGHT / 2)) );
// Pythagorean theorem
return (hdist * hdist) + (vdist * vdist); // don't need longs here, as distance will always be small
}
private void drawEmptyTile(Graphics g, int color, int x, int y) {
// Solid color
g.setColor(color);
g.fillRect(x, y, MapTile.WIDTH, MapTile.HEIGHT);
// ...with a thin black border
g.setColor(0x000000);
g.drawRect(x, y, MapTile.WIDTH, MapTile.HEIGHT);
}
protected void keyPressed(int keyCode) {
doKey(keyCode);
}
protected void keyRepeated(int keyCode) {
doKey(keyCode);
}
private void doKey(int keyCode) {
//app.debug("Key pressed: " + keyCode + "/" + getGameAction(keyCode) + "/" + getKeyName(keyCode));
switch (getGameAction(keyCode)) {
case UP:
moveView(0, -1);
break;
case DOWN:
moveView(0, 1);
break;
case LEFT:
moveView(-1, 0);
break;
case RIGHT:
moveView(1, 0);
break;
}
}
public void commandAction(Command c, Displayable d) {
String cmd = c.getLabel();
if (cmd.equals(ZOOM_IN)) {
zoomIn();
}
if (cmd.equals(ZOOM_OUT)) {
zoomOut();
}
if (cmd.equals(CREATE_MAP_PIN)) {
createMapPin();
}
if (cmd.equals(MAP_PIN_MENU)) {
app.mapPinMenu.cameFrom = this;
app.mapPinMenu.loadList();
app.display.setCurrent(app.mapPinMenu);
}
if (cmd.equals(SEARCH_MENU)) {
app.searchMenu.cameFrom = this;
app.searchMenu.loadList();
app.display.setCurrent(app.searchMenu);
}
if (cmd.equals(MAIN_MENU)) {
app.display.setCurrent(app.mainMenu);
}
}
private void createMapPin() {
final Dialog dlg = Dialog.createTextDialog("New map pin", "Map pin text", "", 40, TextField.ANY);
dlg.setCallback(new Runnable() { public void run() {
String userResponse = (String)dlg.getUserResponse();
if (userResponse != null) {
MapPin pin = new MapPin(x, y);
pin.text = (String)userResponse;
app.mpm.keepPin(pin);
}
}});
dlg.showOn(app);
}
/** Loads position from preferences. */
void loadPosition() {
x = app.prefs.getInt("canvas.x", -2505728);
y = app.prefs.getInt("canvas.y", -1282048);
zoom = app.prefs.getInt("canvas.zoom", 6);
}
/** Saves position to preferences. */
void savePosition() {
app.prefs.put("canvas.x", Integer.toString(x));
app.prefs.put("canvas.y", Integer.toString(y));
app.prefs.put("canvas.zoom", Integer.toString(zoom));
}
}