/*
GNU General Public License
CacheWolf is a software for PocketPC, Win and Linux that
enables paperless caching.
It supports the sites geocaching.com and opencaching.de
Copyright (C) 2006 CacheWolf development team
See http://www.cachewolf.de/ for more information.
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 2 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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package CacheWolf.navi;
import CacheWolf.MainForm;
import CacheWolf.Preferences;
import CacheWolf.controls.InfoBox;
import CacheWolf.database.BoundingBox;
import CacheWolf.database.CWPoint;
import CacheWolf.utils.BetterUTF8Codec;
import CacheWolf.utils.Common;
import CacheWolf.utils.FileBugfix;
import CacheWolf.utils.MyLocale;
import ewe.fx.Point;
import ewe.fx.Rect;
import ewe.io.DataInputStream;
import ewe.io.File;
import ewe.io.FileBase;
import ewe.io.FileInputStream;
import ewe.io.IOException;
import ewe.sys.Time;
import ewe.ui.FormBase;
import ewe.util.Comparer;
import ewe.util.Vector;
/**
* class to handle a list of maps (Vector of MapListEntry) it loads the list, finds the best map for a given location, says if a map is available for a given lat lon at a given scale start offset for language file: 4700
*
*/
public final class MapsList extends Vector {
// absolute deviations from this factor are seen to have the same scale
private static float scaleTolerance = 1.15f;
private static String MapsListVersion = "2";
private String mapsListDir;
/**
* loads all the maps in mapsPath in all subDirs recursive
*
* @param lat
* only for adding empty maps
*/
public MapsList(double lat) {
mapsListDir = MainForm.profile.getMapsDir();
String MapsListPaN = mapsListDir + "MapsList.txt";
File MapsListFile = new File(MapsListPaN);
boolean dontBuildMapsListFile = MapsListFile.exists();
if (dontBuildMapsListFile) {
dontBuildMapsListFile = readMapsListFile(MapsListPaN);
}
if (!dontBuildMapsListFile) {
initMapsList();
writeMapsListFile(MapsListPaN);
}
if (this.isEmpty()) {
new InfoBox(MyLocale.getMsg(4201, "Information"), MyLocale.getMsg(4204, "No georeferenced map available \n Please choose a scale \n to show the track and the caches. \n You can get one by the menu: Application/Maps/download calibrated"))
.wait(FormBase.OKB);
}
// the empty maps must be added last, otherwise in method setbestMIO, when no map is available, a malfunction will happen, see there
this.addEmptyMaps(lat);
this.onCompletedRead();
}
private void initMapsList() {
String dateien[];
String[] dirstmp;
Vector dirs = new Vector();
dirs.add(""); // start with the mapsPath (only this one , without its subdirs) = + dirs.get(0)
Preferences.itself().log("building mapslist started " + mapsListDir);
FileBugfix files = new FileBugfix(mapsListDir);
FileBugfix dirList = new FileBugfix(mapsListDir);
for (int j = 0; j < dirs.size(); j++) {
String aktPath;
//add subdirectories
aktPath = mapsListDir + dirs.get(j);
dirList.set(null, aktPath);
// the options "File.LIST_DONT_SORT | File.LIST_IGNORE_DIRECTORY_STATUS" make it run about twice as fast in sun-vm.
// The option File.LIST_IGNORE_DIRECTORY_STATUS influences only the sorting (dirs first)
dirstmp = dirList.list(null, FileBase.LIST_DIRECTORIES_ONLY | FileBase.LIST_DONT_SORT | FileBase.LIST_IGNORE_DIRECTORY_STATUS);
if (dirstmp != null) {
for (int subDir = 0; subDir < dirstmp.length; subDir++) {
if (!dirstmp[subDir].startsWith(".")) {
String toAdd = dirs.get(j) + dirstmp[subDir] + "/";
dirs.add(j + 1 + subDir, toAdd);
}
}
}
files.set(null, aktPath);
dateien = files.list("*.wfl", FileBase.LIST_FILES_ONLY | FileBase.LIST_DONT_SORT | FileBase.LIST_IGNORE_DIRECTORY_STATUS);
if (dateien != null) {
if (dateien.length == 0) {
// check if there is a tiles - structure in directories
String p[] = ewe.util.mString.split(MainForm.profile.getMapsSubDir(aktPath), '/');
if (p.length > 4) {
String wmsPaN = FileBase.getProgramDirectory() + "/webmapservices/" + p[p.length - 4] + ".wms";
File wmsFile = new File(wmsPaN);
// (.../Google/<zoom>/<x>/<y>.png ) und Google.wms existiert
// Definition: there is a tiles - structure,
// if there exist no wfl - files in the actual directory (aktPath)
// but graphic files,
// if there exists a wms - file with the same name as the directory 3 steps above the actual directory (aktPath)
if (wmsFile.exists()) {
String imageExtension = "*.png";
try {
WebMapService wms = new WebMapService(wmsPaN);
imageExtension = "*" + wms.imageFileExt;
} catch (Exception e) {
}
dateien = files.list(imageExtension, FileBase.LIST_FILES_ONLY | FileBase.LIST_DONT_SORT | FileBase.LIST_IGNORE_DIRECTORY_STATUS);
int zoom = Common.parseInt(p[p.length - 3]);
if (zoom > 0) {
int x = Common.parseInt(p[p.length - 2]);
if (x >= 0) {
// int y from dateien
for (int i = 0; i < dateien.length; i++) {
int y = Common.parseInt(Common.getPathAndFilename(dateien[i]));
if (y >= 0) {
String filename = dateien[i].substring(0, dateien[i].lastIndexOf('.'));
MapImageFileNameObject MapImageFileNameObject = new MapImageFileNameObject(MainForm.profile.getMapsSubDir(aktPath), filename, "");
MapInfoObject mio = new MapInfoObject(x, y, zoom, MapImageFileNameObject);
MapListEntry mle = new MapListEntry(MapImageFileNameObject, "FF1" + mio.getEasyFindString(), (byte) 2);
if (mle.sortEntryBBox != null)
add(mle);
}
}
}
}
}
}
} else {
// all dateien have .wfl extension
for (int i = 0; i < dateien.length; i++) {
MapListEntry mle = new MapListEntry(MainForm.profile.getMapsSubDir(aktPath), dateien[i].substring(0, dateien[i].lastIndexOf('.')));
if (mle.sortEntryBBox != null)
add(mle);
}
}
}
// the .pack files
dateien = files.list("*.pack", FileBase.LIST_FILES_ONLY | FileBase.LIST_DONT_SORT | FileBase.LIST_IGNORE_DIRECTORY_STATUS);
if (dateien != null) {
if (dateien.length > 0) {
for (int i = 0; i < dateien.length; i++) {
createMapListEntries(aktPath + dateien[i]);
}
}
}
}
// if (MapListEntry.rename == 1)
// reset static changes to initial values
MapListEntry.loadingFinished();
Preferences.itself().log("building mapslist finished " + mapsListDir);
}
private void createMapListEntries(String thePackFile) {
FileInputStream stream;
DataInputStream reader;
try {
File queryFile = new File(thePackFile);
stream = new FileInputStream(queryFile);
reader = new DataInputStream(stream);
readString(reader, 32); // String layerName =
readString(reader, 128); // String friendlyName =
readString(reader, 256); // String url =
readReverseLong(reader); // long ticks =
// long MaxAge = ticks;
int numBoundingBoxes = readReverseInt(reader);
for (int i = 0; i < numBoundingBoxes; i++) {
try {
int zoom = readReverseInt(reader);
int MinX = readReverseInt(reader);
int MaxX = readReverseInt(reader);
int MinY = readReverseInt(reader);
int MaxY = readReverseInt(reader);
long OffsetToIndex = readReverseLong(reader);
int Stride = MaxX - MinX + 1; // length of stripe
for (int x = MinX; x <= MaxX; x++) {
for (int y = MinY; y <= MaxY; y++) {
MapImageFileNameObject MapImageFileNameObject = new MapImageFileNameObject(thePackFile.substring(Preferences.itself().absoluteMapsBaseDir.length(), thePackFile.length() - 5), MinX + "!" + MinY + "!" + Stride + "!"
+ OffsetToIndex + "!" + zoom + "!" + x + "!" + y, "");
MapInfoObject mio = new MapInfoObject(x, y, zoom, MapImageFileNameObject);
MapListEntry mle = new MapListEntry(MapImageFileNameObject, "FF1" + mio.getEasyFindString(), (byte) 3);
if (mle.sortEntryBBox != null)
add(mle);
}
}
} catch (Exception e) {
}
}
reader.close();
stream.close();
} catch (Exception e) {
}
}
private String readString(DataInputStream reader, int length) throws IOException {
byte[] asciiBytes = new byte[length];
int last = 0;
for (int i = 0; i < length; i++) {
asciiBytes[i] = reader.readByte();
if (asciiBytes[i] > 32)
last = i;
}
StringBuffer sb = new BetterUTF8Codec().decodeUTF8(asciiBytes, 0, last + 1);
return sb.toString().trim();
}
private long readReverseLong(DataInputStream reader) throws IOException {
byte byte8 = reader.readByte();
byte byte7 = reader.readByte();
byte byte6 = reader.readByte();
byte byte5 = reader.readByte();
byte byte4 = reader.readByte();
byte byte3 = reader.readByte();
byte byte2 = reader.readByte();
byte byte1 = reader.readByte();
return (long) (((byte1 & 0xFF) << 56) + ((byte2 & 0xFF) << 48) + ((byte3 & 0xFF) << 40) + ((byte4 & 0xFF) << 32) + ((byte5 & 0xFF) << 24) + ((byte6 & 0xFF) << 16) + ((byte7 & 0xFF) << 8) + (byte8 & 0xFF));
}
private int readReverseInt(DataInputStream reader) throws IOException {
byte byte4 = reader.readByte();
byte byte3 = reader.readByte();
byte byte2 = reader.readByte();
byte byte1 = reader.readByte();
return (int) (((byte1 & 0xFF) << 24) + ((byte2 & 0xFF) << 16) + ((byte3 & 0xFF) << 8) + (byte4 & 0xFF));
}
private void writeMapsListFile(String PathAndName) {
Preferences.itself().log("write started " + PathAndName);
try {
ewe.io.TextWriter w;
w = new ewe.io.TextWriter(PathAndName, false);
w.codec = new BetterUTF8Codec();
w.println(MapsListVersion);
for (int z = 0; z < this.size(); z++) {
MapListEntry mle = (MapListEntry) this.get(z);
w.println(mle.getMapImageFileNameObject().getPath() + ";" + mle.getMapImageFileNameObject().getMapName() + ";" + mle.sortEntryBBox + ";" + mle.mapType);
}
w.println();
w.close();
} catch (IOException e) {
}
Preferences.itself().log("write finished " + PathAndName);
}
private boolean readMapsListFile(String PathAndName) {
// structure: <relative path to CustomMapsPath>;<mapName>;<sortEntryBBox>;<mapType>
boolean ret = true;
try {
ewe.io.TextReader r;
r = new ewe.io.TextReader(PathAndName);
r.codec = new BetterUTF8Codec();
String s; // structure read
String[] S; // structure splitted by ";"
s = r.readLine();
// check version (changes during development and intermediate svn - uploads)
if (s.equals(MapsListVersion)) {
// check first entry, directory could have been moved
s = r.readLine();
S = ewe.util.mString.split(s, ';');
if (S.length == 0)
return false;
if (S[3].equals("2")) {
if (Common.getImageName(Preferences.itself().absoluteMapsBaseDir + S[0] + "/" + S[1]) == null) {
ret = false;
}
} else {
File test;
if (S[3].equals("3")) {
test = new File(Preferences.itself().absoluteMapsBaseDir + S[0] + ".pack");
} else { // if (S[3].equals("0")) {
test = new File(Preferences.itself().absoluteMapsBaseDir + S[0] + S[1] + ".wfl");
}
if (!test.exists()) {
ret = false;
}
}
} else
ret = false;
while (s != null) {
S = ewe.util.mString.split(s, ';');
if (S.length == 4) {
MapListEntry mle = new MapListEntry(new MapImageFileNameObject(S[0], S[1], ""), S[2], (byte) Common.parseInt(S[3]));
this.add(mle);
} else {
if (s.length() > 0) {
r.close();
return false;
}
}
try {
s = r.readLine();
} catch (Exception e) {
//NPE
}
}
r.close();
} catch (IOException e) {
ret = false;
}
return ret;
}
public void addEmptyMaps(double lat) {
MapListEntry tempMIO;
tempMIO = new MapListEntry(1.0, lat);
add(tempMIO);
tempMIO = new MapListEntry(5.0, lat); // this one ( the 4th last) is automatically used when no real map is available, see MovingMap.setbestMIO
add(tempMIO);
tempMIO = new MapListEntry(50.0, lat);
add(tempMIO);
tempMIO = new MapListEntry(250.0, lat);
add(tempMIO);
tempMIO = new MapListEntry(1000.0, lat);
add(tempMIO);
}
/* diese Routine wird gegenw�rtig f�r 3 ZWecke verwendet:
* a) normal - keep given resolution --> L�sung: �bergebene scale nutzen f�r screen
* b) highest res: Ziel: Karte mit h�chster Aufl�sung, die im screen ist und m�glichst nah an lat/lon -> ich muss aufl�sung noch in Dateinamen schreiben
* c) gegenteil von b)
*/
/**
* the best map in the list of maps is the one whose center is nearest to ll.latDec/ll.lonDec and on screen and with its scale nearest to scale.
*
* @param ll
* CWPoint: ll.latDec/ll.lonDec a point to be inside the map
* @param screen
* Rect: width, height of the screen. The map must overlap the screen.
* @param scale
* float: scale wanted.
* @param forceScale
* : when true, return null if no map with specified scale could be found
* @param withProgressBox
* boolean: true -> with ProgressBox
* @return MapInfoObject if a Map in this list (mapsList Vector) overlaps the screen
*/
public MapInfoObject getBest(CWPoint ll, Rect screen, float scale, boolean forceScale, boolean withProgressBox) {
if (size() == 0)
return null;
long start = new Time().getTime();
InfoBox progressBox = null;
boolean showprogress = false;
String cmp = "FF1" + BoundingBox.getEasyFindString(ll, MAXDIGITS_IN_FF);
int guess = -1;
MapListEntry mle;
MapInfoObject mio;
MapInfoObject bestMIO = null;
double minDistLat = 1000000000000000000000000000000000000000000000.0;
double minDistLon = 1000000000000000000000000000000000000000000000.0;
boolean latNearer, lonNearer;
boolean better = false;
BoundingBox screenArea = null; // getAreaForScreen(screen, lat, lon, bestMIO.scale, bestMIO);
float lastscale = -1;
for (int digitlenght = 0; digitlenght < maxDigits; digitlenght++) {
guess = quickfind(cmp, this.numDigitsStartIndex[digitlenght], this.numDigitsStartIndex[digitlenght + 1] - 1);
for (int i = guess; i >= numDigitsStartIndex[digitlenght]; i--) {
if (withProgressBox) {
if (!showprogress && ((i & 31) == 0) && (new Time().getTime() - start > 100)) {
showprogress = true;
progressBox = new InfoBox(MyLocale.getMsg(327, "Info"), MyLocale.getMsg(4701, "Searching for best map"));
progressBox.exec();
progressBox.waitUntilPainted(100);
ewe.sys.Vm.showWait(true);
Preferences.itself().log(MyLocale.getMsg(4701, "Searching for best map"));
}
}
mle = (MapListEntry) get(i);
if (!BoundingBox.containsRoughly(mle.sortEntryBBox, cmp))
break; // TODO if no map available
else {
mio = mle.getMap();
}
better = false;
if (screenArea == null || !scaleEquals(lastscale, mio)) {
screenArea = getAreaForScreen(screen, ll, mio.scale, mio);
lastscale = mio.scale;
}
if (screenArea.isOverlapping(mio)) {
// is on screen
if (!forceScale || (forceScale && scaleEquals(scale, mio))) {
// different scale?
// inbound and resolution nearer at wanted resolution
// or old one is on screen but lat/long not inbound
// -> better
if (!forceScale && (mio.isInBound(ll) && (bestMIO == null || scaleNearer(mio.scale, bestMIO.scale, scale) || !bestMIO.isInBound(ll))))
better = true;
else {
if (bestMIO == null || scaleNearerOrEuqal(mio.scale, bestMIO.scale, scale)) {
latNearer = java.lang.Math.abs(ll.latDec - mio.center.latDec) / mio.sizeKm < minDistLat;
lonNearer = java.lang.Math.abs(ll.lonDec - mio.center.lonDec) / mio.sizeKm < minDistLon;
// for faster processing:
// if lat and lon are nearer then the distancance doesn't need to be calculated
if (latNearer && lonNearer)
better = true;
else {
if ((latNearer || lonNearer)) {
if (bestMIO == null || mio.center.getDistanceRad(ll) < bestMIO.center.getDistanceRad(ll)) {
better = true;
}
}
}
}
}
if (better) {
minDistLat = java.lang.Math.abs(ll.latDec - mio.center.latDec) / mio.sizeKm;
minDistLon = java.lang.Math.abs(ll.lonDec - mio.center.lonDec) / mio.sizeKm;
bestMIO = mio;
}
}
}
}
}
if (progressBox != null) {
progressBox.close(0);
ewe.sys.Vm.showWait(false);
}
if (bestMIO == null)
return null;
// return a copy of the MapInfoObject so that zooming won't change the MapInfoObject in the list
return new MapInfoObject(bestMIO);
}
/*
public MapInfoObject getBestNotStrictScale(double lat, double lon, Area screen, float scale) {
MapInfoObject ret = getBest(lat, lon, screen, scale, true);
if (ret == null) ret = getBest(lat, lon, screen, scale, false);
return ret;
}
*/
private final static int MAXDIGITS_IN_FF = 30;
/**
* after calling onCompletedRead() this will contain a list of indexes at which a new number of digits in the sortEntryBBox start
*/
private int[] numDigitsStartIndex = new int[MAXDIGITS_IN_FF];
private int maxDigits = -1;
public void onCompletedRead() {
sort(new Comparer() {
public int compare(Object o1, Object o2) {
String s1 = ((MapListEntry) o1).sortEntryBBox;
String s2 = ((MapListEntry) o2).sortEntryBBox;
int ret = s1.length() - s2.length(); // sort shorter sortEntryBBox at the beginning
if (ret == 0)
ret = s1.compareTo(s2);
return ret;
}
}, false);
int digits_index = 0;
int numdigits = 0;
int s = size();
for (int i = 0; i < s; i++) {
if (((MapListEntry) get(i)).sortEntryBBox.length() > numdigits) {
numDigitsStartIndex[digits_index] = i;
digits_index++;
numdigits = ((MapListEntry) get(i)).sortEntryBBox.length();
}
}
numDigitsStartIndex[digits_index] = s;
maxDigits = digits_index;
}
/**
* @param llimitorig
* lower limit to start the search from
* @param ulimit
* upper limit to stop the search. llimit and ulimit must be set in a way that the sortEntryBBox of each entry betwenn the limits have the same length
* @param searchfor
* String starting with FF1, it should be longer than any sortEntryBoxString in the list
* @return the highest index which matches the searchfor-String. Look downward from there to find the best map, returns llimit if there is no match
*/
private int quickfind(String searchfor, int llimitorig, int ulimit) {
int llimit = llimitorig;
int test;
String cmp = ((MapListEntry) this.get(llimit)).sortEntryBBox;
String sshort = searchfor.substring(0, cmp.length());
int comp;
if (cmp.compareTo(sshort) > 0 || ((MapListEntry) this.get(ulimit)).sortEntryBBox.compareTo(sshort) < 0)
// if searchfor is not in the range, return llimit (llimit because getBest counts downward, so returning llimit will cause it to do only 1 test
return llimit;
while (llimit < ulimit - 1) {
test = (ulimit + llimit) / 2;
cmp = ((MapListEntry) this.get(test)).sortEntryBBox;
comp = cmp.compareTo(searchfor);
if (comp > 0) {
// test > searchfor
ulimit = test;
} else {
// test <= searchfor
if (comp < 0)
// test < serachfor
llimit = test;
else {
// test == searchfor
llimit = test;
ulimit = test;
}
}
}
// searchfor is between llimit and ulimit
// OR is at ulimit or higher
// OR at llimit or lower
// we want to return the highest index of the map which starts with searchfor
comp = ((MapListEntry) this.get(ulimit)).sortEntryBBox.compareTo(searchfor);
if ((comp <= 0) && searchfor.startsWith(((MapListEntry) this.get(ulimit)).sortEntryBBox))
llimit = ulimit; // search for is on ulimit or higher
if (!searchfor.startsWith(((MapListEntry) this.get(llimit)).sortEntryBBox))
llimit = llimitorig; // if the found mapListEntry doesn't contain the searchfor, then there is no map containing it.
return llimit;
}
/**
* @return a map which includs topleft and bottomright, if no map includes both it returns null
* @param if more than one map includes topleft and bottomright than the one will be returned which has its center nearest to topleft. If you have gps-pos and goto-pos as topleft and bottomright use gps as topleft. if topleft is really topleft or
* if it is bottomright is not relevant.
*/
public final MapInfoObject getMapForArea(CWPoint topleft, CWPoint bottomright) {
long start = new Time().getTime();
InfoBox progressBox = null;
boolean showprogress = false;
MapListEntry ml;
MapInfoObject mi;
String cmp = "FF1" + (new BoundingBox(topleft, bottomright)).getEasyFindString();
String cmppadded = Common.rightPad(cmp, 30);
MapInfoObject fittingmap = null;
int guess;
boolean latNearer, lonNearer;
boolean better;
double minDistLat = 10000000000000000000000.0;
double minDistLon = 10000000000000000000000.0;
for (int digitlength = 0; digitlength < maxDigits; digitlength++) {
guess = quickfind(cmppadded, this.numDigitsStartIndex[digitlength], this.numDigitsStartIndex[digitlength + 1] - 1);
if (((MapListEntry) get(guess)).sortEntryBBox.length() > cmp.length())
break; // if the sortEntryBBox indicates that it cannot contain both points, stop searching
for (int i = guess; i >= numDigitsStartIndex[digitlength]; i--) {
if (!showprogress && ((i & 31) == 0) && (new Time().getTime() - start > 100)) { // reason for (i & 7 == 0): test time only after i is incremented 15 times
showprogress = true;
progressBox = new InfoBox(MyLocale.getMsg(327, "Info"), MyLocale.getMsg(4701, "Searching for best map"));
progressBox.exec();
progressBox.waitUntilPainted(100);
ewe.sys.Vm.showWait(true);
}
ml = (MapListEntry) get(i);
if (!BoundingBox.containsRoughly(ml.sortEntryBBox, cmp))
// TODO if no map available
continue;
else {
mi = ml.getMap();
}
better = false;
if (mi.isInBound(topleft) && mi.isInBound(bottomright)) { // both points are inside the map
if (fittingmap == null || fittingmap.scale > mi.scale * scaleTolerance) {
better = true; // mi map has a better (lower) scale than the last knwon good map
} else {
if (scaleEquals(mi, fittingmap)) { // same scale as bestMIO till now -> test if its center is nearer to the gps-point = topleft
latNearer = java.lang.Math.abs(topleft.latDec - mi.center.latDec) / mi.sizeKm < minDistLat;
lonNearer = java.lang.Math.abs(topleft.lonDec - mi.center.lonDec) / mi.sizeKm < minDistLon;
if (latNearer && lonNearer)
better = true; // for faster processing: if lat and lon are nearer then the distancance doesn't need to be calculated
else {
if ((latNearer || lonNearer)) {
if (mi.center.getDistanceRad(topleft) < fittingmap.center.getDistanceRad(topleft))
better = true;
}
}
}
}
if (better) {
fittingmap = mi;
minDistLat = java.lang.Math.abs(topleft.latDec - mi.center.latDec);
minDistLon = java.lang.Math.abs(topleft.lonDec - mi.center.lonDec);
}
}
} // for i
} // for digitlength
if (progressBox != null) {
progressBox.close(0);
ewe.sys.Vm.showWait(false);
}
if (fittingmap == null)
return null;
// TODO in case that this one and the old one are identical this instantiation could eventually be avoided as it is done at every greater shift of the map
return new MapInfoObject(fittingmap);
}
/**
*
* @param ll
* lat/lon a point to be inside the map
* @param screen
* : width, height of the screen. The map must overlap the screen. xy: where is lat/lon on screen
* @param curScale
* reference scale to be changed
* @param moreDetails
* true: find map with more details == higher resolustion = lower scale / false find map with less details = better overview
* @return
*/
public MapInfoObject getMapChangeResolution(CWPoint ll, Rect screen, float curScale, boolean moreDetails) {
if (size() == 0)
return null;
long start = new Time().getTime();
InfoBox progressBox = null;
boolean showprogress = false;
MapListEntry ml;
MapInfoObject mi;
MapInfoObject bestMIO = null; // = (MapInfoObject)get(0);
double minDistLat = 1000000000000000000000000000000000000000000000.0;
double minDistLon = 1000000000000000000000000000000000000000000000.0;
boolean latNearer, lonNearer;
boolean better = false;
BoundingBox screenArea = null; // getAreaForScreen(screen, lat, lon, bestMIO.scale, bestMIO);
float lastscale = -1;
String cmp = "FF1" + BoundingBox.getEasyFindString(ll, MAXDIGITS_IN_FF);
for (int i = size() - 1; i >= 0; i--) {
// test time only after i is incremented 31 times
if (!showprogress && ((i & 31) == 0) && (new Time().getTime() - start > 100)) {
showprogress = true;
progressBox = new InfoBox(MyLocale.getMsg(327, "Info"), MyLocale.getMsg(4701, "Searching for best map"));
progressBox.exec();
progressBox.waitUntilPainted(100);
ewe.sys.Vm.showWait(true);
}
better = false;
ml = (MapListEntry) get(i);
if (!BoundingBox.containsRoughly(ml.sortEntryBBox, cmp))
// TODO if no map available
continue;
else {
mi = ml.getMap();
}
if (mi.getMapType() == 1)
continue; // leeres image
if (screenArea == null || !scaleEquals(lastscale, mi)) {
screenArea = getAreaForScreen(screen, ll, mi.scale, mi);
lastscale = mi.scale;
}
if (screenArea.isOverlapping(mi)) { // is on screen
if (bestMIO == null || !scaleEquals(mi, bestMIO)) { // different scale than known bestMIO?
if (mi.isInBound(ll) && ( // more details wanted and this map has more details? // less details than bestMIO
(moreDetails && (curScale > mi.scale * scaleTolerance) && (bestMIO == null || mi.scale > bestMIO.scale * scaleTolerance)) // higher resolution wanted and mi has higher res and a lower res than bestMIO, because we dont want to overjump one resolution step
|| (!moreDetails && (curScale * scaleTolerance < mi.scale) && (bestMIO == null || mi.scale * scaleTolerance < bestMIO.scale)) // lower resolution wanted and mi has lower res and a higher res than bestMIO, because we dont want to overjump one resolution step
))
better = true; // inbound and higher resolution if higher res wanted -> better
} else { // same scale as bestMIO -> look if naerer
latNearer = java.lang.Math.abs(ll.latDec - mi.center.latDec) / mi.sizeKm < minDistLat;
lonNearer = java.lang.Math.abs(ll.lonDec - mi.center.lonDec) / mi.sizeKm < minDistLon;
if (latNearer && lonNearer)
better = true; // for faster processing: if lat and lon are nearer then the distancance doesn't need to be calculated
else {
if ((latNearer || lonNearer)) {
if (mi.center.getDistanceRad(ll) < bestMIO.center.getDistanceRad(ll))
better = true;
}
}
} // same scale
if (better) {
minDistLat = java.lang.Math.abs(ll.latDec - mi.center.latDec) / mi.sizeKm;
minDistLon = java.lang.Math.abs(ll.lonDec - mi.center.lonDec) / mi.sizeKm;
bestMIO = mi;
}
}
}
if (progressBox != null) {
progressBox.close(0);
ewe.sys.Vm.showWait(false);
}
if (bestMIO == null)
return null;
return new MapInfoObject(bestMIO);
}
/**
* returns an area in lat/lon of the screen
*
* @param screen
* screen width/height and x/y position on the screen of lat/lon
* @param ll
* lat/lon a (reference) point on the screen
* @param scale
* scale (meters per pixel) of the map for which the screen edges are wanted
* @param map
* map for which the screen edges are wanted
* @return BoundingBox
*/
private BoundingBox getAreaForScreen(Rect screen, CWPoint ll, float scale, MapInfoObject map) {
BoundingBox ret = null;
Point xy = map.calcMapXY(ll);
Point topleft = new Point(xy.x - screen.x, xy.y - screen.y);
ret = new BoundingBox(map.calcLatLon(topleft), map.calcLatLon(topleft.x + screen.width, topleft.y + screen.height));
return ret;
}
public static boolean scaleEquals(MapInfoObject a, MapInfoObject b) {
//return java.lang.Math.abs(a.scale - b.scale) < scaleTolerance;
if (a.scale > b.scale)
return a.scale / b.scale < scaleTolerance;
else
return b.scale / a.scale < scaleTolerance;
}
public static boolean scaleEquals(float scale, MapInfoObject b) {
//return java.lang.Math.abs(scale - b.scale) < scaleTolerance;
if (scale > b.scale)
return scale / b.scale < scaleTolerance;
else
return b.scale / scale < scaleTolerance;
}
/**
*
* @param test
* @param old
* @param wanted
* @return true if test is nearer to wanted than old, false if the change in the scale is lower than scaleTolerance
*/
public static boolean scaleNearer(float test, float old, float wanted) {
float testa, wanta, wantb, olda;
if (test > wanted) { // ensure that first term is greater than 1
testa = test;
wanta = wanted;
} else {
testa = wanted;
wanta = test;
}
if (old > wanted) { // ensure that second term is greater than 1
olda = old;
wantb = wanted;
} else {
olda = wanted;
wantb = old;
}
return testa / wanta * scaleTolerance < olda / wantb;
}
public static boolean scaleNearerOrEuqal(float test, float old, float wanted) {
float testa, wanta, wantb, olda;
if (test > wanted) { // ensure that first term is greater than 1
testa = test;
wanta = wanted;
} else {
testa = wanted;
wanta = test;
}
if (old > wanted) { // ensure that second term is greater than 1
olda = old;
wantb = wanted;
} else {
olda = wanted;
wantb = old;
}
return testa / wanta < olda / wantb * scaleTolerance;
}
public String getDirOfMapsList() {
return mapsListDir;
}
/* may be the following code is used same time later to further enhance the speed of finding the best map
public int getQuickMap(String search){
boolean found = false; // TODO unfertig
int upperbound = 0;
int downbound = size();
int test;
while (!found) {
test = (upperbound + downbound)/2;
if ( ((Comparable)(get(test))).compareTo(search) < 0) downbound = test;
else upperbound = test;
}
return 1;
}
*/
/**
* for determining if a new map should be downloaded public boolean isInAmap(CWPoint topleft, CWPoint bottomright) { if (!latRangeList.isInRange(topleft.latDec) || !latRangeList.isInRange(bottomright.latDec)) ||
* !lonRangeList.inInRange(topleft.lonDec) || !lonRangeList.isInRange(boxsttomright.lonDec) return false; }
*/
}
final class MapListEntry {
public String sortEntryBBox;
private MapImageFileNameObject MapImageFileNameObject;
public MapInfoObject map = null;
public byte mapType = 0; // 0 = has wfl-file (from wms - Server or Maperitive), 1 = with zoom/x/y.imageExtension (from Tile - Server)
static int rename = 0;
static InfoBox renameProgressInfoB = null;
/**
* constructes a MapListEntry with given sortEntryBBox
*/
public MapListEntry(MapImageFileNameObject MapImageFileNameObject, String sortEntryBBox, byte mapType) {
this.MapImageFileNameObject = MapImageFileNameObject;
this.sortEntryBBox = sortEntryBBox;
this.mapType = mapType;
}
/**
* constructes a MapListEntry with given sortEntryBBox from Filename (start with FF1, end with E-) or is calculated using the MapInfoObject created from wfl - File
*/
public MapListEntry(String subPath, String filenamei) {
this.MapImageFileNameObject = new MapImageFileNameObject(subPath, filenamei, "");
this.sortEntryBBox = null;
try {
if (filenamei.startsWith("FF1"))
sortEntryBBox = filenamei.substring(0, filenamei.indexOf("E-"));
} catch (IndexOutOfBoundsException ex) {
Preferences.itself().log("[MapsList:MapListEntry] Bad File in maps: " + filenamei);
}
String mapsPath = MainForm.profile.getMapsDir();
if (sortEntryBBox == null) {
try {
this.map = new MapInfoObject(MapImageFileNameObject);
sortEntryBBox = "FF1" + this.map.getEasyFindString();
rename = 2;
/* no longer needed
if (rename == 0) { // never asked before
if ((new InfoBox(MyLocale.getMsg(4702, "Optimisation"), MyLocale.getMsg(4703,
"Cachewolf can make loading maps much faster by adding a identification mark to the filename. Do you want me to do this now?\n It can take several minutes"), FormBase.YESB | FormBase.NOB)).execute() == FormBase.IDYES) {
renameProgressInfoB = new InfoBox(MyLocale.getMsg(327, "Info"), MyLocale.getMsg(4704, "\nRenaming file:") + " \n");
renameProgressInfoB.exec();
renameProgressInfoB.waitUntilPainted(100);
rename = 1; // rename
}
else
rename = 2; // don't rename
}
*/
if (rename == 1) {
String imageExtension = "";
String f = mapsPath + subPath + filenamei + ".wfl";
renameProgressInfoB.setInfo(MyLocale.getMsg(4704, "\nRenaming file: ") + f + "\n");
String to = sortEntryBBox + "E-" + filenamei + ".wfl";
if (!new File(f).rename(to))
new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4705, "Failed to rename:\n") + f + MyLocale.getMsg(4706, "\nto:\n") + to).wait(FormBase.OKB);
f = Common.getImageName(mapsPath + subPath + filenamei);
if (f != null) {
imageExtension = Common.getExtension(f);
to = sortEntryBBox + "E-" + filenamei + imageExtension;
if (!new File(f).rename(to)) {
Preferences.itself().log("MapListEntry Failed to rename: " + mapsPath + subPath + filenamei + ": " + f + " to: " + to, null);
new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4705, "Failed to rename:\n") + f + MyLocale.getMsg(4706, "\nto:\n") + to).wait(FormBase.OKB);
}
} else {
Preferences.itself().log("MapListEntry: Could not find image assiciated to: " + mapsPath + subPath + filenamei + ".wfl", null);
new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4709, "Could not find image assiciated to:\n") + mapsPath + subPath + filenamei + ".wfl").wait(FormBase.OKB);
}
// this.MapImageFileNameObject.setMapName(sortEntryBBox + "E-" + filenamei);
this.MapImageFileNameObject.setMapName(filenamei);
this.MapImageFileNameObject.setImageExtension(imageExtension);
}
} catch (IOException ioex) { // this should not happen
new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4707, "I/O-Error while reading:") + " " + mapsPath + subPath + filenamei + ": " + ioex.getMessage()).wait(FormBase.OKB);
Preferences.itself().log("MapListEntry: I/O-Error while reading: " + mapsPath + subPath + filenamei + ": ", ioex);
} catch (Exception ex) {
new InfoBox(MyLocale.getMsg(5500, "Error"), MyLocale.getMsg(4706, "Error while reading:") + " " + mapsPath + subPath + filenamei + ": " + ex.getMessage()).wait(FormBase.OKB);
Preferences.itself().log("MapListEntry: Error while reading: " + mapsPath + subPath + filenamei + ": ", ex);
}
}
}
/**
* constructes a MapListEntry with a MapInfoObject without an associated map but with 1 Pixel = scale meters
*/
public MapListEntry(double scale, double lat) {
this.MapImageFileNameObject = new MapImageFileNameObject("", MyLocale.getMsg(4300, "empty 1 Pixel = ") + scale + MyLocale.getMsg(4301, "meters"), "");
map = new MapInfoObject(scale, lat, MapImageFileNameObject);
this.sortEntryBBox = "FF1";
this.mapType = 1;
}
public MapInfoObject getMap() {
if (this.map != null)
return this.map;
else
// implicit sets this.map
return new MapInfoObject(this);
}
public static void loadingFinished() {
if (renameProgressInfoB != null)
renameProgressInfoB.close(0);
renameProgressInfoB = null;
rename = 0;
}
public MapImageFileNameObject getMapImageFileNameObject() {
return MapImageFileNameObject;
}
public void setMapImageFileNameObject(MapImageFileNameObject MapImageFileNameObject) {
this.MapImageFileNameObject = MapImageFileNameObject;
}
public String getMapNameForList() {
return MapImageFileNameObject.getMapNameForList(mapType);
}
public byte getMapType() {
return mapType;
}
}