/*
* 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.provider;
import java.io.IOException;
import javax.microedition.lcdui.*;
import com.mapmidlet.CloudGps;
import com.mapmidlet.misc.*;
import com.mapmidlet.options.Options;
/**
* Class representing tile - square map fragment. All it does is load (from HTTP
* or from file) and paint itself.
*
* @author Damian Waradzyn
*/
public abstract class Tile {
public static final int STATE_EMPTY = 0;
public static final int STATE_LOADING = 1;
public static final int STATE_ERROR = 2;
public static final int STATE_LOADED = 3;
public static final int STATE_SCALED_DOWN = 4;
public static final int STATE_SCALED_UP = 4;
public int state;
protected int width, height;
protected double latitude, longitude;
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
protected int zoom;
private Image image;
private long imageLoadTime;
private Thread backgroundJob = null;
private String error;
private static Worker downloader = new Worker("downloader_", true, 64, 4);
public Tile(int zoom, int width, int height, double latitude, double longitude) {
this.zoom = zoom;
this.width = width;
this.height = height;
this.latitude = latitude;
this.longitude = longitude;
}
public void load() {
state = STATE_LOADING;
int mapSize = 1 << zoom;
if (latitude < 0 || longitude < 0 || latitude >= mapSize || longitude >= mapSize) {
state = STATE_EMPTY;
return;
}
Options options = Options.getInstance();
if (options.useFileApi && !options.fileReadInSeparateThread) {
loadFromFile();
}
if (image == null) {
downloader.addTask(new TileLoader(this));
// backgroundJob = new Thread();
// backgroundJob.start();
}
}
private void loadFromFile() {
try {
// long time = System.currentTimeMillis();
String fileUrl = getFileUrl();
image = IOTool.readFile(fileUrl);
// System.out.println("read file time " +
// (System.currentTimeMillis() - time) + " ms");
imageLoadTime = System.currentTimeMillis();
if (image != null) {
state = STATE_LOADED;
}
} catch (Exception e) {
error = e.getClass().getName() + ": " + e.getMessage();
state = STATE_ERROR;
}
}
public void paint(Graphics g, int screenWidth, int screenHeight) {
switch (state) {
case STATE_LOADED:
paintLoaded(g, screenWidth, screenHeight);
break;
case STATE_LOADING:
paintLoading(g);
break;
case STATE_ERROR:
paintError(g);
break;
default:
paintEmpty(g, width, height);
}
}
private static void clear(Graphics g, int width, int height) {
g.setColor(0, 0, 0);
g.fillRect(0, 0, width, height);
}
private void clear(Graphics g) {
g.setColor(0, 0, 0);
g.fillRect(0, 0, width, height);
}
private void paintLoading(Graphics g) {
clear(g);
int loadingFrame = (int) ((System.currentTimeMillis() / 80) % 8);
Image img = Options.getInstance().skin.getImage("loading" + loadingFrame + ".png");
g.drawImage(img, width / 2, height / 2, Graphics.HCENTER | Graphics.VCENTER);
}
private void paintError(Graphics g) {
clear(g);
Image img = Options.getInstance().skin.getImage("error.png");
g.drawImage(img, width / 2, height / 2, Graphics.HCENTER | Graphics.VCENTER);
g.setColor(255, 0, 0);
if (error != null) {
g.drawString(error, width / 2, height / 2, Graphics.HCENTER | Graphics.BASELINE);
}
}
private void paintLoaded(Graphics g, int screenWidth, int screenHeight) {
Image tmp = image;
if (tmp != null) {
long currentTime = System.currentTimeMillis();
if (Options.getInstance().fadeEffect && currentTime - imageLoadTime < 2550) {
int[] subImageData = new int[tmp.getWidth() * tmp.getHeight()];
tmp.getRGB(subImageData, 0, tmp.getWidth(), 0, 0, tmp.getWidth(), tmp.getHeight());
int len = subImageData.length;
for (int i = 0; i < len; i++) {
int a = 0;
int color = (subImageData[i] & 0x00FFFFFF);
a = (int) ((currentTime - imageLoadTime) / 10);
color += a << 24;
subImageData[i] = color;
}
g.drawImage(Image.createRGBImage(subImageData, tmp.getWidth(), tmp.getHeight(), true), 0, 0,
Graphics.TOP | Graphics.LEFT);
} else {
g.drawImage(tmp, 0, 0, Graphics.TOP | Graphics.LEFT);
}
} else {
paintEmpty(g, width, height);
}
}
public static void paintEmpty(Graphics g, int width, int height) {
clear(g, width, height);
}
public String toString() {
return "(" + latitude + ", " + longitude + ", " + (image == null) + ")";
}
public void deallocate() {
image = null;
if (backgroundJob != null) {
try {
backgroundJob.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
}
}
abstract protected String getFileUrl();
abstract protected String getHttpUrl();
public static class TileLoader implements Runnable {
private Tile tile;
public TileLoader(Tile tile) {
this.tile = tile;
}
public void run() {
final Options opts = Options.getInstance();
try {
if (opts.useFileApi && opts.fileReadInSeparateThread) {
tile.loadFromFile();
}
if (tile.image == null && opts.onlineMode) {
tile.state = STATE_LOADING;
int retries = 0;
while (tile.image == null && retries < opts.maxRetries) {
try {
tile.image = IOTool.downloadAndSaveImage(tile.getHttpUrl(), tile.getFileUrl());
if (tile.image != null) {
tile.state = STATE_LOADED;
} else {
retries++;
tile.state = STATE_ERROR;
}
} catch (IOException e) {
e.printStackTrace();
retries++;
tile.error = e.getClass().getName() + ": " + e.getMessage();
}
}
}
if (tile.image == null) {
tile.state = STATE_ERROR;
if (tile.error == null) {
tile.error = "Load failed";
}
}
} catch (Throwable e) {
if (e instanceof InterruptedException) {
return;
}
CloudGps.setError(e);
tile.error = e.getClass().getName() + ": " + e.getMessage();
tile.state = STATE_ERROR;
}
}
}
}